Implementing a Custom Windows User Control

Implementing a Custom Windows User Control

A useful control on which to build is the user control. The user control can be designed, like a Windows form, which makes it an ideal candidate for designing business controls comprised of several constituent controls. The key is to flatten the properties of the constituent control, making the user control seem like a single control with several properties rather than a composite control.

The control I defined as the example for this section is a custom user control named AddressUserControl , which contains several constituent controls common to U.S. addresses (see Figure 9.8). I defined AddressUserControl as part of a class library and added the control to the Toolbox (see the earlier section Adding a Control to the Toolbox).

Figure 9.8. A custom user control containing typical address information.



An interesting addition to our AddressUserControl would be an address validation Web Service. Such a service would probably require its own separate care and feeding as well as a huge database. A vendor like could provide access to such a database for a small subscription fee or for the opportunity to bring users' attention to the vendor's products and services.

Surfacing Constituent Properties

Figure 9.8 indicates that AddressUserControl contains labels, text boxes, and one combobox. What if we elected to allow a consumer to modify these values at design time? Intuitively you might think we could do so by making the TextBox controls public. However, this intuitive solution is not currently supported. Instead we have to add public properties that return the underlying Text property of these controls. This is referred to as surfacing constituent controls . The basic technique involves adding properties that get and set the values of properties belonging to the aggregate constituent controls. Listing 9.6 demonstrates the technique by listing the code for AddressUserControl .

Listing 9.6 Surfacing Properties of Constituent Controls on a User Control
 1:  Imports System.Text.RegularExpressions 2:  Imports System.Windows.Forms 3: 4:  Public Class AddressUserControl 5:      Inherits System.Windows.Forms.UserControl 6: 7:  [ Windows Form Designer generated code ] 8: 9:    Public Property Address1() As String 10:   Get 11:     Return TextBoxAddress1.Text 12:   End Get 13:   Set(ByVal Value As String) 14:     TextBoxAddress1.Text = Value 15:   End Set 16:   End Property 17: 18:   Public Property Address2() As String 19:   Get 20:     Return TextBoxAddress2.Text 21:   End Get 22:   Set(ByVal Value As String) 23:     TextBoxAddress2.Text = Value 24:   End Set 25:   End Property 26: 27:   Public Property City() As String 28:   Get 29:     Return TextBoxCity.Text 30:   End Get 31:   Set(ByVal Value As String) 32:     TextBoxCity.Text = Value 33:   End Set 34:   End Property 35: 36:   Public Property State() As String 37:   Get 38:     Return ComboBoxState.Text 39:   End Get 40:   Set(ByVal Value As String) 41:     ComboBoxState.Text = Value 42:   End Set 43:   End Property 44: 45:   Public Property ZipCode() As String 46:   Get 47:     Return TextBoxZipCode.Text 48:   End Get 49:   Set(ByVal Value As String) 50:     TextBoxZipCode.Text = Value 51:   End Set 52:   End Property 53: 54:   Private Sub TextBoxZipCode_Validating(ByVal sender As Object, _ 55:     ByVal e As System.ComponentModel.CancelEventArgs) _ 56:     Handles TextBoxZipCode.Validating 57: 58:     If (Not Regex.IsMatch(ZipCode, "^\d{5}(-\d{4})?$")) Then 59:       e.Cancel = Not AcceptInvalidZipCode() 60:     End If 61:   End Sub 62: 63:   Private Function AcceptInvalidZipCode() As Boolean 64:     Const Mask As String = _ 65:       "Zip code {0} is invalid" 66: 67:     Return MessageBox.Show(String.Format(Mask, ZipCode), "Error", _ 68:       MessageBoxButtons.OKCancel, _ 69:         MessageBoxIcon.Error) = DialogResult.OK 70:   End Function 71: 72: End Class 

For each control I defined a public property that promoted the property that will become part of the AddressUserControl 's public interface and will be modifiable in the Properties window at design time and runtime.

Lines 54 through 70 demonstrate how we might incorporate validation into our user control. The event handler compares the value of the ZipCode property to the regular expression "^\d{5}(-\d{4})?$" . If ZipCode does not match the regular expression, a message dialog is displayed, allowing the user to override a failed match pattern. (The message dialog is displayed in the function AcceptInvalidZipCode .)

The regular expression on line 58 can be understood to mean a succession of five digits followed by a hyphen and an optional four more digits. The question mark after the group (-\d{4}) makes the four trailing digits optional.

An excellent example of combining and layering code would be to incorporate our RegexTextBox from earlier in the chapter, replacing the plain- vanilla text box and event handler in the listing. Try this as an exercise.

Binding Data to a Custom User Control

.NET supports many powerful concepts that would be difficult to discover by trial and error. One such concept relates to data binding. We can actually bind the constituent controls in our AddressUserControl to the properties of a valid DataSource property. A valid DataSource property implements the IList interface and includes typed collections, ArrayList objects, and DataSet objects.

In order to demonstrate data binding I defined a new AddressList XML schema and used that schema to generate a strongly typed data set. (Read Chapter 12, Advanced ADO.NET, for more information on XML schemas and typed data sets.) Subsequent to defining the data set, an instance of the strongly typed Addresses table was created and data was added to the data table. With test data available it is easy to see the results of the DataBinding statements. Listing 9.7 provides you with the source code (available for download as part of the UserControlLibrary.sln file).

Listing 9.7 Data Binding Examples with a Custom User Control and a Typed Data Set
 1:  Imports System.Data 2: 3:  Public Class Form1 4:      Inherits System.Windows.Forms.Form 5: 6:  [ Windows Form Designer generated code ] 7:    Private Addresses As AddressList.AddressesDataTable 8: 9:    Private Sub Form1_Load(ByVal sender As System.Object, _ 10:     ByVal e As System.EventArgs) Handles MyBase.Load 11: 12:     Addresses = New AddressList.AddressesDataTable() 13:     Addresses.AddAddressesRow( _ 14:       "11546 Beaumont St", "", "Okemos", "MI", "48864") 15: 16:     Addresses.AddAddressesRow( _ 17:       "1313 Mockingbird Ln.", "Apt 4", "Okemos", _ 18:       "MI", "48864") 19: 20:     AddressUserControl1.DataBindings.Add( _ 21:       New Binding("Address1", Addresses, "AddressLine1")) 22: 23:     AddressUserControl1.DataBindings.Add( _ 24:       New Binding("Address2", Addresses, "AddressLine2")) 25: 26:     AddressUserControl1.DataBindings.Add( _ 27:       New Binding("City", Addresses, "City")) 28: 29:     AddressUserControl1.DataBindings.Add( _ 30:       New Binding("State", Addresses, "State")) 31: 32:     AddressUserControl1.DataBindings.Add( _ 33:       New Binding("ZipCode", Addresses, "ZipCode")) 34: 35:   End Sub 36: 37:   Private Sub Button1_Click(ByVal sender As System.Object, _ 38:     ByVal e As System.EventArgs) Handles Button1.Click 39: 40:     CType(AddressUserControl1.BindingContext(Addresses), _ 41:       CurrencyManager).Position -= 1 42:   End Sub 43: 44:   Private Sub Button2_Click(ByVal sender As System.Object, _ 45:     ByVal e As System.EventArgs) Handles Button2.Click 46:     CType(AddressUserControl1.BindingContext(Addresses), _ 47:       CurrencyManager).Position += 1 48:   End Sub 49: 50: End Class 

Lines 7 demonstrates how we can declare an instance of the nested, strongly typed AddressesDataTable (see Chapter 12). The Form1_Load event creates an instance of the data table, and lines 13 through 18 add rows to the table, providing us with something to look at as we navigate through the data.

For each public property defined in AddressUserControl , a Binding object was created and added to AddressUserControl 's DataBindings collection. For example, line 21 creates a new Binding object inline that binds the AddressUserControl.Address1 property to the typed data table's AddressLine1 field (the first and third arguments, respectively). The middle, or second argument, to the Binding object's constructor is the data source. This process is repeated for every constituent control's publicly promoted property (lines 20 through 33).

To support navigation I added buttons to the test form and implemented Click events for the buttons. In each Click event, AddressUserControl 's BindingContext for the Addresses table is obtained and cast to CurrencyManager . CurrencyManager inherits from BindingBaseManager and can be used to safely set the index position of the data source. The Position property is modified by using the decrement and increment operators in lines 41 and 47, respectively. BindingContext will not permit you to position the internal index to an invalid position.

As you can see from the listing (and by downloading and running the example), it is a relatively straightforward process to support binding typed data and implementing navigation with the support of the framework.

Visual Basic. NET Power Coding
Visual Basic(R) .NET Power Coding
ISBN: 0672324075
EAN: 2147483647
Year: 2005
Pages: 215
Authors: Paul Kimmel © 2008-2017.
If you may any questions please contact us: