You would like to create your own Windows Forms control by building it up from other existing controls.
Sample code folder: Chapter 14\UserControl
Create a user control, a custom user-interface control built from a drawing surface in which any other existing controls can appear.
Visual Basic allows you to build two types of controls: user controls and custom controls. User controls act somewhat like borderless forms on which you can "draw" other existing controls. Custom controls provide no default user interface; you must manage all custom control drawing yourself through source code. This recipe will focus on the user control, designing a simple control that displays the current time.
Create a new Windows Forms application. For now, we'll just ignore the Form1 form included in the project. To add a new user control to the project, select the Project Add User Control menu command. Accept the default Figure 14-2. A new user control surface
Our simple user control will include two constituent controls: a label to display the time, and a timer that will trigger once a second to update the time. First, resize the user control down to a reasonable size. We used a Size property of 96, 24. Add a Label control named Label1, and set the following properties:
Add a Timer control named Timer1, and set the following properties:
Switch to the source code for the user control through the View Code menu command, and add the following source code:
Public Class UserControl1 Public Event TimeChanged(ByVal sender As UserControl1, _ ByVal e As System.EventArgs) Private Sub Timer1_Tick(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Timer1.Tick ' ----- Update every second. Dim newTime As String If (Me.DesignMode = False) Then newTime = Format(Now, "h:mmtt").ToLower( ) If (newTime <> Label1.Text) Then Label1.Text = newTime RaiseEvent TimeChanged(Me, New System.EventArgs) End If End If End Sub Private Sub UserControl1_Load(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles Me.Load ' ----- Always reset the time when first started. If (Me.DesignMode = False) Then Label1.Text = Format(Now, "h:mmtt").ToLower( ) RaiseEvent TimeChanged(Me, New System.EventArgs) End If End Sub End Class
That's the whole control. It's just about ready to add to the Form1 surface, but you first have to build the project to allow Visual Studio to create an instance of the control. Build it using the Build Build WindowsApplication1 menu command.
Switch over to the Form Designer for Form1. If you open the Toolbox, you will see the user control UserControl1 in the magically added WindowsApplication1 Components section, as shown in Figure 14-3. (The section name will vary if you gave your project a different name.)
Figure 14-3. The new UserControl1 control in the Toolbox
Double-click the user control in the Toolbox to add it to the form surface. It should display the "12:00am" message we added to the control's label. However, if you run the application, the running form will display the correct time.
Our user control included a public event named TimeChanged:
Public Event TimeChanged(ByVal sender As UserControl1, _ ByVal e As System.EventArgs)
You can respond to this event from Form1. Open the source code for Form1, and add the following event handler:
Private Sub UserControl11_TimeChanged( _ ByVal sender As UserControl1, _ ByVal e As System.EventArgs) _ Handles UserControl11.TimeChanged MsgBox("Changed!") End Sub
Now, when you run the program, a "Changed!" message appears at startup (via the code for the user control's UserControl1_Load event handler), and also every time the minute changes (via the user control's Timer1_Tick event handler).
Visual Basic 2005 lets you easily design a new user control using mixtures of existing controls. You can also draw on the user control's surface through its Paint event handler, but you don't have to. (If you wish to update the surface via Paint, and not through subordinate controls, use a custom control instead of a user control.)
All child controls added to the surface of the user control are "owned" by the user control, not by (in this example) Form1. This means that your control can monitor any normal control events for its child controls, but the form using your user control will not know about those events. In this recipe, the user control exposes a Click event that Form1 can monitor. An event fires any time the user clicks on the user control surface. However, because we covered the surface with a label, clicks will never reach the user control surface, and the form will never be informed of such click events. If you want clicks on the label to transfer to the user control, you must manage that yourself. Adding this code to the user control's source code will do the trick:
Public Shadows Event Click(ByVal sender As Object, _ ByVal e As System.EventArgs) Private Sub Label1_Click(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles Label1.Click RaiseEvent Click(Me, e) End Sub Private Sub UserControl1_Click(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles MyBase.Click RaiseEvent Click(Me, e) End Sub
Because the UserControl class (from which our UserControl1 class derives) already exposes a Click event, you have to cover it up by declaring a new Click event. The Shadows keyword covers up the event in the base. Now add Click event handlers to capture clicks on both the Label and UserControl surfaces, and pass them on to those who add UserControl1 to their forms. Look carefully at the UserControl1_Click event handler just above. Make sure that it handles MyBase.Click, and not Me.Click. If you use Me.Click, a click on the control surface will repeatedly call itself until you run out of stack space.
After adding this code, resize the label a little smaller so that the user can click on the user control surface. Return to the source code for Form1, and add this code to its class template:
Private Sub UserControl11_Click(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles UserControl11.Click MsgBox("Clicked!") End Sub
Now run the program. You will see the "Clicked!" message whether you click on the label or the user control surface.
If you are building a user control for use elsewhere in the same project, any child controls you include on the surface of your user control will, by default, be accessible to the entire application. For instance, in this recipe's code, you can access the caption for the user control's label from the code for Form1. Go back to that UserControl11_TimeChanged event handler you added to Form1. On a new line, type the following:
As you type the letter L, you will see Label1 appear in the IntelliSense pop up. If you don't want this to happen, return to the user control designer, select Label1, and change its Modifers property to Private instead of Friend.