Navigation
 - Home
 - Programming
 - Astronomy
 - Places
 - Essays
 - Friends and Family
 - Music
 - Food
 - Resume

  Other Stuff
 - Message Board
 - Cancun Weather

 

Sites of Interest :

Your Site Here Your Site Here Your Site Here Your Site Here Your Site Here

C#/.NET Modal Form (Dialog) HOWTO

Last Updated: 05/19/03

Let me start by saying, I've looked everywhere, and I can't find a comprehensive source of information on this topic. I have 3 C# books, and not a single one discusses dialogs (modal forms) in any reasonable detail. Great amounts of really important information is either glossed over or omitted altogether.

I've searched online, and maybe I'm just missing, but I can't find anything worth a damn, so I hope this helps keep people from going through all the headaches I went through dealing with Dialogs. Coming from the C++/MFC/Win SDK world, I'll probably use "dialog" and "modal form" interchangably. They both mean the same thing, though.

I've only begun this page and will be adding more as time goes on. Time is short at the moment, but if there's an area you'd like to see covered here that isn't, let me know by sending an e-mail to pdavis68@hotmail.com

Overview

First of all, my samples and explanations are based in C#, since that's the language I use. My guess is that this all translates to VB.NET pretty nicely. While this page is a bit less than comprehensive, I expect to complete it at some point and actually make it a fairly comprehensive reference. If you have any suggestions, please e-mail me at pdavis68@hotmail.com

After much searching, I've found code samples to skin a dialog, do dialogs that are non-rectangular, and all sorts of crazy stuff. What I haven't found is how to just do a plain old dialog that's robust, has good data validation, and so forth. I guess us people writing boring apps can forget about decent support ;-) What follows is simply how to do your "run-of-the-mill" dialog that performs data validation.

Setup

The first thing to do when creating a modal form is to go through the properties for the form and make the following settings:

  • FormBorderStyle=FixedDialog
  • MaximizeBox=false
  • MinimizeBox=false
  • ShowInTaskbar=false

If you are going to use localization (internationalization), do not set localization to true at this point. See the section below on localization.

Controls

Most controls are fairly straight-forward, and I'm not going to go into how to use each control because most of that's covered in a number of other places. What we're concerned with is the dialog itself and the things common to modal dialogs. Generally you'll have an OK and Cancel button, so we will discuss those. First, you'll add the two buttons and make the text "&OK" and "&Cancel" or whatever you want. And give them names such as "okButton" and "cancelButton".

For the OK button you want to set the DialogResult in the properties to "OK" and for the Cancel button, you want to set the DialogResult to "Cancel". This provides the return value from the call to Form.ShowDialog() so that you can determine from the caller, how the user exited the form. Either accepting or canceling the data.

Now, go back to your form's properties and set the AcceptButton to okButton and the CancelButton to cancelButton. The reason we do this is that the AcceptButton property identifies the "Default" button in MFC/SDK terminology. It is the button that gets pressed if the user hits the Enter key. The CancelButton is the button that gets pressed when we hit the Esc key.

Data Validation

There are basically two schools of thought on data validation. The authors of .NET apparently wanted us to validate each control as the user enters data. The other school of thought is that you validate the controls after the OK button has been pressed. I'm generally against the notion of validating the controls as the users enter data. While you can argue that it gives users quicker feedback, and I'll grant that it does, that's the only advantage it really offers.

The disadvantages are that it locks the user into a certain order of entering data. Maybe "locks" is too strong a word, but every field you fail to fill out properly as you're filling out data, becomes marked as such. Second, there are cases where a single field can't be validated until after other data has been provided. In this case, field-by-field validation won't work and is actually misleading to the user, as they may become confused about why no matter what data they enter into a control, it won't validate. Finally, if you do field-by-field validation, your validation code is necessarily broke up into a number of event handlers, one for each field. This is simply messy and difficult to maintain, which is the biggest problem of all, in my mind. Since the .NET team built .NET around field-by-field validation, you're kind of stuck out in the cold with no built-in solution. I did see a transcript between the .NET team and developers where there was talk of a potential "ValidateAll" event in the future, and that would be great. I need a solution NOW!

So, I'm from the school that believes in validating the entire form at once. In addition to the advantages above, it's also how validation has been done for years and it's what users are used to. When it comes to users, you don't want to rock the boat. You want them to do what they're used to and what's comfortable for them. Things start changing, and users get nervous and confused. I don't like my users to be nervous and confused.

So, to perform form level validation there are a few steps we need to take. The first is that we need to create a base class from which all of our forms that perform data validation are derived. Below is what I've called the "ValidatingForm" class. Discussion follows the listing.

using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; namespace SharedForms { public class ValidatingForm : System.Windows.Forms.Form { private bool formValidated; ///

/// Required designer variable. /// private System.ComponentModel.Container components = null; public ValidatingForm() { // // Required for Windows Form Designer support // InitializeComponent(); formValidated = true; } /// /// Clean up any resources being used. /// protected override void Dispose( bool disposing ) { if( disposing ) { if(components != null) { components.Dispose(); } } base.Dispose( disposing ); } #region Windows Form Designer generated code /// /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// private void InitializeComponent() { this.components = new System.ComponentModel.Container(); this.Size = new System.Drawing.Size(300,300); this.Text = "ValidatingForm"; this.Closing += new System.ComponentModel.CancelEventHandler(this.form_Closing); } #endregion protected bool FormValidated { get { return formValidated; } set { formValidated = value; } } private void form_Closing(object seder, System.ComponentModel.CancelEventArgs e) { if (false == formValidated) { e.Cancel = true; formValidated = true; } } } }

You can set the namespace to whatever makes sense for you. I have a "SharedForms" namespace where I have several derivable forms and user components that I reuse. The key here is that we're handling the "Closing" event for the form. In the closing, we check the formValidated member to see if it's false. If it is, then the derived class has told us validaton should fail. We set the e.Cancel to true which will keep the form from closing, and then we reset the formValidated to true.

In the dervied class, we will handle the OK buttons Clicked event and either perform our validation directly in the OK clicked event or in a "ValidateForm" method or something of that sort. If at any point, any data in the form does not meet our validation criteria, we simply notify the users (using the ErrorProvider class and/or message boxes) set this.FormValidated=false. (keep in mind that formValidated is a private member, and FormValidated is a protected property accessible from derived classes)

ErrorProvider

The ErrorProvider is a great little class, and if you come from the C++/MFC world, it performs the error notification aspect that DDX/DDV handled in MFC, though it does it in a different way. Instead of popping up a message box, the ErrorProvider class puts an icon next to each field that failed to validate properly. Placing the cursor over that icon provides text describing the nature of the validation failure (programmer supplied).

The first step to using the ErrorProvider is to simply drop the ErrorProvider component onto your form from the toolbox. When validation for a control fails, simply call myErrorProvider.SetError() (where myErrorProvider is whatever you named the ErrorProvider component). SetError takes two paramters: The control that failed validation and the string describing the cause of the validation failure. For example, an empty name text field might require something like: myErrorProvider.SetError(nameTextBox, "Name cannot be blank").

Localization

Localization is the last thing you want to do to your dialog. When I say last, I mean, after you've written all your code and tested your dialog in the default language, then you can go back and localize. I say this because it's happened to me and I've heard from others, of entire forms getting wiped out because you localize and then make some innocent change. There appears to be a bug in some aspect of the form editor, so make a back-up of your form code before you localize. Or better yet, use source control. If you're already using source control, check in what you have before localizing.

First of all, if you're using the HelpProvider component, your forms won't localize. Well, they will, but the code generated won't compile. This is a bug which is supposed to be addressed but as yet, I'm unaware of it being corrected. In the meantime, I've actually provided a class that derives from HelpProvider that can be used in its place and will compile properly. Simply build the component, add it to your toolbox, and drop it on the form instead of the normal HelpProvider component. It will act just like the HelpProvider component (if it doesn't, that's a bug, and please notify me if you find any).

Okay, so you have your working dialog backed up in source safe or somewhere else, right? No you can go ahead and set Localizable=true. At this point, Language will be set to (Default). From here, you want to select the individual languages you're going to use. Each time you select a language, a duplicate of your dialog layout information is saved to a new resx file, one for each language. Try to stick to the languages you're going to actually use. Don't go and select every language, or you're going to end up with a HUGE resource file.

For each language, go through and translate the text and re-arrange the controls as necessary. Make sure the Language setting in the properties is selected to the correct language. You'll probably have to move some controls around to make room for longer or shorter phrases. In general, in the initial layout of your controls, try to make enough room for the labels to assume for the longest language. For example, I develop primarily in English but translate to Spanish which generally has longer phrases, so I add extra space. Spanish isn't a very compact language.



Copyright 2003, 2004 Pete Davis. Site Designed by http://www.quickness.uni.cc. All Rights Reserved.