Data Exchange


No matter what kind of form you've got, after you've created it, you need to get data into it and out of it. Although it is possible for a form to update an application's data directly when the user presses OK or Apply, this is generally considered bad practice for anything except the main form of your application. The problem is that changes in one part of the application might adversely affect your code. For this reason, forms should be as stand-alone as possible. This means that forms will have a set of properties that they manage, letting the client of the form populate the initial values of the properties and pulling out the final values as appropriate, just as you saw earlier in the typical usage of ColorDialog.

Because most properties managed by a form are actually properties on the controls that make up the form, you may be tempted to make the control fields in your form public, letting the client of the form do this:

 
 Dim dlg As LoanApplicationDialog = New LoanApplicationDialog()  dlg.applicantNameTextBox.Text = "Joe Borrower" ' DON'T  Dim res As DialogResult = dlg.ShowDialog() 

The problem with this approach is the same problem you'll encounter when making any field public: If LoanApplicationDialog wants to change the way the applicant 's name is displayed, such as from a TextBox control to a Label control, all users of the LoanApplicationDialog class must now be updated. To avoid this problem, the general practice is to expose public custom form properties that get and set the form's child control properties: [1]

[1] Because the dialog's constructor calls InitializeComponent, which creates the dialog's child controls, the client of the dialog is free to get and set properties as soon as the dialog object is created.

 
 Public Property ApplicantName() As String  Get  Return applicantNameTextBox.Text  End Get   Set(ByVal Value As String)  applicantNameTextBox.Text = Value  End Set  End Property 

Properties are a means of exposing data fields to the users of a class without giving those users direct access to the data itself. The data fields of a class (in this case, the dialog) should remain private . This is one of the principles of encapsulation . Properties look exactly like data fields to users of the class, and thus programming against them is simple and natural. However, properties allow you to validate the access to the data fields. When users request access to the data, they are actually executing code in the Get or Set methods of the property. By eliminating either the Get or Set methods , you can make a property write- or read-only, respectively. In addition, the code that gets can make decisions about whom to grant access to, and also whether to accept values during Set operations. Finally, properties result in a simpler usage model for the form clients , because they no longer need to concern themselves with the implementation details best left to the form:

 
 Dim dlg As LoanApplicationDialog = New LoanApplicationDialog() dlg.ApplicantName = "Joe Borrower" Dim res As DialogResult = dlg.ShowDialog() 

Handling OK and Cancel

Before data can be retrieved from the property of a modal form, ShowDialog must first return, and this means that the form must be closed. One way to do that is by calling the Form.Close function inside each button's Click event handler:

 
 Sub okButton_Click(sender As Object, e As EventArgs) _       Handles okButton.Click  Me.Close()  End Sub Sub cancelButton_Click(sender As Object, e As EventArgs) _       Handles cancelButton.Click  Me.Close()  End Sub 

Unfortunately, by default, calling Close will return DialogResult.Cancel from the ShowDialog method. For other buttons , we'd like to be able to return other members of the DialogResult enumeration:

 
 Enum DialogResult   Abort   Cancel ' result of calling Form.Close()   Ignore   No   None ' default   OK   Retry   Yes End Enum 

The Abort, Ignore, No, and Retry values are used mostly by MessageBox.Show, [2] but you should feel free to use them for your own custom forms. The one we want to return from ShowDialog when the OK button is pressed is, of course, OK. Checking the return value from ShowDialog is a shortcut for checking the DialogResult property of the form itself, something you can do instead:

[2] In contrast with Form.Show, MessageBox.Show is modal, not modeless, introducing an inconsistency between the two methods with the same name.

 
 dlg.ShowDialog() Dim res As DialogResult = dlg.DialogResult If res = DialogResult.OK ' user pressed OK 

By default, the DialogResult property of any form is None. To return something other than Cancel from ShowDialog, you set the form's DialogResult property before closing the form:

 
 Sub okButton_Click(sender As Object, e As EventArgs) _       Handles okButton.Click  Me.DialogResult = DialogResult.OK  Me.Close() End Sub Sub cancelButton_Click(sender As Object, e As EventArgs) _       Handles cancelButton.Click   ' Close will set the DialogResult to Cancel, so setting   ' it explicitly is just good manners   Me.DialogResult = DialogResult.Cancel   Me.Close() End Sub 

When you set the form's DialogResult to something other than None, a modal form interprets that to mean that it should close. Calling Close in this case isn't even necessary, reducing our code to the following for modal forms:

 
 Sub okButton_Click(sender As Object, e As EventArgs) _      Handles okButton.Click   Me.DialogResult = DialogResult.OK End Sub Sub cancelButton_Click(sender As Object, e As EventArgs) _       Handles cancelButton.Click   Me.DialogResult = DialogResult.Cancel End Sub 

With this code in place, clicking on the OK or Cancel button dismisses a form such as the one shown in Figure 3.4. This action returns the correct result and, using properties, exposes whatever information the user entered during the lifetime of the form.

Figure 3.4. A Sample Form Used as a Dialog (See Plate 6)

Unfortunately, we don't have quite all the behavior we need from our OK and Cancel buttons. In Figure 3.4 notice that the OK button is not drawn as the default button. The default button is the one invoked when the Enter key is pressed, and it's typically drawn with a thicker border than nondefault buttons. In addition, although you can't see this in a picture, the Cancel button isn't invoked when the ESC key is pressed. Enabling this behavior is a matter of designating in the form itself which buttons should be invoked when Enter and ESC are pressed. You do this by setting the form's AcceptButton and CancelButton properties:

 
 Sub InitializeComponent()   ...  Me.AcceptButton = Me.okButton   Me.CancelButton = Me.cancelButton  ... End Sub Sub okButton_Click(sender As Object, e As EventArgs) _       Handles okButton.Click   Me.DialogResult = DialogResult.OK End Sub Sub cancelButton_Click(sender As Object, e As EventArgs) _       Handles cancelButton.Click   Me.DialogResult = DialogResult.Cancel End Sub 

Notice that we used the Designer to set these two properties. This is handy because the Property Browser shows a drop-down list of all the buttons currently on the form to choose from. As it turns out, after you've set the form's CancelButton property, you don't need to set the DialogResult property in the Cancel button's Click event handler, making the Cancel button's Click event handler itself unnecessary. This works because when you set the form's CancelButton property, the Designer sets the DialogResult property on the Cancel button itself. Because it's normally a button that dismisses a form, the Button class provides a DialogResult property to designate the result that pressing this button will have on the form. By default, the value of this property is DialogResult.None, but the Designer sets to Cancel the DialogResult property of the button designated as the CancelButton on the form.

However, the Designer does not set the form's AcceptButton DialogResult property in the same manner. [3] Luckily, if you set the DialogResult property of the OK button to DialogResult.OK yourself, you can dismiss the form without having the OK or Cancel button Click event handler at all:

[3] There's an open debate in the WinForms community as to which is a bug: that the Designer sets the DialogResult of the CancelButton, or that the Designer doesn't set the DialogResult of the AcceptButton. We think it's a bug that the Designer doesn't do the same thing for both buttons.

 
 Sub InitializeComponent()   ...   Me.okButton.DialogResult = DialogResult.OK   Me.cancelButton.DialogResult = DialogResult.Cancel   ...   Me.AcceptButton = Me.okButton   Me.CancelButton = Me.cancelButton   ... End Sub 'okButton_Click handler not needed 'cancelButton_Click handler not needed 

So even though it's possible to implement the client event handlers for the OK and Cancel buttons, often you can get away with simply setting the form's AcceptButton and CancelButton properties and setting the DialogResult property of the OK button. This technique gives you all the data exchange behavior you'll need in a modal form (except data validation, which we cover later in this chapter).

Modeless Form Data

Modeless forms require a different strategy than modal forms to communicate user-updated data to the form's client. For one thing, setting the DialogResult property of a modeless form doesn't automatically dismiss it as it does for a modal form. For another thing, because Show returns immediately, the client usage model is different. Finally, modeless forms acting as dialogs usually have Apply and Close buttons, so data entered into the form can be used before the modeless form even goes away.

What's needed is a way to notify a client of a modeless form when the Accept button is pressed. Luckily, standard .NET events [4] can be used for this purpose:

[4] For more information about .NET delegates and events, see Appendix B: Delegates and Events.

 
 Public Class PropertiesDialog   Inherits Form   ' Event to fire when Accept is pressed   Public Event Accept As EventHandler   Sub acceptButton_Click(sender As Object, e As EventArgs) _           Handles acceptButton.Click       RaiseEvent Accept(Me, EventArgs.Empty)   End Sub End Class 

In this example, notice that PropertiesDialog exposes a public event called Accept using the standard EventHandler delegate signature. When the Accept button is pressed, the form fires the Accept event to notify interested parties. We use the AddHandler method to register a function to be called when the event is fired , passing in the location of the function via AddressOf:

 
 Private WithEvents dlg As PropertiesDialog Sub showProperties_Click(sender As Object, e As EventArgs) _      Handles showProperties.Click   Dim dlg As PropertiesDialog = New PropertiesDialog()   AddHandler dlg.Accept, AddressOf Properties_Accept   dlg.Show() End Sub Sub dlg_Accept(sender As Object, e As EventArgs) Handles dlg.Accept   Dim dlg As PropertiesDialog = CType(sender, PropertiesDialog)   MessageBox.Show(dlg.SomeProperty) End Sub 

When the dialog is created, notice that it is declared at class scope using the WithEvents keyword. This notifies the compiler, and VS.NET, that we wish to bind to events the class might throw. A method is created that handles the dialog's Accept event. By convention, the dialog passes itself to the event handler when it fires so that the receiver of the event can get back the reference to the dialog using a simple cast operation. The only thing left to do is to make the modeless dialog's Close button call Form.Close, and you've got yourself a modeless dialog. See Appendix B: Delegates and Events for more on this topic.



Windows Forms Programming in Visual Basic .NET
Windows Forms Programming in Visual Basic .NET
ISBN: 0321125193
EAN: 2147483647
Year: 2003
Pages: 139

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net