CausesValidation CausesProblems

After searching through many lists for .NET, I have realized that CausesValidation has some issues, before .NET 2.0.

Note: With the introduction of .NET 2.0, these issues can be avoided by using AutoValidate and changing it to AutoValidate.EnableAllowFocusChange. When the OK button is clicked, call Validate to fire all the validating event handlers for the form; call ValidateChildren if the form has container controls that have controls with validating events.

Issue 1 According to the .NET documentation, if a button has the CausesValidation property set to false then validation events should not fire. This is not correct if a form is opened using Show instead of ShowDialog. In this case, the button's event handler is called first, then when the handler calls the Close method, the validation events fire.

Solution In this case it is possible to fix the error because after the validation events fire, the Closing event fires. The closing event receives the same EventArgs as the validating events, so the solution is to set e.Cancel = false;

	
  private void LoanApplicationDialog_Closing(object sender, System.ComponentModel.CancelEventArgs e)
  {
    e.Cancel = false;
  }
However, this only works in Microsoft .NET Framework 1.1. In version 1.0, you will need to use the alternate solutions listed below.

Issue 2 According to the .NET documentation, if a button has its DialogResult set to Cancel and the form has the CancelButton set to this button, then when the user presses the ESC key, the button's event handler should fire. If there is a control that has a validating event and the control has invalid data, then the validating event handler will be called before the cancel button's event handler is called and will prevent the form from closing. Apparently, the validation event fires twice if the form itself has CausesValidation set to true. However, even with CausesValidation set to false for the form, there is still a mysterious call to the validating event handler that cannot be prevented by setting/unsetting a property.

Solution In this case, it is necessary to override the ProcessDialogKey and intercept the ESC key and call the event handler for the button directly.

  protected override System.Boolean ProcessDialogKey ( System.Windows.Forms.Keys keyData ) 
  {
    if (keyData == Keys.Escape) 
    {
      //call the button click event directly
      this.cancelButton_Click(null, new EventArgs());
      //return true to indicate that the key has been handled
      return true;
    } 
    else 
    {
      return base.ProcessDialogKey(keyData);
    }
  }

Issue 3 The previous solutions should handle most cases. Issue 2 deals with the ESC key, but the same thing happens if the Close button (the X) on the control bar is clicked. However, in this case, the Closing event will be fired, so the first solution will also fix that problem. However, if more control is needed, the following code can be used to intercept the key press of the Close on the control bar. In the section for the SC_CLOSE message, a method could be called, or a boolean flag could be set to indicate that the form is closing. There is a choice to be made concerning the call to the base class WndProc. Do not call it if you plan to handle the event entirely yourself.

	
  public enum WindowMessages
  {
    WM_SYSCOMMAND   = 0x0112,
    SC_CLOSE        = 0xf060
  }

  protected override void WndProc ( ref Message msg )
  {	
    if ( msg.Msg == (int)WindowMessages.WM_SYSCOMMAND )
    {
      switch ( msg.WParam.ToInt32() )
      {
        case (int)WindowMessages.SC_CLOSE:
        {
          //map to a button, or set a flag
          //to identify that the form is closing
        } 
        break;
      }
    }
    // Call base class function
    base.WndProc(ref msg);
  }

Alternate Solution The alternate solution is to fire the validation events under program control instead of using the Windows default event. In this case, turn off all CausesValidation properties and define a new delegate.

Define a Delegate
Define a new delegate that is global because it defines a type. Declare it in the namespace, but outside of any class definitions. A delegate defines a way for classes to cummunicate. One class declares an instance of a delegate, while another registers methods with the declared delegate. The definition of the delegate specifies the signature of all the methods that can be registered with an instance of the delegate.
public delegate void ValidateControl(object sender, System.ComponentModel.CancelEventArgs e);
Declare a Delegate
Declare a delegate in the class and qualify it as an event. The event keyword limits access to the delegate so that methods can only be added and removed using += and -=, assignment is not allowed.
public event ValidateControl validateControl;
Create Handlers
Create the validating handlers by mapping the Validating event, then remove the validating event, but keep the method definition. You can also use Visual Studio to auto-generate the ValidateControl methods.
Instantiate the Delegate with Handlers
Instantiate the delegate with new delagates that are constructed with the name of the validating handlers. The handler must have the signature that was defined by the delegate. A typical class would be instantiated with the assignment operator, but since the delegate was declared as an event, then += must be used to associate an object with the delegate. Think of this as a linked list and that each new object is added to the end of the previous delegate in the list.
validateControl += new ValidateControl(applicantNameTextBox_Validating);  
validateControl += new ValidateControl(applicantLoanAmountTextBox_Validating);
Fire the Delegate
Executing the event handlers is known as firing the delegate. There are two ways to do this.
  1. If you want to be able to stop executing the handlers at the first error, then you will need to loop through each event and test args.Cancel, breaking if it is true. This is the technique that is used by the default CausesValidation process.
    foreach( ValidateControl vc in validateControl.GetInvocationList() ) 
    {
      CancelEventArgs args = new CancelEventArgs();
      vc(sender, args);
      if (e.Cancel) break;
    }
    //test args.Cancel to see if there was success or failure
    
  2. If you want to test them all, then you can fire the delegate directly and it will call each event in turn. Each event will receive the CancelEventArgs that you send to the delegate. The same arguments will be passed in turn to each handler.
    CancelEventArgs args = new CancelEventArgs();
    if (validateControl != null) validateControl(sender, args);
    //test args.Cancel to see if there was success or failure
    


Zip file of a project that exhibits these errors and solutions, except for overriding WndProc.