Advanced Basics
Adding New Features with User Controls
Ken Spencer

Code download available at: AdvancedBasics0306.exe (156KB)

Q How can I draw a line on a Windows® Form that is selectable by the user and can respond to mouse events?


A In past versions of Visual Basic®, there were rudimentary graphics controls. In Visual Basic .NET you have the GDI+ library, which enables you to draw lines, circles, and most anything else. But how can you use the functionality of GDI+ to create lines and other graphics that respond to user mouse clicks and events?

To answer, I created a UserControl that implements a line drawn with GDI+. At first this seems like a trivial exercise, but it's fun because of the various properties you create to control a line and the testing you go through to make this work.

Figure 1 shows my test application using the new line control. You can see the line across the top of the form, and the status bar shows the results of clicking the line. You can fiddle with the line's size and other properties using this form by changing various options and clicking the Change Line Properties button.

Figure 1 Control Test App
Figure 1 Control Test App

Let's walk through the code used to implement this control. First, I created several enumerations to use for various line properties. The enumerations make it easy for users to enter a property value. They also strongly type the properties, making it very difficult to introduce errors by entering an incorrect value. The LineStyles enum controls the style of the line and the LineWeightStyles enum controls the weight (density) of the line:

Enum LineStyles
Solid = 0
    Hatched = 1
    TiledImage = 2
End Enum
Enum LineWeightStyles
    Small = 0
    Medium = 1
    Large = 2
    Graphic = 3
End Enum 

Next, I added a set of private variables to handle the internals of size, line weight, color, and other such properties. These variables are shown in Figure 2.

Then, I created several public properties that are used to control the line. These properties are shown in Figure 3. The LineWidth property is straightforward; it simply sets the width of the line in pixels. Notice that it also sets the width of the UserControl to make the control's size match the size of the line. LineColor determines the color of the line. The LineStyle property can be used to specify how the line is displayed. In this version, this property is not fully implemented since only the Solid and Hatched styles are supported. The LineWeight property controls the thickness of the line. This property is interesting because the weight controls not only the thickness of the line but also the height of the control. You can see how the value set using the enum is converted to a pixel value in the control. This allows you to present a meaningful value to the user such as Large and then you can internally set Large to, say, 15 pixels or so.

The next bit of code in the class is the LineClick event. This event is fired when the user clicks the control. The definition for the event is shown in the following code:

#Region "Public Events"
    Public Event LineClick(ByVal ClickedLocation As Point)
#End Region

Now let's walk through creating the line in the Paint event (see Figure 4). The Paint event fires when the control is dirty and must be cleaned up—when another window covers the control or parent of the control. This event is a good place to add code to draw something on the control. The first line after the Try statement calls the ResetSize procedure, which sets the UserControl's size to the width and the height of the line.

Now let's look at the Select Case statement inside the Try block. The first Case statement draws the solid line:

Select Case privateStyle
Case LineStyles.Solid
This line creates a new solid brush in the color selected by the user:
Dim SolidBrush As New SolidBrush(privateColor)
The next line creates a new instance of a pen with the solid brush just created and the weight selected by the user:
LinePen = New Pen(SolidBrush, privateWeightPixels)
Next, draw the line by calling DrawLine:
e.Graphics.DrawLine(LinePen, privateStart.X, privateStart.Y, _
    privateEnd.X, privateEnd.Y)

Now let's look at the Hatched line. Think of it as a little lesson in using GDI+ and a few aspects of the Microsoft® .NET Framework. Adding support for the hatched line only requires a few tweaks. The enum for the hatched style is already in place, so you only need to add support for this style in the Paint event. The first step in creating the hatched style is to create a HatchBrush instead of a SolidBrush:

Dim HatchedBrush As New HatchBrush(HatchStyle.DiagonalCross, privateColor)

The HatchStyle allows you to select a variety of options. This example uses a hardcoded style, but of course you can easily add an enum and property for the hatched style and implement a property for it. Then you simply change the HatchStyle to point to that variable. The rest of the code is the same as the Solid line:

LinePen = New Pen(HatchedBrush, privateWeightPixels)
e.Graphics.DrawLine(LinePen, privateStart.X, privateStart.Y, _
    privateEnd.X, privateEnd.Y)

Now, let's look at the code that handles the click event. The first thing to do is track where the user clicks the mouse. This is handled in the MouseMove event, which picks up the x and y coordinates of the clicked location:

Private Sub MyLine_MouseMove(ByVal sender As Object, _
    ByVal e As System.Windows.Forms.MouseEventArgs) _
    Handles MyBase.MouseMove
 privateCurrentMouseX = e.X
 privateCurrentMouseY = e.Y
End Sub

When the user clicks the line, they are really just clicking the UserControl, so the actual click is processed by the Click event for the UserControl.

Private Sub MyLine_Click(ByVal sender As Object, _
    ByVal e As System.EventArgs) Handles MyBase.Click
RaiseEvent LineClick(New Point(privateCurrentMouseX, _
    privateCurrentMouseY))
End Sub
As you can see, the LineClick event is fired in the Click event for the UserControl. The already set mouse coordinates are then passed to this event.

Figure 5 shows the key event procedures from the form which uses the new line control, FormTwo.vb. The form load event checks several properties in the line control (MyLine1) and modifies the interface as necessary for those controls. The cmdChange_Click sets the control from properties set on the form. This will repaint the control with the new values. The exception to this is the color picker, which repaints the control by resetting the LineColor property directly.

The most important code in FormTwo that is related to your question is the MyLine1_LineClick shown in the following code:

Private Sub MyLine1_LineClick( _
ByVal ClickedLocation As System.Drawing.Point) Handles MyLine1.LineClick
    StatusBar1.Text = "You clicked the line at " & _
        ClickedLocation.ToString
End Sub
This implements the LineClick event from MyLine, which fires when the line is clicked. This event simply takes the location that's clicked and shows it in the status bar.

That's it. You now have a line control that can show either a solid or hatched horizontal line.

As mentioned earlier, you are not limited to a line control using this technique. You can build anything—such as a rectangle, circle, curve, sophisticated graphics, and so on. All of these controls can respond to any mouse event you want to implement because you can intercept the click to the UserControl just as this control does.

The only trick in implementing many other kinds of graphics will be how you handle non-rectangular images and other shapes. Let's say you need to implement a diagonal line. To do so you'll need to handle the non-rectangular shape in order to prevent the control from blocking other controls that are behind it and outside of the drawn area. The second issue concerns oddly shaped graphics. For non-horizontal and non-vertical shapes, you must calculate where on the control the user clicked and possibly check that a point is contained within a region. If you try this yourself, you'll see that it's pretty straightforward, but more difficult than implementing simple horizontal and vertical shapes.


Send your questions and comments for Ken to  basics@microsoft.com.



Ken Spencer works for 32X Tech (http://www.32X.com), where he provides training, software development, and consulting services on Microsoft technologies.


© 2007 Microsoft Corporation and CMP Media, LLC. All rights reserved; reproduction in part or in whole without permission is prohibited.