Title: Use a symbiote to graphically select hours in C#
This example modifies the previous example Graphically select hours in C# so it uses a symbiote class. That example shows how to use a PictureBox to let the user select a range of hours. If you want to let the user select more than one range, you could turn this into a custom control. You could place a PictureBox on a UserControl, compile it, and put as many of these as you like on a form.
One drawback to that method is that the new control must be compiled before you can use it or see it in the toolbox. That's not the end of the world and works pretty well if you're distributing compiled programs, but I only post source code, never compiled code. If I post a project that contains a non-compiled custom control, when you first load the project any form that uses the control cannot display it. You need to compile the project before you can see it.
An alternative is to make what I call a "symbiote" class. A symbiote is a class that is used to add features to another class. In this case, a SelectHoursSymbiote object adds features to a PictureBox to make it behave like the control used in the previous example. This gives you almost all of the benefits of a compiled custom control but you don't need to compile it separately. The drawback is that it doesn't appear in the toolbox. You create the PictureBox control and then attach the symbiote to it at run time.
The SelectHoursSymbiote class contains all of the code used by the previous example to work with the PictureBox control plus a little extra code to manage events and such.
The following code shows how the class declares its HoursScrolled and HoursChanged events.
// Declare events.
public event EventHandler HoursScrolled;
public event EventHandler HoursChanged;
These events tell the main program when the symbiote's hours change.
The following code shows the class's StartHour and StopHour properties.
// The selected hours.
private int _StartHour = 0;
private int _StopHour = 0;
public int StartHour
{
get { return _StartHour; }
set
{
_StartHour = value;
// Raise the HoursChanged event.
if (HoursChanged != null) HoursChanged(this, null);
}
}
public int StopHour
{
get { return _StopHour; }
set
{
_StopHour = value;
// Raise the HoursChanged event.
if (HoursChanged != null) HoursChanged(this, null);
}
}
These are fairly straightforward properties. You could implement them as public fields (if you're willing to ignore the purists who say you should never expose fields publicly), but I wanted the code to raise the HoursChanged event when these values changed.
The following code shows the class's only constructor.
// The PictureBox we manage.
public PictureBox PictureBox;
// Constructor.
public SelectHoursSymbiote(PictureBox pic)
{
PictureBox = pic;
// Add event handlers.
Pic.Paint += pic_Paint;
Pic.MouseDown += pic_MouseDown;
Pic.MouseMove += pic_MouseMove;
Pic.MouseUp += pic_MouseUp;
}
The PictureBox variable holds a reference to the PictureBox to which the symbiote is attached. The constructor saves a reference to the PictureBox.
It then gives the PictureBox event handlers to catch the Paint event and the mouse events it needs to let the user select hours. These event handlers are almost exactly the same as those used by the previous example except they work with the PictureBox control instead of the specific picHours control. See the previous example for information about how the mouse events and the Paint event work.
The only real differences are where the MouseMove and MouseUp event handlers raise events. After it calculates the newly selected hours, the MouseMove event handler uses the following code to raise the symbiote's HoursScrolled event.
// Raise the HoursScrolled event.
if (HoursScrolled != null) HoursScrolled(this, null);
The MouseUp event handler uses the following code to raise the HoursChanged event.
// Raise the HoursChanged event.
if (HoursChanged != null) HoursChanged(this, null);
The main program uses the following code to attach symbiotes to its PictureBox controls.
// The symbiotes.
private SelectHoursSymbiote Symbiote1, Symbiote2;
private void Form1_Load(object sender, EventArgs e)
{
this.ResizeRedraw = true;
// Create the symbiotes.
Symbiote1 = new SelectHoursSymbiote(picHours1);
Symbiote1.HoursChanged += pic_HoursChanged;
Symbiote1.HoursScrolled += pic_HoursChanged;
Symbiote1.StartHour = 6;
Symbiote1.StopHour = 14;
Symbiote2 = new SelectHoursSymbiote(picHours2);
Symbiote2.HoursChanged += pic_HoursChanged;
Symbiote2.HoursScrolled += pic_HoursChanged;
Symbiote2.StartHour = 9;
Symbiote2.StopHour = 17;
}
The code creates the new symbiotes, passing their constructors the PictureBox controls to which they should be attached. It registers event handlers for the symbiotes' HoursScrolled and HoursChanged events and gives them initial StartHour and StopHour values.
The following code shows how the program responds when it receives an HoursScrolled or HoursChanged event.
// Show the times in the TextBoxes.
// Show the times in the TextBoxes.
private void pic_HoursChanged(object sender, EventArgs e)
{
SelectHoursSymbiote symbiote = sender as SelectHoursSymbiote;
DateTime start_time =
new DateTime(2000, 1, 1, symbiote.StartHour, 0, 0);
DateTime stop_time =
new DateTime(2000, 1, 1, symbiote.StopHour, 0, 0);
string tip = start_time.ToShortTimeString() +
" to " +
stop_time.ToShortTimeString();
if (tipHour.GetToolTip(symbiote.PictureBox) != tip)
tipHour.SetToolTip(symbiote.PictureBox, tip);
if (symbiote == Symbiote1)
{
txtStartTime1.Text = start_time.ToShortTimeString();
txtStopTime1.Text = stop_time.ToShortTimeString();
}
else
{
txtStartTime2.Text = start_time.ToShortTimeString();
txtStopTime2.Text = stop_time.ToShortTimeString();
}
}
This code converts the symbiote's hours into DateTime objects and then displays them as short times in the appropriate textboxes.
That's all there is to it. Creating and using symbiote classes is remarkably easy. Usually it only takes a few minutes to convert code inside a form into a symbiote. (Writing it all up in a post takes a lot longer.😉)
Download the example to experiment with it and to see additional details.
|