1. Trang chủ
  2. » Công Nghệ Thông Tin

Visual Basic 2005 Design and Development - Chapter 10 pps

34 230 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 34
Dung lượng 739,92 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

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 1

Custom 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 2

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 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 3

Private 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 4

Return 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 5

Private 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 6

Figure 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 7

Figure 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 8

Setting 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 9

tool-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 10

While 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 11

This 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 12

ByVal 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 13

Controls 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 14

Both 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 15

Building 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 16

Dim 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 17

If (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

Ngày đăng: 14/08/2014, 11:20

TỪ KHÓA LIÊN QUAN