Professional VB 2005 with .NET 3.0 (Programmer to Programmer)
Our earlier example showed inheriting an existing control, which was the first of the three techniques for creating custom controls. The next step up in complexity and flexibility is to combine more than one existing control to become a new control. This is similar to the process of creating a UserControl in VB6, but it is easier to do in Windows Forms.
The main steps in the process of creating a UserControl are as follows:
-
Start a new Windows Control Library project, and assign names to the project and the class representing the control.
-
The project will contain a design surface that looks a lot like a form. You can drag controls onto this surface just as you would a form. Writing code that works with the controls, such as event routines, is done the same way as with a form, but with a few extra considerations that don’t apply to most forms. In particular, it is important to handle resizing when the UserControl is resized. This can be done by using the Anchor and Dock properties of the constituent controls, or you can create resize logic that repositions and resizes the controls on your UserControl when it is resized on the form containing it. Another option is to use FlowLayoutPanel and/or TableLayoutPanel controls to do automatic layout.
-
Create properties of the UserControl to expose functionality to a form that will use it. This typically means creating a property to load information into and get information out of the control. Sometimes properties to handle cosmetic elements are also necessary.
-
Build the control and use it in a Windows application exactly as you did for the inherited controls discussed earlier.
Tip There is a key difference between this type of development and inheriting a control, as shown in the preceding examples. A UserControl will not by default expose the properties of the controls it contains. It exposes the properties of the UserControl class plus any custom properties that you give it. If you want properties for contained controls to be exposed, you must explicitly create logic to expose them.
Creating a Composite UserControl
To demonstrate the process of creating a composite UserControl, the next exercise builds one that is similar to what is shown in Figure 16-3. The control is named ListSelector.
This type of layout is common in wizards and other user interfaces that require selection from a long list of items. The control has one list box holding a list of items that can be chosen (on the left), and another list box containing the items chosen so far (on the right). Buttons enable items to be moved back and forth.
Loading this control means loading items into the left list box, which we will call SourceListBox. Getting selected items back out involves exposing the items that are selected in the right list box, named TargetListBox.
The buttons in the middle that transfer elements back and forth are called AddButton, AddAllButton, RemoveButton, and ClearButton, from top to bottom, respectively.
There are several ways to handle this kind of interface element in detail. A production-level version would have the following characteristics:
-
Buttons would gray out (disable) when they are not appropriate. For example, btnAdd would not be enabled unless an item were selected in lstSource.
-
Items could be dragged and dropped between the two list boxes.
-
Items could be selected and moved with a double-click.
Such a production-type version contains too much code to discuss in this chapter. For simplicity, the exercise has the following limitations:
-
Buttons do not gray out when they should be unavailable.
-
Drag-and-drop is not supported. (Implementation of drag-and-drop was discussed in Chapter 15, if you are interested in adding it to the example.)
-
No double-clicking is supported.
This leaves the following general tasks to make the control work, and these tasks are detailed in the step-by-step exercise that follows:
-
Create a UserControl and name it ListSelector.
-
Add the list boxes and buttons to the ListSelector design surface, using a TableLayoutPanel and a FlowLayoutPanel to control layout when the control is resized.
-
Add logic to transfer elements back and forth between the list boxes when buttons are pressed. (More than one item may be selected for an operation, so several items may need to be transferred when a button is pressed.)
-
Expose properties to enable the control to be loaded, and for selected items to be fetched by the form that contains the control.
Resizing the Control
As shown in Figure 16-3, there are three main areas of the control - the two Listbox controls and a vertical strip between them that holds the buttons. As the control is resized, these areas need to also be appropriately resized.
If the ListSelector control gets too small, there won’t be enough room for the buttons and the list boxes to display properly, so it needs to have a minimum size. That’s enforced by setting the MinimumSize property for the UserControl in the designer. The MinimumSize property is inherited from the Control class (as discussed in the previous chapter).
The rest of the resizing is handled by using a TableLayoutPanel that contains three columns, one for each of the three areas. That is, the first column of the TableLayoutPanel will hold SourceListBox, the second column will hold the buttons, and the third column will hold TargetListBox. The capabilities of the TableLayoutPanel enable the middle column to be a fixed size, and the left and right columns to share all remaining width.
The middle column could contain a standard Panel in the middle column to hold the buttons, but it’s a bit easier to use a FlowLayoutPanel because it will automatically stack the buttons.
Exposing Properties of Contained Controls
Most of the controls contained in the composite control in this exercise do not need to expose their interfaces to the form that will use the composite control. The buttons, for example, are completely private to the ListSelector - none of their properties or methods need to be exposed.
The easiest way to load up the control is to expose the Items property of the source list box. Similarly, the easiest way to allow access to the selected items is to expose the Items property of the target list box. The Items property exposes the entire collection of items in a list box, and can be used to add, clear, or examine items. No other properties of the list boxes need to be exposed.
The exercise also includes a Clear method that clears both list boxes simultaneously. This allows the control to be easily flushed and reused by a form that consumes it.
Stepping through the Example
Here is the step-by-step procedure to build our composite UserControl:
-
Start a new Windows Control Library project. Name it ListSelector.
-
Right-click on the UserControl1.vb module that is generated for the project and select Rename. Change the name of the module to ListSelector.vb. This automatically changes the name of your class to ListSelector.
-
Go to the design surface for the control. Increase the size of the control to about 300 × 200. Then drag a TableLayoutPanel onto the control, and set the Dock property of the TableLayoutPanel to Fill.
-
Click the smart tag (the triangular glyph in the upper-right corner) of the TableLayoutPanel. A menu will appear. Select Edit Rows and Columns.
-
Highlight Column2 and click the Insert button. The TableLayoutPanel will now have three columns. In the new column just inserted (the new Column2), the width will be set to an absolute size of 20 pixels. Change that width to 100 pixels. The dialog containing your column settings should now look like Figure 16-4.
Figure 16-4 -
Click the Show drop-down menu in the upper-left corner and select Rows. Press the Delete button to delete a row because you need only one row in the control. Click OK. The design surface for the control should now look similar to Figure 16-5.
Figure 16-5 -
Drag a Listbox into the first cell and another one into the third cell. Drag a FlowLayoutPanel into the middle cell. For all three of these, set the Dock property to Fill.
-
Drag four buttons into the FlowLayoutPanel in the middle. At this point your control should look like Figure 16-6.
Figure 16-6 -
Change the names and properties of these controls as shown in the following table:
Open table as spreadsheet Original Name
New Name
Properties to Set for Control
Listbox1
SourceListBox
Listbox2
TargetListBox
Button1
AddButton
Text = "Add >"
Size.Width = 90
Button2
AddAllButton
Text = "Add All >>"
Size.Width = 90
Button3
RemoveButton
Text = "< Remove"
Margin.Top = 20
Size.Width = 90
Button4
ClearButton
Text = "<< Clear"
Size.Width = 90
-
In the Properties window, click the drop-down at the top and select ListSelector so that the properties for the UserControl itself appear in the Properties window. Set the MinimumSize height and width to 200 pixels each.
-
Create the public properties and methods of the composite control. In this case, you need the following members:
Open table as spreadsheet Member
Purpose
Clear method
Clears both list boxes of their items
SourceItems property
Exposes the items collection for the source list box
SelectedItems property
Exposes the items collection for the target list box
The code for these properties and methods is as follows:
<Browsable(False)> _ Public ReadOnly Property SourceItems() As ListBox.ObjectCollection Get Return SourceListBox.Items End Get End Property <Browsable(False)> _ Public ReadOnly Property SelectedItems() As ListBox.ObjectCollection Get Return TargetListBox.Items End Get End Property Public Sub Clear() SourceListBox.Items.Clear() TargetListBox.Items.Clear() End Sub
-
Put logic in the class to transfer items back and forth between the list boxes and clear the target list box when ClearButton is pressed. This logic manipulates the collections of items in the list boxes, and is fairly brief. You need one helper function to check whether an item is already in a list box before adding it (to avoid duplicates). Here are the click events for each of the buttons, with the helper function at the top:
Private Function ItemInListBox(ByVal ListBoxToCheck As ListBox, _ ByVal ItemToCheck As Object) As Boolean Dim bFound As Boolean = False For Each Item As Object In ListBoxToCheck.Items If Item Is ItemToCheck Then bFound = True Exit For End If Next Return bFound End Function Private Sub AddButton_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles AddButton.Click For Each SelectedItem As Object In SourceListBox.SelectedItems If Not ItemInListBox(TargetListBox, SelectedItem) Then TargetListBox.Items.Add(SelectedItem) End If Next End Sub Private Sub AddAllButton_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles AddAllButton.Click For Each SelectedItem As Object In SourceListBox.Items If Not ItemInListBox(TargetListBox, SelectedItem) Then TargetListBox.Items.Add(SelectedItem) End If Next End Sub ' For both the following operations, we have to go through the ' collection in reverse because we are removing items. Private Sub RemoveButton_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles RemoveButton.Click For iIndex As Integer = TargetListBox.SelectedItems.Count - 1 To 0 Step -1 TargetListBox.Items.Remove(TargetListBox.SelectedItems(iIndex)) Next iIndex End Sub Private Sub ClearButton_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles ClearButton.Click For iIndex As Integer = TargetListBox.Items.Count - 1 To 0 Step -1 TargetListBox.Items.Remove(TargetListBox.Items(iIndex)) Next iIndex End Sub
The logic in the Click events for RemoveButton and ClearButton needs a bit of explanation. Because items are being removed from the collection, it is necessary to go through the collection in reverse. Otherwise, the removal of items will confuse the looping enumeration and a runtime error will be generated.
-
Build the control. Then create a Windows Application project to test it in. You can drag the control from the top of the Toolbox, add items in code (via the Add method of the SourceItems collection), resize, and so on. When the project is run, the buttons can be used to transfer items back and forth between the list boxes, and the items in the target list box can be read with the SelectedItems property.
Keep in mind that you can also use the techniques for inherited controls in composite controls, too. You can create custom events, apply attributes to properties, and create ShouldSerialize and Reset methods to make properties work better with the designer. (That wasn’t necessary here because our two properties were ReadOnly.)
Категории