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:

 LoanApplicationDialog dlg = new LoanApplicationDialog();  dlg.applicantNameTextBox.Text = "Joe Borrower"; // DON'T!  DialogResult res = dlg.ShowDialog(); if( res == DialogResult.OK ) { /* user pressed OK */ } 

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 string ApplicantName {  get {  return applicantNameTextBox.Text;  }   set {  applicantNameTextBox.Text = value;  }  } 

The client uses properties in the same way a field is used. However, unlike a field, getting or setting a property executes code that you're free to change without requiring a code change in the form client. Furthermore, properties result in a simpler usage model for the form client, because they no longer need to concern themselves with the implementation details best left to the form:

 LoanApplicationDialog dlg = new LoanApplicationDialog();  dlg.ApplicantName = "Joe Borrower";  DialogResult res = 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:

 void okButton_Click(object sender, EventArgs e) {  this.Close();  } void cancelButton_Click(object sender, EventArgs e) {  this.Close();  } 

Unfortunately, calling Close will return DialogResult.Cancel from the ShowDialog method by default. 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, } 

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();  DialogResult res = dlg.DialogResult;  if( res == DialogResult.OK ) { /* user pressed OK */ } dlg.ShowDialog() 

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:

 void okButton_Click(object sender, EventArgs e) {  this.DialogResult = DialogResult.OK;  this.Close(); } void cancelButton_Click(object sender, EventArgs e) {   // Close will set the DialogResult to Cancel, so setting   // it explicitly is just good manners   this.DialogResult = DialogResult.Cancel;   this.Close(); } 

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:

 void okButton_Click(object sender, EventArgs e) {  this.DialogResult = DialogResult.OK;  } void cancelButton_Click(object sender, EventArgs e) {  this.DialogResult = DialogResult.Cancel;  } 

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:

 void InitializeComponent() {   ...  this.AcceptButton = this.okButton;   this.CancelButton = this.cancelButton;  ... } void okButton_Click(object sender, EventArgs e) {   this.DialogResult = DialogResult.OK; } void cancelButton_Click(object sender, EventArgs e) {   this.DialogResult = DialogResult.Cancel; } 

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. As for me, I think it's a bug that the Designer doesn't do the same thing for both buttons.

 void InitializeComponent() {     ...  this.okButton.DialogResult = DialogResult.OK;  ...  this.cancelButton.DialogResult = DialogResult.Cancel;  ...     this.AcceptButton = this.okButton;     this.CancelButton = this.cancelButton;     ... } // 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 I cover later in this chapter).

Modeless Form Data

Modeless forms require a different strategy from 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.

 class PropertiesDialog : Form {   ...  // Event to fire when Accept is pressed   public event EventHandler Accept;  void acceptButton_Click(object sender, EventArgs e) {  // Fire event when Accept is pressed   if( Accept != null ) Accept(this, EventArgs.Empty);  }   void closeButton_Click(object sender, EventArgs e) {     this.Close();   } } 

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 modeless form fires the Accept event to notify any interested parties that the Accept button has been pressed. The client of the form can subscribe to the event when the form is created:

 // Client creates, connects to, and shows modeless form void showProperties_Click(object sender, EventArgs e) {   PropertiesDialog dlg = new PropertiesDialog();  dlg.Accept += new EventHandler(Properties_Accept);  dlg.Show(); }  // Client handles event from form to access accepted values   void Properties_Accept(object sender, EventArgs e) {  PropertiesDialog dlg = (PropertiesDialog)sender;   MessageBox.Show(dlg.SomeProperty);  }  

When the form is created but before it's shown, notice that the client subscribes to the Accept event. When the Accept button is pressed, the notification shows up in the client's event handler. By convention, the form passes itself when it fires the event so that the receiver of the event can use a simple cast operation to get back the reference to the form. The only thing left to do is to make the modeless form's Close button call the form's Close method, and you've got yourself a modeless form.



Windows Forms Programming in C#
Windows Forms Programming in C#
ISBN: 0321116208
EAN: 2147483647
Year: 2003
Pages: 136
Authors: Chris Sells

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