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

Visual Basic 2005 Design and Development - Chapter 10 pps

34 228 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

Nội dung

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 ignore the 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 pro- 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. Controls and components provide excellent encapsulation. They can encapsulate complicated behavior and allow developers to interact with them only through properties, methods, and events. 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 and take advantage of its internals, possibly tying themselves to a particular implementation of the control. Controls and components are also extremely common items in Visual Basic development. Even a relative 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. 15_053416 ch10.qxd 1/2/07 6:32 PM Page 263 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 UserControl class, 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 control from scratch. Instead, you can derive a control from the existing one, and then modify its behavior to suit your needs. To derive a control or component from an existing class, you simply use the Imports statement. Then you 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 ListBox control. Visual Basic’s ListBox control pro- vides the ability to draw items with different images, fonts, colors, and other graphical effects, but it doesn’t make the process easy. The example described here lets you easily assign images, colors, and fonts to list items at design time. To build the controls, start by creating a new Windows Controls Library project. Remove the UserControl created by default and add a new class. Name the class StyledListBox and make it inherit from the ListBox class. 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 you remove an entry from the beginning of the item list, you also need to remove corresponding items from the color, font, and image lists. Instead of this approach, I decided to make a new class StyledListBoxItem to store the information about a list box entry. The following code shows how the class stores an entry’s color, text, font, and image values: <Serializable()> _ Public Class StyledListBoxItem ‘ The Color property. Private m_Color As Color Public Property Color() As Color Get Return m_Color End Get Set(ByVal value As Color) m_Color = value End Set End Property ‘ The Text property. 264 Part II: Meta-Development 15_053416 ch10.qxd 1/2/07 6:32 PM Page 264 Private m_Text As String Public Property Text() As String Get Return m_Text End Get Set(ByVal value As String) m_Text = value End Set End Property ‘ The Font property. Private m_Font As Font Public Property Font() As Font Get Return m_Font End Get Set(ByVal value As Font) m_Font = value End Set End Property ‘ The Image property. Private m_Image As Image Public Property Image() As Image Get Return m_Image End Get Set(ByVal value As Image) m_Image = value End Set End Property ‘ Display the object’s text. Public Overrides Function ToString() As String Return Me.Text End Function End Class Note that this class is serializable. Visual Basic needs the class to be serializable so that it can save and restore values set at design time. The following code shows the StyledListBox class: Public Class StyledListBox Inherits 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 StyledListBoxItem For i As Integer = 0 To MyBase.Items.Count - 1 item_list(i) = DirectCast(MyBase.Items(i), StyledListBoxItem) Next i 265 Chapter 10: Custom Controls and Components 15_053416 ch10.qxd 1/2/07 6:32 PM Page 265 Return item_list End Get Set(ByVal value() As StyledListBoxItem) MyBase.Items.Clear() For Each styled_item As StyledListBoxItem In value MyBase.Items.Add(styled_item) Next styled_item End Set End Property ‘ Specify OwnerDraw mode. Public Sub New() MyBase.New() MyBase.DrawMode = Windows.Forms.DrawMode.OwnerDrawVariable End Sub ‘ Set the size for an item. Private Const ITEM_MARGIN As Integer = 2 Private 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 = 0 Dim wid As Integer = 0 ‘ Allow room for the image if present. If the_item.Image IsNot Nothing Then wid = the_item.Image.Width + 2 * ITEM_MARGIN hgt = the_item.Image.Height + 2 * ITEM_MARGIN End If ‘ Measure the text. If (the_item.Text IsNot Nothing) AndAlso (the_item.Text.Length > 0) Then Dim the_font As Font = the_item.Font If the_font Is Nothing Then the_font = Me.Parent.Font Dim 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_hgt wid += CInt(item_size.Width + 2 * ITEM_MARGIN) End If e.ItemWidth = wid e.ItemHeight = hgt End Sub ‘ Draw the item. 266 Part II: Meta-Development 15_053416 ch10.qxd 1/2/07 6:32 PM Page 266 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 Then Dim y As Integer = _ e.Bounds.Top + (e.Bounds.Height - the_item.Image.Height) \ 2 e.Graphics.DrawImage(the_item.Image, x, y) x = the_item.Image.Width + 2 * ITEM_MARGIN End If ‘ Draw the text if present. If (the_item.Text IsNot Nothing) AndAlso (the_item.Text.Length > 0) Then Dim the_font As Font = the_item.Font If the_font Is Nothing Then the_font = Me.Parent.Font Dim 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 If End Sub End Class The control starts by inheriting from the ListBox class. It then defines a new Items property that shadows the one provided by ListBox. The new version is an array of StyledListboxItem objects. Exposing this array lets the Properties window and other design- ers manage the items as StyledListboxItem objects. 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 Items property. 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 Items property so it saves some coding. 267 Chapter 10: Custom Controls and Components 15_053416 ch10.qxd 1/2/07 6:32 PM Page 267 Figure 10-1 shows the Properties window displaying a StyledListBox control’s Items property. The entry is expanded to show the items in the array. Figure 10-1: The Properties window lists the objects in the Items property. The values displayed in the array are those returned by the StyledListBoxItem class’s ToString method. If you don’t provide an overridden version of this function, the Properties window uses the object’s default ToString method, which makes every item display the value “FancyListControls .StyledListBoxItem .” If you click on the ellipsis to the right of the Items property, Visual Studio displays the collection editor shown in Figure 10-2. Click the Add button to make a new StyledListBoxItem. Then use the property grid on the right to set the item’s properties. Visual Studio automatically provides this editor because the Items property is an array of serializable objects. The Items property procedures delegate their responsibilities to the ListBox class’s Items property. The actual StyledListboxItem objects are stored in the ListBox class’s Items property. 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 DrawMode prop- erty to OwnerDrawVariable. This tells the control that the program will draw its items and that they will not all be the same size. When the control needs to display an item, it raises a MeasureItem event. The StyledListBox_ MeasureItem event handler catches this event and sets the size needed to draw the item currently being considered. The event handler retrieves the item of interest and converts it from a generic Object into a StyledListBoxItem. It then calculates the space needed by the item. This program allows room for the item’s image and text, plus a 2-pixel margin around each. If the developer has not assigned a font to the item, the code uses the list box’s parent’s font. 268 Part II: Meta-Development 15_053416 ch10.qxd 1/2/07 6:32 PM Page 268 Figure 10-2: Visual Studio automatically provides this collection editor for the Items property. When the control needs to draw an item, it raises a DrawItem event. The StyledListBox_DrawItem event handler follows steps similar to those taken by StyledListBox_MeasureItem, except that it draws the item rather than just measuring it. The e parameter’s DrawBackground method draws the item’s background in a color that depends on whether the item is currently selected. If the item is selected, the event handler calls the e parameter’s DrawFocusRectangle to draw a focus rectangle around the item. Next, the event handler draws the item. The e parameter’s Bounds property gives the area on which this item should be drawn. The code draws the item’s image and text centered vertically within the bounds. Figure 10-3 shows the StyledListBox control in action. The first Question and second Smiley items are selected, so they display a darker background. The DrawItem event handler provides the appropri- ate background when it calls the DrawBackground method. (You can download this example at www .vb-helper.com/one_on_one.htm .) Figure 10-3: The StyledListBox control displays items with images, different fonts, and different colors. 269 Chapter 10: Custom Controls and Components 15_053416 ch10.qxd 1/2/07 6:32 PM Page 269 Setting Toolbox Bitmaps By default, icons that you make display a little gear in the control toolbox. It’s a cute icon, but it would- n’t be very informative if you have dozens of controls that all display the same icon, particularly if you don’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. Unfortunately, setting a control’s toolbox bitmap is rather tricky. To set the bitmap, you add a Toolbox Bitmap attribute to the control’s class. This attribute has a couple of different constructors. The simplest gives the complete path to the control’s 16_16–pixel bitmap file. The following code shows this type of attribute for the StyledListBox control: <ToolboxBitmap(“C:\StyledListBox\Resources\tbxStyledListBox.bmp”)> _ Public Class StyledListBox This version of the ToolboxBitmap attribute is simple and reliable. Unfortunately, it ties the code to a specific location. If you later move the toolbox bitmap, the statement no longer works. To avoid this problem, you can just leave the control and its files in the location where you initially build them. Another solution is to place all of your toolbox bitmaps in a directory somewhere and never move them. These solutions work, but restrict what you can do with the control’s project. For example, if you email the project to someone else, they must modify the attribute’s constructor so it can find the bitmap on the new computer. Another constructor provided by the ToolboxBitmap attribute takes as parameters the type of a class contained in an assembly, and then the name of a bitmap resource in that assembly. The following code shows this version of the attribute for the StyledListBox control: <ToolboxBitmap(GetType(StyledListBox), “tbxStyledListBox”)> _ Public Class StyledListBox This code tells Visual Basic to look in the assembly containing the StyledListBox class for the resource named tbxStyledListBox. In theory, this is a nice solution that lets the attribute find the bitmap resource even if you move the pro- ject. In practice, getting this to work can be tricky. First, add a 16_16–pixel bitmap resource to the project and draw the image you want to display in the toolbox. Next, select the bitmap file in the Solution Explorer. In the Properties window, set the bitmap’s Build Action property to Embedded Resource. If all goes well, the control will get the correct toolbox bitmap. Unfortunately, if there’s any kind of prob- lem (for example, if Visual Basic cannot find the file or the class), you won’t see an error message and the control gets the default gear icon. If you change the control’s icon and still see the wrong icon in the toolbox, click the control in the tool- 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 270 Part II: Meta-Development 15_053416 ch10.qxd 1/2/07 6:32 PM Page 270 or EXE file. When you select the file and click Open, the dialog adds the controls contained in that file to its list and selects them. Click the control’s entry to preview its toolbox bitmap. If you see the gear bitmap (which you may be getting sick of by this point), click Cancel so you don’t add the control to the toolbox (so you don’t have to remove it again) and try again. Testing Controls Eventually, you may want to compile your control into a DLL or EXE and distribute it to others. They can right-click in the control toolbox, select Choose Items, and select the compiled control library to use the control. 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 Visual Studio 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 code and debug it. Debugging controls can be fairly tricky, particularly when you’re trying to debug drawing code. If you set a breakpoint inside a Paint event handler, the control immediately receives another Paint event 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 Paint event handlers with breakpoints. Use Debug.WriteLine statements 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 you execute the project, the Test Container shown in Figure 10-4 appears. Figure 10-4: The TestContainer lets you test a UserControl. 271 Chapter 10: Custom Controls and Components 15_053416 ch10.qxd 1/2/07 6:32 PM Page 271 While you are running in the Test Container, you can interact with the control just as you could if it were running in an application. You can also set properties in the property grid on the right, and the control immediately 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 a property, 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 from the 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 the Test Container. Building UserControls UserControl is 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 UserControl that contains seven labels (six prompts and a completion aid giving the ZIP code format), five text boxes, and a combo box. UserControl also provides support for the Test Container, so you can use the Test Container to test your control. The Test Container acts pretty much as any other form that contains an instance of the UserControl and provides a property grid to manipulate the UserControl’s properties. Building controls by using a UserControl is easy. If you start a new Windows Control Library project, Visual Studio adds a UserControl to the project by default. You can then simply drop controls onto the UserControl, set their properties, give them event handlers, and do everything else you normally do to controls on a form. The UserControl represents the group of controls that you place on it as a single entity. It does not pro- vide direct access to those controls, so you need to add whatever properties, methods, and events you think appropriate to provide access to the controls. For example, the ContactInfo UserControl shown in Figure 10-4 provides property procedures to get and set the control’s FirstName, LastName, Street, City, State, and Zip values. The UserControl delegates these properties to its constituent controls. The following code shows how it delegates its FirstName property to the txtFirstName control. It handles its other properties similarly. <Category(“ContactInfo”)> _ Public Property FirstName() As String Get Return txtFirstName.Text End Get Set(ByVal value As String) txtFirstName.Text = value End Set End Property 272 Part II: Meta-Development 15_053416 ch10.qxd 1/2/07 6:32 PM Page 272 [...]... Toolbox and drop them on the Component Designer You can also drag controls from the Toolbox onto the Component Designer 282 15_053416 ch10.qxd 1/2/07 6:32 PM Page 283 Chapter 10: Custom Controls and Components Figure 1 0-7 : At design time, components appear in the component tray below a form If you made the component by selecting the Project menu’s Add Component command, a hidden file contains designer-generated... validated, and you’re ready to go Summar y Controls and components provide a nice, clean, easy-to-understand interface for developers to use at design time and run-time They encapsulate features tightly so that developers don’t need to worry about the objects’ internal details 294 15_053416 ch10.qxd 1/2/07 6:32 PM Page 295 Chapter 10: Custom Controls and Components By building your own controls and components,... 15_053416 ch10.qxd 1/2/07 6:32 PM Page 289 Chapter 10: Custom Controls and Components Figure 1 0-1 0: The RegexValidationProvider and Regex HighlightProvider components use regular expressions to validate text The text boxes on the right use the RegexHighlightProvider component Whenever a text box’s text changes, the component determines whether the text matches the control’s regular expression and, if the... at www.vb-helper.com/one_on_one.htm.) 290 15_053416 ch10.qxd 1/2/07 6:32 PM Page 291 Chapter 10: Custom Controls and Components A More Useful Example The ExtraTagProvider component described in the previous section is not spectacularly useful You can use it to give Labels and TextBoxes an ExtraTag property at design time At run-time, the program can use the ExtraTagProvider’s GetExtraTag and SetExtraTag... below the form’s main design area Figure 1 0-7 shows a form containing Timer, ErrorProvider, EventLog, FileSystemWatcher, and PerformanceCounter components These are standard components that come with Visual Basic To create a component, start a new Class Library project and remove the class it initially contains Then select the Project menu’s Add Component command and add a Component Class to the project... service for controls Because it displays a graphical icon and message, it wouldn’t make sense for an ErrorProvider to provide this service for a non -visual component such as a Timer or FileOpenDialog Each ExtenderProvider can decide the kinds of objects that it will support 287 15_053416 ch10.qxd 1/2/07 6:32 PM Page 288 Part II: Meta -Development Figure 1 0-9 : The ErrorProvider component displays error messages... methods to get and set values for Labels and TextBoxes That’s about all this component can do This section explains how the much more useful RegexValidationProvider and RegexHighlightProvider components work To build the RegexValidationProvider, I used the Project menu’s Add Component command In addition to making a new class for the provider, Visual Basic also made a hidden designer-generated module... of a map The program displays a full-scale version of the 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 1 0-6 : The MapViewFinder control on the right lets the user select an area in a larger map to view 276 15_053416 ch10.qxd 1/2/07 6:32 PM Page 277 Chapter 10: Custom Controls and Components Building a view finder... Alternatively, you can make a class and make it inherit from System.ComponentModel.Component If you open the Project Explorer and double-click a component, the Component Designer opens Initially, the designer displays a message that says: To add components to your class, drag them from the Toolbox and use the Properties window to set their properties To Create methods and events for your class, click... regular expression and, if the text doesn’t match the expression, the provider makes the control’s background yellow In Figure 1 0-1 0, the left SSN field displays an error icon because its value isn’t a valid Social Security number In the right-hand column, the phone number and floating-point value with exponent have invalid formats, so they have yellow backgrounds A Simple Example Before discussing these . Test Container shown in Figure 1 0-4 appears. Figure 1 0-4 : The TestContainer lets you test a UserControl. 271 Chapter 10: Custom Controls and Components 15_053416 ch10.qxd 1/2/07 6:32 PM Page 271 While. the_scale 277 Chapter 10: Custom Controls and Components 15_053416 ch10.qxd 1/2/07 6:32 PM Page 277 Dim x0 As Double = (Me.Width - 1 - scaled_map_wid) / 2 Dim y0 As Double = (Me.Height - 1 - scaled_map_hgt). basically like controls that are invisible at run-time. At design time, they appear as labeled icons in the component tray below the form’s main design area. Figure 1 0-7 shows a form contain- ing

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

TỪ KHÓA LIÊN QUAN