Of course, controls and components are just special kinds of libraries, so you can gain similar advan-tages by using compiled libraries, but the ability to assign properties at design ti
Trang 1Custom Controls and
Components
Perhaps the most important benefit of object-oriented programming is encapsulation
Encapsulation, or information hiding, protects the internal details of a class from prying eyes It
insulates the rest of the program from changes to the class’s internals
Probably the biggest benefit of all is that encapsulation lets developers working with a class ignorethe details of the class You can use an object without needing to keep track of how the class works
so that you can focus on other things Imagine trying to keep all of the details of a 100,000-line gram in your head at the same time! You need to be able to ignore as much of the program as pos-sible while you focus on the part you are writing
pro-Controls and components provide excellent encapsulation They can encapsulate complicatedbehavior and allow developers to interact with them only through properties, methods, andevents
If the controls are compiled rather than included within the main program, then the encapsulation
is fairly tight It’s not as easy for developers to peek inside a compiled control or component andtake advantage of its internals, possibly tying themselves to a particular implementation of thecontrol
Controls and components are also extremely common items in Visual Basic development Even arelative novice knows how to use a control’s properties, methods, and events
Controls and components provide such tight encapsulation, they work particularly well when your project involves developers at different locations One developer can build a control and other developers can use it The separation of the control’s code and the main program’s code helps keep the developers’ work decoupled, and creates a design-by-contract atmosphere almost all
by itself I’ve used this approach on a couple of projects very successfully.
Trang 2Of course, controls and components are just special kinds of libraries, so you can gain similar
advan-tages by using compiled libraries, but the ability to assign properties at design time in the Properties
window seems to foster the feeling of separation.
This chapter explains how to build custom controls and components It describes three main approaches:deriving controls and components from existing classes, building controls based on the UserControlclass, and building controls and components from scratch
Building Derived Controls
If an existing control does almost what you need, it would be a waste of time for you to build the controlfrom scratch Instead, you can derive a control from the existing one, and then modify its behavior tosuit your needs
To derive a control or component from an existing class, you simply use the Importsstatement Thenyou can override and shadow existing features of the parent class, and add new features of your own.For example, suppose you want to build a better ListBoxcontrol Visual Basic’s ListBoxcontrol pro-vides the ability to draw items with different images, fonts, colors, and other graphical effects, but itdoesn’t make the process easy The example described here lets you easily assign images, colors, andfonts to list items at design time
To build the controls, start by creating a new Windows Controls Library project Remove the
UserControlcreated by default and add a new class Name the class StyledListBoxand make itinherit from the ListBoxclass Now, add code to this class to allow it to store and display list items
You could add new properties to the control to let the user define a series of images, fonts, and colors.Then the control would need to match up the entries in those lists with the control’s items to draw them.Unfortunately, this means you must match up several different lists of properties That’s rather cumber-some, and makes it hard for the developer to keep the properties synchronized For example, if youremove an entry from the beginning of the item list, you also need to remove corresponding items fromthe color, font, and image lists
Instead of this approach, I decided to make a new class StyledListBoxItemto store the informationabout a list box entry The following code shows how the class stores an entry’s color, text, font, andimage values:
<Serializable()> _
Public Class StyledListBoxItem
‘ The Color property
Private m_Color As ColorPublic Property Color() As ColorGet
Return m_ColorEnd Get
Set(ByVal value As Color)m_Color = valueEnd Set
End Property
Trang 3Private m_Text As StringPublic Property Text() As StringGet
Return m_TextEnd Get
Set(ByVal value As String)m_Text = value
End SetEnd Property
‘ The Font property
Private m_Font As FontPublic Property Font() As FontGet
Return m_FontEnd Get
Set(ByVal value As Font)m_Font = valueEnd Set
End Property
‘ The Image property
Private m_Image As ImagePublic Property Image() As ImageGet
Return m_ImageEnd Get
Set(ByVal value As Image)m_Image = valueEnd Set
End Property
‘ Display the object’s text
Public Overrides Function ToString() As StringReturn Me.Text
End FunctionEnd Class
Note that this class is serializable Visual Basic needs the class to be serializable so that it can save andrestore values set at design time
The following code shows the StyledListBoxclass:
Public Class StyledListBoxInherits ListBox
‘ Create a new strongly typed Items property so
‘ the Properties Window can provide an editor for it
‘ Delegate it to the inherited Items property
Public Shadows Property Items() As StyledListBoxItem()Get
Dim item_list(0 To MyBase.Items.Count - 1) As StyledListBoxItemFor i As Integer = 0 To MyBase.Items.Count - 1
item_list(i) = DirectCast(MyBase.Items(i), StyledListBoxItem)Next i
Trang 4Return item_listEnd Get
Set(ByVal value() As StyledListBoxItem)MyBase.Items.Clear()
For Each styled_item As StyledListBoxItem In valueMyBase.Items.Add(styled_item)
Next styled_itemEnd Set
End Property
‘ Specify OwnerDraw mode
Public Sub New()MyBase.New()MyBase.DrawMode = Windows.Forms.DrawMode.OwnerDrawVariableEnd Sub
‘ Set the size for an item
Private Const ITEM_MARGIN As Integer = 2Private Sub StyledListBox_MeasureItem(ByVal sender As Object, _ByVal e As System.Windows.Forms.MeasureItemEventArgs) Handles Me.MeasureItem
If e.Index < 0 Or e.Index >= MyBase.Items.Count Then Exit Sub
‘ Convert the item into a StyledListBoxItem
Dim the_item As StyledListBoxItem = _DirectCast(MyBase.Items(e.Index), StyledListBoxItem)
‘ Debug.WriteLine(“Measure: “ & the_item.Text)
‘ See how much room it will need
Dim hgt As Integer = 0Dim wid As Integer = 0
‘ Allow room for the image if present
If the_item.Image IsNot Nothing Thenwid = the_item.Image.Width + 2 * ITEM_MARGINhgt = the_item.Image.Height + 2 * ITEM_MARGINEnd If
‘ Measure the text
If (the_item.Text IsNot Nothing) AndAlso (the_item.Text.Length > 0) ThenDim the_font As Font = the_item.Font
If the_font Is Nothing Then the_font = Me.Parent.FontDim item_size As SizeF = _
e.Graphics.MeasureString(the_item.Text, the_font)Dim txt_hgt As Integer = CInt(item_size.Height + 2 * ITEM_MARGIN)
If txt_hgt > hgt Then hgt = txt_hgtwid += CInt(item_size.Width + 2 * ITEM_MARGIN)End If
e.ItemWidth = wide.ItemHeight = hgtEnd Sub
‘ Draw the item
Trang 5Private Sub StyledListBox_DrawItem(ByVal sender As Object, _ByVal e As System.Windows.Forms.DrawItemEventArgs) Handles Me.DrawItem
If e.Index < 0 Or e.Index >= MyBase.Items.Count Then Exit Sub
‘ Convert the item into a StyledListBoxItem
Dim the_item As StyledListBoxItem = _DirectCast(MyBase.Items(e.Index), StyledListBoxItem)
‘ Debug.WriteLine(“Draw: “ & the_item.Text)
‘ Clear the item’s drawing area
e.DrawBackground()
If Me.SelectedIndex = e.Index Then e.DrawFocusRectangle()
‘ Draw the image if present
Dim x As Integer = ITEM_MARGIN
If the_item.Image IsNot Nothing ThenDim y As Integer = _
e.Bounds.Top + (e.Bounds.Height - the_item.Image.Height) \ 2e.Graphics.DrawImage(the_item.Image, x, y)
x = the_item.Image.Width + 2 * ITEM_MARGINEnd If
‘ Draw the text if present
If (the_item.Text IsNot Nothing) AndAlso (the_item.Text.Length > 0) ThenDim the_font As Font = the_item.Font
If the_font Is Nothing Then the_font = Me.Parent.FontDim item_size As SizeF = _
e.Graphics.MeasureString(the_item.Text, the_font)Dim y As Integer = _
e.Bounds.Top + CInt((e.Bounds.Height - item_size.Height) / 2)Using the_brush As New SolidBrush(the_item.Color)
e.Graphics.DrawString(the_item.Text, the_font, the_brush, x, y)End Using
End IfEnd SubEnd Class
The control starts by inheriting from the ListBoxclass
It then defines a new Itemsproperty that shadows the one provided by ListBox The new version is anarray of StyledListboxItemobjects Exposing this array lets the Properties window and other design-ers manage the items as StyledListboxItemobjects
Some developers might add a new property to the control instead of shadowing the existing one That could lead to confusion when other developers using the control tried to figure out which property to use The control is intended to display a single list of items, so it should have a single Itemsproperty Shadowing the original property lets the control manage its list more directly while avoiding possible confusion.
This also lets the control delegate to the existing Itemsproperty so it saves some coding.
Trang 6Figure 10-1 shows the Properties window displaying a StyledListBoxcontrol’s Itemsproperty Theentry is expanded to show the items in the array.
Figure 10-1: The Properties window lists the objects in the Itemsproperty
The values displayed in the array are those returned by the StyledListBoxItemclass’s ToStringmethod If you don’t provide an overridden version of this function, the Properties window uses theobject’s default ToStringmethod, which makes every item display the value “FancyListControls.StyledListBoxItem.”
If you click on the ellipsis to the right of the Itemsproperty, Visual Studio displays the collection editorshown in Figure 10-2 Click the Add button to make a new StyledListBoxItem Then use the propertygrid on the right to set the item’s properties Visual Studio automatically provides this editor because theItemsproperty is an array of serializable objects
The Itemsproperty procedures delegate their responsibilities to the ListBoxclass’s Itemsproperty.The actual StyledListboxItemobjects are stored in the ListBoxclass’s Itemsproperty These prop-erty procedures simply move the items in an out of an array of StyledListboxItem
The control’s constructor invokes its parent class’s constructor It then sets the control’s DrawModeerty to OwnerDrawVariable This tells the control that the program will draw its items and that theywill not all be the same size
prop-When the control needs to display an item, it raises a MeasureItemevent The StyledListBox_MeasureItemevent handler catches this event and sets the size needed to draw the item currently beingconsidered
The event handler retrieves the item of interest and converts it from a generic Objectinto a
StyledListBoxItem It then calculates the space needed by the item This program allows room for theitem’s image and text, plus a 2-pixel margin around each If the developer has not assigned a font to theitem, the code uses the list box’s parent’s font
Trang 7Figure 10-2: Visual Studio automatically provides this collection editor for theItemsproperty.
When the control needs to draw an item, it raises a DrawItemevent The StyledListBox_DrawItemevent handler follows steps similar to those taken by StyledListBox_MeasureItem, except that itdraws the item rather than just measuring it
The eparameter’s DrawBackgroundmethod draws the item’s background in a color that depends onwhether the item is currently selected If the item is selected, the event handler calls the eparameter’sDrawFocusRectangleto draw a focus rectangle around the item
Next, the event handler draws the item The eparameter’s Boundsproperty gives the area on which thisitem should be drawn The code draws the item’s image and text centered vertically within the bounds.Figure 10-3 shows the StyledListBoxcontrol in action The first Questionand second Smileyitemsare selected, so they display a darker background The DrawItemevent handler provides the appropri-ate background when it calls the DrawBackgroundmethod (You can download this example at www.vb-helper.com/one_on_one.htm.)
Figure 10-3: The StyledListBoxcontrol displays items with
Trang 8Setting Toolbox Bitmaps
By default, icons that you make display a little gear in the control toolbox It’s a cute icon, but it n’t be very informative if you have dozens of controls that all display the same icon, particularly if youdon’t use the toolbox’s list view so the icons and tooltips are the only tools you have for finding the con-trols you want
would-Unfortunately, setting a control’s toolbox bitmap is rather tricky To set the bitmap, you add a ToolboxBitmapattribute to the control’s class This attribute has a couple of different constructors The simplestgives the complete path to the control’s 16_16–pixel bitmap file The following code shows this type ofattribute for the StyledListBoxcontrol:
on the new computer
Another constructor provided by the ToolboxBitmapattribute takes as parameters the type of a classcontained in an assembly, and then the name of a bitmap resource in that assembly The following codeshows this version of the attribute for the StyledListBoxcontrol:
If all goes well, the control will get the correct toolbox bitmap Unfortunately, if there’s any kind of lem (for example, if Visual Basic cannot find the file or the class), you won’t see an error message and thecontrol gets the default gear icon
prob-If you change the control’s icon and still see the wrong icon in the toolbox, click the control in the box, press the Delete key, and confirm that you want to remove the control from the toolbox Next, right-click the toolbox and select Choose Items Click the Browse button and find the compiled control’s DLL
Trang 9tool-or EXEfile When you select the file and click Open, the dialog adds the controls contained in that file toits list and selects them Click the control’s entry to preview its toolbox bitmap If you see the gearbitmap (which you may be getting sick of by this point), click Cancel so you don’t add the control to thetoolbox (so you don’t have to remove it again) and try again.
Testing Controls
Eventually, you may want to compile your control into a DLLor EXEand distribute it to others They canright-click in the control toolbox, select Choose Items, and select the compiled control library to use thecontrol
While you’re building the control, however, you need a way to test it in the debugger The easiest way to
do that is to load the control’s project, open the File menu, select the Add item, and pick New Project.Add a new Windows Application project to the solution
In the Project Explorer, right-click the new project and select “Set as StartUp Project” so that VisualStudio will run this new project instead of the control project when you execute the solution
Now you can add the control to the new project and test it You can set breakpoints in the control’s codeand debug it
Debugging controls can be fairly tricky, particularly when you’re trying to debug drawing code If you set a breakpoint inside a Paintevent handler, the control immediately receives another Paintevent when you resume execution This is a problem with other kinds of projects, too, but is particularly com- mon when you are building controls.
Avoid interrupting Paintevent handlers with breakpoints Use Debug.WriteLinestatements or write debugging information into a text file instead.
If you build a UserControl(described shortly), you can also test the control in the Test Container If youexecute the project, the Test Container shown in Figure 10-4 appears
Figure 10-4: The TestContainerlets you test a UserControl
Trang 10While you are running in the Test Container, you can interact with the control just as you could if it wererunning in an application You can also set properties in the property grid on the right, and the controlimmediately updates itself The reverse is not true, however If you manipulate the control and its prop-erties change, the property grid on the right does not automatically display the new values If you click aproperty, the grid refreshes its display for that property and shows the new value.
If the control project contains more than one control, you can select the control that you want to test fromthe drop-down list at the top
To debug the control, you can set breakpoints and step through the code as you interact with it in theTest Container
Building UserControls
UserControlis a class that can act as a container for new controls It acts as a container where you can
place other constituent controls to build a self-contained package Figure 10-4 shows the Test Container
displaying a UserControlthat contains seven labels (six prompts and a completion aid giving the ZIPcode format), five text boxes, and a combo box
UserControlalso provides support for the Test Container, so you can use the Test Container to testyour control The Test Container acts pretty much as any other form that contains an instance of theUserControland provides a property grid to manipulate the UserControl’s properties
Building controls by using a UserControlis easy If you start a new Windows Control Library project,Visual Studio adds a UserControlto the project by default You can then simply drop controls onto theUserControl, set their properties, give them event handlers, and do everything else you normally do tocontrols on a form
The UserControlrepresents the group of controls that you place on it as a single entity It does not vide direct access to those controls, so you need to add whatever properties, methods, and events youthink appropriate to provide access to the controls
pro-For example, the ContactInfo UserControlshown in Figure 10-4 provides property procedures to getand set the control’s FirstName, LastName, Street, City, State, and Zipvalues The UserControldelegates these properties to its constituent controls The following code shows how it delegates itsFirstNameproperty to the txtFirstNamecontrol It handles its other properties similarly
<Category(“ContactInfo”)> _
Public Property FirstName() As String
GetReturn txtFirstName.TextEnd Get
Set(ByVal value As String)txtFirstName.Text = valueEnd Set
End Property
Trang 11This UserControlalso provides a public DataChangedevent that it raises when any of the values played by its controls changes The following code shows how the ConstituentDataChangedsubrou-tine catches events for all of the controls and raises the UserControl’s DataChangedevent:
dis-Public Event DataChanged()
‘ If any data changes, raise the DataChanged event
Private Sub ConstituentDataChanged(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles txtFirstName.TextChanged, _txtLastName.TextChanged, txtStreet.TextChanged, txtCity.TextChanged,
RaiseEvent DataChanged()End Sub
Finally, the UserControlprovides a ToStringfunction to return a textual version of all of the control’sproperties:
‘ Return a textual representation of the data
Public Overrides Function ToString() As StringReturn txtFirstName.Text & “ “ & txtLastName.Text & “, “ & txtStreet.Text & “,
“ & txtCity.Text & “ “ & cboState.Text & “ “ & txtZip.TextEnd Function
Figure 10-5 shows the RGBColorSelector UserControlrunning in the Test Container
Figure 10-5: The RGBColorSelector UserControllets the user set a color’s red,green, and blue components
This UserControldelegates its R, G, and Bproperty values to its three horizontal scroll bars The ing code shows how the control works:
follow-Public Class RGBColorSelectorPublic Event ColorChanged()Private Sub hbarColor_Scroll(ByVal sender As System.Object, _
Trang 12ByVal e As System.Windows.Forms.ScrollEventArgs) Handles _hbarRed.Scroll, hbarGreen.Scroll, hbarBlue.Scroll
ShowSample()End Sub
Private Sub hbarColor_ValueChanged(ByVal sender As Object, _ByVal e As System.EventArgs) Handles hbarRed.ValueChanged, _hbarGreen.ValueChanged, hbarBlue.ValueChanged
ShowSample()End Sub
Private Sub ShowSample()picSample.BackColor = Color.FromArgb(255, hbarRed.Value, _hbarGreen.Value, hbarBlue.Value)
RaiseEvent ColorChanged()End Sub
Public Property R() As ByteGet
Return CByte(hbarRed.Value)End Get
Set(ByVal value As Byte)hbarRed.Value = valueEnd Set
End PropertyPublic Property G() As ByteGet
Return CByte(hbarGreen.Value)End Get
Set(ByVal value As Byte)hbarGreen.Value = valueEnd Set
End PropertyPublic Property B() As ByteGet
Return CByte(hbarBlue.Value)End Get
Set(ByVal value As Byte)hbarBlue.Value = valueEnd Set
End PropertyEnd Class
The hbarColor_Scrolland hbarColor_ValueChangedevent handlers catch Scrolland
ValueChangedevents for all three scroll bars Those routines just call subroutine ShowSample
Subroutine ShowSampledisplays the currently selected color in the sample picture box named
picSampleand then raises the control’s ColorChangedevent
The rest of the code shows how the control delegates its R, G, and Bproperties to its scroll bars (You candownload this example at www.vb-helper.com/one_on_one.htm.)
Trang 13Controls such as the RGBColorSelectorprovide a somewhat useful selection mechanism that youmight want to add to many different applications It might make sense to compile that project into a con-trol library and use it in several applications.
The ContactInfocontrol, however, has a much more specific use, so it might not make sense to use it inmany applications Sometimes you might build a similar control with other information such as accountnumbers and customer IDs that is so specific that it would be useless in other applications In that case,
it would make sense to build the UserControlinto the application that needs it, rather than making aseparate control library
To do that, create the main Windows Application project Then open the Project menu and select AddUser Control Now the control is part of the main project and not in its own library
This may seem like an awkward way to provide the controls contained in the ContactInfoUserControl After all, you could simply add those labels and text boxes directly to the program’sforms Using a control lets you provide consistency in different parts of the application, however Thecontrol can also include validation and error-handling code that you won’t need to repeat if you need togather the same information on several forms Finally, the control encapsulates these controls so devel-opers working on a particular form don’t need to deal with any details hidden within the control
Building Controls from Scratch
Derived controls and UserControls make building certain kinds of controls fairly easy
If you want to make a control that does almost what an existing control does, you can derive your trol from the existing control If you need a control composed out of other controls, the UserControlmakes building the control easy
con-I have seen applications where developers built UserControls that were used a gle time For example, imagine a form with several tabs that display different kinds
sin-of information about a customer One might display basic contact information, another might show orders pending, and a third might show an order history You could build a UserControlthat holds all of the controls for each of these tabs, and then place a single instance of the controls on the pages of a TabControl.
It doesn’t seem like there’s much benefit to building these UserControls that are used only once They contain all of the code you would need to add to the form if you had placed the constituent controls directly inside the TabControl’s pages In fact, they contain extra code because you need to provide property procedures to let the program get values in and out of the controls.
However, all of this code is localized to the controls and not in the form that uses them, so that form’s code is much simpler It may be better to write a few extra lines
of code in exchange for simplifying what would otherwise be a much more complex form You may even be able to have separate developers work on the controls and the form that uses them.
Trang 14Both of these methods come with some extra overhead, however When you derive a control from anexisting control, you carry along all of the baggage of the existing control When you build a
UserControl, you not only carry the load of the features provided by the UserControlitself, but youalso bring along everything included by all of the controls that you place on the UserControl If youdon’t need all of these extras, you may be better off building your control from scratch
To make a control from scratch, start a new Windows Control Library project and delete the initialUserControlthat Visual Studio creates Next, use the Project menu’s Add New Item command to add anew Custom Control to the project This makes Visual Studio create a new class that inherits fromSystem.Windows.Forms.Control If you look at the designer-generated code, you can see the class’sInheritsstatement
The following shows the code initially created for you to modify If the control is to display anything ibly, you need to implement the Paintevent handler, so Visual Studio makes one for you
vis-Public Class CustomControl1
Protected Overrides Sub OnPaint( _ByVal pe As System.Windows.Forms.PaintEventArgs)MyBase.OnPaint(pe)
‘Add your custom paint code hereEnd Sub
End Class
For an example, look at Figure 10-6 The MapViewFindercontrol on the upper right lets the user drag
a rectangular “thumb” around on a small image of a map The program displays a full-scale version ofthe area selected by the thumb on the picture box on the left (You can download this example at
www.vb-helper.com/one_on_one.htm.)
Figure 10-6: The MapViewFindercontrol on the right lets the user select anarea in a larger map to view
Trang 15Building a view finder such as this one would involve some fairly confusing code, so it’s probably worthmoving out of the main program and into a control just for the encapsulation value It might also be use-ful in other applications, so making it a control will make the code more easily reusable.
Of course, this is a chapter about writing controls, so it’s a foregone conclusion that this will be built as
a control In real development, however, it’s worth taking a few seconds to decide whether it’s worth the extra hassle of building a special-purpose control.
You could derive this control from the PictureBoxclass Probably the only feature of the PictureBoxclass that this control would use would be the Imageproperty to display the small map image, so thisapproach won’t help you too much
You could also build this as a UserControl It doesn’t require a group of constituent controls, however.You would either end up placing a single PictureBoxon the UserControl, or drawing directly on theUserControl’s surface This approach wouldn’t help you much and would add the extra overheadimposed by the UserControl
The key to this control is to make a graphical transformation to make working in the original map’scoordinate system easier In general, a transformation scales, translates, and rotates points from onecoordinate system to another This control uses a transformation that maps points in the map’s coordi-nate system onto the control’s surface so that the map fills the control without distortion
The control provides two public properties MapImageis a picture of the program’s map at full scale Inthis example, it’s a map of Europe that you can see at reduced size in the view finder in Figure 10-6 Themap’s coordinates are measured in pixels on the original map image
The ThumbLocationproperty is a Rectanglethat gives the location and size of the thumb within themap in the map’s coordinate system
The MakeTransformationsubroutine shown in the following code builds the control’s transformation:
‘ Make a transformation to display the map on our surface
Private Sub MakeTransformation()
‘ Do nothing if we have no map image
If m_MapImage Is Nothing Then Exit Sub
If Me.Width < 1 Then Exit Sub
If Me.Height < 1 Then Exit Sub
‘ Find the scales needed to shrink the map
‘ to fit vertically and horizontally
Dim scale_horz As Double = (Me.Width - 1) / m_MapImage.WidthDim scale_vert As Double = (Me.Height - 1) / m_MapImage.Height
‘ Use the smaller scale
Dim the_scale As Double = Math.Min(scale_horz, scale_vert)
‘ Make the transformation to map
‘ the map image onto our surface
Dim scaled_map_wid As Double = m_MapImage.Width * the_scaleDim scaled_map_hgt As Double = m_MapImage.Height * the_scale
Trang 16Dim x0 As Double = (Me.Width - 1 - scaled_map_wid) / 2Dim y0 As Double = (Me.Height - 1 - scaled_map_hgt) / 2Dim pts() As PointF = { _
New PointF(CSng(x0), CSng(y0)), _New PointF(CSng(x0 + scaled_map_wid), CSng(y0)), _New PointF(CSng(x0), CSng(y0 + scaled_map_hgt)) _}
m_Transformation = _New Matrix(m_MapImage.GetBounds(GraphicsUnit.Pixel), pts)
‘ Save the inverse transformation
m_InverseTransformation = m_Transformation.Clone()m_InverseTransformation.Invert()
‘ Redraw
Me.Invalidate()End Sub
The code exits if the control doesn’t yet have a map image or a size It then determines the factor bywhich it would need to scale the map image to make it fit within the control vertically and horizontally
It picks the smaller of the two values for its scale so that the image fits, but is not stretched or squashed.The code then determines the size of the shrunken map image and calculates where it needs to put themap’s upper-left corner to center the image
Next, the program makes an array of PointFstructures that define the upper-left, upper-right, and left corners of the rectangle where the shrunken map should be drawn The code then uses a constructorthat creates a transformation that maps a source rectangle onto the rectangle defined by those points If ituses this transformation, the program can draw using the map image’s coordinates, and the transforma-tion will automatically convert the coordinates so that they fit properly on the control’s surface
lower-More generally, the points can define a parallelogram that is not necessarily rectangular, but that’s not needed for this control A non-rectangular parallelogram would distort the map.
The code makes a copy of the transformation and inverts it to get a transformation that maps pointsfrom the control’s surface back to the map’s coordinate system The code described shortly uses theinverse transformation to tell where the user clicks and drags on the map
The MakeTransformationsubroutine finishes by invalidating the control so that it generates a Paintevent The following code shows how the control draws itself:
Protected Overrides Sub OnPaint(ByVal pe As System.Windows.Forms.PaintEventArgs)MyBase.OnPaint(pe)
‘Add your custom paint code hereDrawControl(pe.Graphics)
End Sub
‘ Draw the control
Private Sub DrawControl(ByVal gr As Graphics)
‘ Do nothing if we have no image or transformation
Trang 17If (m_MapImage Is Nothing) Thengr.DrawLine(Pens.Blue, 0, 0, Me.Width, Me.Height)gr.DrawLine(Pens.Blue, 0, Me.Height, Me.Width, 0)Exit Sub
‘ Draw the thumb
If (m_ThumbLocation.Width > 0) AndAlso (m_ThumbLocation.Height > 0) ThenUsing foreground_pen As New Pen(Me.ForeColor)
gr.DrawRectangle(foreground_pen, m_ThumbLocation)End Using
End IfEnd Sub
The OnPaintsubroutine calls the Controlbase class’s OnPaintmethod and then calls theDrawControlsubroutine
If the control doesn’t yet have a map image, the DrawControlsubroutine draws an X on the control andexits If the control does have a map image, the code sets the Graphicsobject’s transformation to theone created by subroutine MakeTransformation It then draws the map image and thumb onto thecontrol The code uses the map’s coordinate system, and the transformation translates onto the control’ssurface
The final interesting piece of code lets the user click and drag the thumb The following code shows themouse event handlers that deal with this:
‘ MouseDown See if we are within the thumb
Private Sub MapViewFinder_MouseDown(ByVal sender As Object, _ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseDown
‘ Transform the mouse’s position into map coordinates
Dim pts() As Point = {New Point(e.X, e.Y)}
m_InverseTransformation.TransformPoints(pts)
‘ See if this is within the thumb
If Not m_ThumbLocation.Contains(pts(0)) Then Exit Sub
‘ It’s inside the thumb Start dragging
m_Dragging = Truem_PrevX = pts(0).Xm_PrevY = pts(0).YEnd Sub
‘ Continue dragging
Private Sub MapViewFinder_MouseMove(ByVal sender As Object, _ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseMove