In this section, you'll learn how to use some of the graphics primitives provided by Visual Basic .NET to draw your own graphics objects. Rather than just read about graphics objects, you will build a simple gauge control by using Visual Basic .NET's Graphics objects. You will develop the Gauge control by using the techniques you learned in Chapter 21. When you are finished, you will have a new control that you can use in other projects.
You should start by creating a new project and then selecting the Add User Control menu option. You should name the new control Gauge . The following section shows you how to start adding the code to the Gauge control.
The Gauge Control Code
Listing 22.3 shows the members of the Gauge control class.
Listing 22.3 The Data Members for the Gauge Control
Imports System.ComponentModel Public Class Gauge Inherits System.Windows.Forms.UserControl ' Windows Form Designer generated code '================== Data Members ===================== Private mMin As Double 'Minimum gauge value Private mMax As Double 'Maximum gauge value Private mDanger As Double 'Redline danger value Private mCaution As Double 'Yellowline cautionary value Private mRange As Double 'The range of gauge values Private mCurrent As Double 'Current gauge value Private mTick As Double 'A tick mark on the scale Private mGap As Integer 'The range between tick marks Private mTall As Integer 'How tall is the gauge Private mWide As Integer 'How wide is the gauge Private mLeftSide As Integer 'Where is the left side of the gauge Private mTopSide As Integer 'Where is the top of the gauge
Listing 22.3 begins by importing System.ComponentModel so you can classify each member and display a help message in the Properties window.
The purpose of each data member of the Gauge control is fairly clear from their names and comments in Listing 22.3. There are, however, two members that may be hard to figure out. The mDanger member allows the user to specify a value at which the color of the gauge changes to red. The mCaution member allows the user to set a value that causes the gauge to display yellow. Together, these two members allow the user to create the effect of redline and yellowline values for the gauge.
The mRange member is the range of values that should be displayed on the gauge. This is set by subtracting mMin from mMax . mTick is set by dividing mRange by 5 and is used for placing the scale values on the gauge. (To simplify things a bit, the number of scale values displayed is limited to five.) The remaining members are used to actually draw the control.
Listing 22.4 shows the property methods for the Gauge control.
Listing 22.4 The Gauge Control Property Methods
'================== The Property Procedures ===================== <Description("The minimum gauge value"), _ Category("Appearance")> _ Property Minimum() As Double ' Minimum Get Return mMin End Get Set(ByVal Value As Double) mMin = Value End Set End Property <Description("The maximum gauge value"), _ Category("Appearance")> _ Property Maximum() As Double ' Maximum Get Return mMax End Get Set(ByVal Value As Double) mMax = Value End Set End Property <Description("The gauge danger value where the color changes to red"), _ Category("Appearance")> _ Property Danger() As Double ' The redline danger value Get Return mDanger End Get Set(ByVal Value As Double) mDanger = Value End Set End Property <Description("The gauge caution value where the color changes to yellow"),_ Category("Appearance")> _ Property Caution() As Double ' The yellowline cautionary value Get Return mCaution End Get Set(ByVal Value As Double) mCaution = Value End Set End Property <Description("The gauge height"), _ Category("Appearance")> _ Property Tall() As Integer ' How tall is the gauge? Get Return mTall End Get Set(ByVal Value As Integer) mTall = Value End Set End Property <Description("The gauge width"), _ Category("Appearance")> _ Property Wide() As Integer ' How wide is the gauge? Get Return mWide End Get Set(ByVal Value As Integer) mWide = Value End Set End Property <Description("The left edge location of the gauge"), _ Category("Appearance")> _ Property LeftSide() As Integer ' Where's the left side of the gauge Get ' on the form? Return mLeftSide End Get Set(ByVal Value As Integer) mLeftSide = Value End Set End Property <Description("The top edge location of the gauge"), _ Category("Appearance")> _ Property TopSide() As Integer ' Where is the top of the gauge on Get ' the form? Return mTopSide End Get Set(ByVal Value As Integer) mTopSide = Value End Set End Property <Description("The present value of the gauge"), _ Category("Data")> _ Property Current() As Double ' What is current value? Get Return mCurrent End Get Set(ByVal Value As Double) mCurrent = Value End Set End Property
The code in Listing 22.4 is very straightforward. Each property method simply gets or sets the appropriate value for the data member of the control. The real work for the control is done in the remaining methods. The SetGaugeParameters() and Gauge_Resize() methods are shown in Listing 22.5.
Listing 22.5 The SetGaugeParameters() and Gauge_Resize() Methods
'==================== Methods ======================== Private Sub SetGaugeParameters() ' Purpose: This subroutine sets some of the parameters that are ' used by the gauge. ' ' Argument list: ' none ' ' Return value: ' n/a ' If mCurrent > mMax Then ' Value too large? mCurrent = mMax End If If mCurrent < mMin Then ' Value too small? mCurrent = mMin End If mRange = mMax - mMin ' Find the gauge range of values mTick = mRange / 5.0 ' Find where tick marks should start mGap = mTall / 5 ' Find the gaps between tick marks End Sub Private Sub Gauge_Resize(ByVal sender As Object, ByVal e As _ System.EventArgs) Handles MyBase.Resize Dim ControlWidth As Integer = Me.ClientRectangle.Width Dim ControlHeight As Integer = Me.ClientRectangle.Height If ControlHeight < 50 Then ' Set a minimum control height ControlHeight = 50 Me.Height = 50 End If If ControlWidth < 50 Then ' Set a minimum control width ControlWidth = 50 Me.Width = ControlWidth End If mTall = Me.Height * 0.9 mTopSide = (Me.Height - mTall) / 2 ' location for top edge of gauge mLeftSide = Me.Width * 0.05 ' Location for left edge of the gauge mWide = Me.Width * 0.2 ' The width of the gauge Invalidate() End Sub
The SetGaugeParameters() method is called to check and set some of the member values for the gauge. For example, if the current value for the gauge ( mCurrent ) is greater than mMax , mCurrent is set to mMax . Likewise, if the current value is too small, mCurrent is set to mMin . After the current value is checked, the mRange , mTick , and mGap values are determined.
You want the user to be able to resize the control, and the Gauge_Resize() method handles the Resize() event. Note that resizing is done relative to the control's size, not the size of the gauge. Because you want the gauge to be sized relative to the control's size, all your calculations for the gauge are based on the control size. Therefore, as the user resizes the control, the gauge should increase or decrease in size relative to the changes in the control's size.
This statement is used to determine the current width of the control:
Dim ControlWidth As Integer = Me.ClientRectangle.Width
Me , of course, refers to the Gauge control. The ClientRectangle method can be used to examine a list of parameters associated with the rectangle formed by the control. This statement returns the current width of the control. Your code examines only the width and height of the control, but the ClientRectangle method can be used to return additional information about the rectangle, including Top , Bottom , Left , Location , X , and Y values.
After you have determined the current size of the control, you should check to make sure the user is not trying to make the control too small. In this case, "too small" would be a control size that would not allow the gauge and its values to be displayed. You have hard-coded the minimum width and height to be 50 pixels. This value results in a fairly skinny gauge.
These statements set the properties for the new gauge size relative to the new size of the control:
mTall = Me.Height * 0.9 mTopSide = (Me.Height - mTall) / 2 ' location for top edge of gauge mLeftSide = Me.Width * 0.05 ' Location for left edge of the gauge mWide = Me.Width * 0.2 ' The width of the gauge
mTall sets the height of the gauge to be 90% of the height of the control. The next calculation sets the top of the gauge ( mTopSide ) so that the gauge is centered within the control's vertical size. The third statement places the left edge of the gauge ( mLeftSide ) so that it is located near the left edge of the control. (Its value is 5% of the control's width.) Finally, the width of the gauge ( mWide ) is set to be 20% of the control's width.
You can change the gauge parameters to suit your own needs. Indeed, you could even make the hard-coded parameters (for example, .90, .05,.20) members of the class and allow the user to set those, too. Although it would take a little more work, you could also orient the gauge to make it horizontal rather than vertical. (Trying to make these changes to the code would be a valuable learning experience!)
After the members have been reset to reflect the control's new size, the code calls the Invalidate() method. The Invalidate() method call causes Visual Basic .NET to send a message to Windows to redraw the control. Upon return from the call, the control is displayed to the user, using the resized parameters.
The OnPaint() Method
The real work for the gauge is done by the Graphics object's OnPaint() method. This method is designed to paint a Visual Basic .NET Graphics object. Each Graphics object has numerous methods associated with it, and these methods are responsible for all visual aspects of the object. These responsibilities include changing colors, drawing lines and shapes, filling shapes , drawing fonts, and taking care of other visual elements that appear onscreen. Indeed, every form you've used thus far has an OnPaint() method provided by the System.Windows.Forms.Form class.
Because it is possible to override the OnPaint() method, you can write your own version of the method and use it to draw the gauge. The argument to the OnPaint() method is PaintEventArgs , which contains a Graphics property that you can use for drawing purposes. The code for the OnPaint() method is shown in Listing 22.6.
Listing 22.6 The OnPaint() Method for the Gauge Control
Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs) ' Purpose: This subroutine overrides the OnPaint() event to draw ' the gauge ' ' Argument list: ' e the paint event arguments ' ' Return value: ' n/a ' ' CAUTION: As written, this code assumes there are valid values ' for mTop, mLeftSide, mTall, and mWide. No checks are ' performed. Dim Canvas As Graphics = e.Graphics Dim MyPen As Pen = New Pen(Color.Black) Dim MyBrush As SolidBrush = New SolidBrush(Color.Green) Dim MyStyle As FontStyle = FontStyle.Regular Dim MyFont As Font = New Font("Microsoft Sans Serif", 8, MyStyle) Dim NumberOffset As Integer, buff As String Dim TickOffset As Integer, TopOffset As Integer, TickValues As Double Dim i As Integer SetGaugeParameters() ' Calculate some initial values If mCaution <> 0 Or mDanger <> 0 Then ' have they set yellow or red lines? If mCurrent >= mCaution Then ' See if value requires a color change MyBrush.Color = Color.Yellow End If If mCurrent >= mDanger Then MyBrush.Color = Color.Red End If End If ' Draw a rectangle and then fill it with a background color Canvas.DrawRectangle(MyPen, mLeftSide, mTopSide, mWide, mTall) Canvas.FillRectangle(MyBrush, mLeftSide + 1, mTopSide + 1, mWide - 1, mTall - 1) MyBrush.Color = Color.Black ' Reset brush color for numbers TickOffset = mWide + mLeftSide TickValues = mMax NumberOffset = MyFont.GetHeight(Canvas) / 2 For i = 0 To 4 ' Draw the tick marks and values TopOffset = mTopSide + i * mGap ' First, draw tick marks Canvas.DrawLine(MyPen, TickOffset, TopOffset, TickOffset + 3, TopOffset) buff = Format(TickValues - mTick * i, ".00") ' Now draw scale values Canvas.DrawString(buff, MyFont, MyBrush, TickOffset + 5, _ TopOffset - NumberOffset) Next i ' Need this because of possible rounding errors for pixel values when drawing ' the minimum value tick mark. It draws the minimum tick and value Canvas.DrawLine(MyPen, TickOffset, mTopSide + mTall, TickOffset + 3, _ mTopSide + mTall) Canvas.DrawString(CStr(mMin), MyFont, MyBrush, TickOffset + 5, _ mTopSide + mTall - NumberOffset) ' Now draw a filled rectangle for the UNUSED portion of the gauge ' using a white brush MyBrush.Color = Color.White ' This determines how "deep" to draw the new rectangle. The adjustments ' by +/- 1 pixel is so we don't overwrite the gauge border i = (1.0 - mCurrent / mRange) * mTall + (mMin / mRange) * mTall - 1 Canvas.FillRectangle(MyBrush, mLeftSide + 1, mTopSide + 1, mWide - 1, i) End Sub
The OnPaint() method must use the Overrides keyword because you want to override the base class OnPaint() method. You use the PaintEventArgs parameter e to define a Graphics object named Canvas that paints the object. (The Graphics class has a number of public methods that are associated with it and that are used in this example.) The following statement defines the Graphics object:
Dim Canvas As Graphics = e.Graphics
The variable name Canvas reinforces the idea that you are interacting with a drawing object (that is, the surface of the control) when you use an object of the Graphics class.
The next two statements define a Pen object and a SolidBrush object:
Dim MyPen As Pen = New Pen(Color.Black) Dim MyBrush As SolidBrush = New SolidBrush(Color.Green)
A Pen object is used to draw lines on a Graphics object. The SolidBrush object is derived from the Brush class and is used to draw solid shapes on the Graphics object. The Pen object draws the gauge, its tick marks, and its text. The SolidBrush object fills in the gauge with the different colors.
Visual Basic .NET provides a much larger selection of predefined color names than found in earlier releases of Visual Basic. When you write code to set the color for the Pen object or the SolidBrush object, Visual Basic .NET presents you with a list of these predefined colors, as shown in Figure 22.7. Visual Basic .NET provides about 150 colors from which to choose.
Figure 22.7. The predefined Visual Basic .NET colors.
Colors values can now be defined differently than they were in earlier versions of Visual Basic. Previously, a function named RGB() allowed you to set the red, green, and blue (RGB) components of a color. Each color had a numeric value that varied between (the absence of the color) and 255 (full intensity of the color). By mixing these color values, you could create different color values.
A fourth color argument, named Alpha , has been added in Visual Basic .NET. The Alpha component determines the opacity of the color and can assume the values through 255 . If the Alpha value is near , the background color bleeds through the RGB components to create a transparency effect. As the Alpha value increases , the RGB colors become more "solid," and the transparency effect diminishes. For example, this statement sets the Alpha value to , the red component to 255 , and the green and blue values to :
MyColor = Color.FromArgb(0, 255, 0, 0)
Because the Alpha value is , the background color dominates and the red color is totally absent. If you raise the Alpha value to 50 , the background color recedes, and the red component increases, yielding a pinkish color. You can achieve some interesting color effects by using the Alpha component, especially when overlapping colors are involved.