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:

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.

Figure 16-3

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:

Such a production-type version contains too much code to discuss in this chapter. For simplicity, the exercise has the following limitations:

This leaves the following general tasks to make the control work, and these tasks are detailed in the step-by-step exercise that follows:

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:

  1. Start a new Windows Control Library project. Name it ListSelector.

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

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

  4. Click the smart tag (the triangular glyph in the upper-right corner) of the TableLayoutPanel. A menu will appear. Select Edit Rows and Columns.

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

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

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

  8. Drag four buttons into the FlowLayoutPanel in the middle. At this point your control should look like Figure 16-6.

    Figure 16-6

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

      

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

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

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

  13. 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.)

Категории