Thursday, March 10, 2005

GOTCHA: Cancelling a control's validating event in MDI apps

Okay, this is the first time I ever noticed this one and I think it's either a bug or pretty damn retarded. Alright, say you have an MDI application with two child forms call 'em Form1 and Form2, each with a text box on them. You open the forms from your MDI container form like normal (probably from a Main Menu's click event):
Dim frm as New Form1
frm.MdiParent = Me
frm.Show()
Now on Form1, you are handling the TextBox1's validating event and are cancelling the event (probably based on some condition):
Private Sub TextBox1_Validating(ByVal sender As Object, _
ByVal e As System.ComponentModel.CancelEventArgs) Handles TextBox1.Validating

   'If some condition blah blah blah
   e.Cancel = True

End Sub
When you run your application, open Form1 and trigger the event, the focus remains in the TextBox1 as expected... until.....

Now you want to open Form2 while Form1 is still open (a perfectly valid use case for an MDI application). BUT, once Form2 opens, even though the form is activated, no control can receive focus on Form2. It basically renders Form2 useless and in a user's eyes it looks like the form is not responding. WHAT!? Why in the world is the Textbox1's Validating event being raised at all on Form1 once Form2 is active? This has got to be a bug or it is just plain retarded. I fail to see why you would ever want this behavior. This is not a problem if the application is not an MDI application. In that case the event will not be raised if the form is not active.

I can't figure out how to prevent the Validating event from being raised, however, I did find a work-around. You can use this check in the handler to avoid running your handler code:
Private Sub TextBox1_Validating(ByVal sender As Object, _
ByVal e As System.ComponentModel.CancelEventArgs) Handles TextBox1.Validating

  If Me.MdiParent.ActiveMdiChild Is Me Then
   'If some condition blah blah blah
    e.Cancel = True
  End If

End Sub
This is moderately painful because I have to search my app for "_Validating" and add this line to all the handlers. If anyone's got a better idea I'm all ears.

2 comments:

Anonymous said...

Problem:
"...Validating Event and Sticky Forms the Validating event also fires when you close
a form. If you set the Cancel property of the CancelEventArgs object to true inside
this event, it will cancel the close operation as well. "..
(Quoted from: http://www.examcram2.com/articles/article.asp?p=30936&seqNum=6)

The closing operation can be conducted in one of three ways:
Either 1) Clicking a cancel button, 2) Window close button or 3) Escape key.


Solution:
We now show how to stop the validation from occuring in case of canceling the operation. We
refer to each of the possible operations specified above.


1. Cancel Button:
Simply set the CausesValidation of the cancel button to false . Thus, every control that
is assigned to a validating event will not be called.

2. Window close button:
Need to override the wndProc method, check the window msg and if the last equals WN_CLOSE, and define it somewhere (it is not automatically defined) ,
set the CausesValidation of the active control to false.
e.g.
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_CLOSE)
{
ActiveControl.CausesValidation = false;
}
base.wndProc (m);
}

3. Escape key:
Derive a class from System.Windows.Forms.Button where you override the IButtonControl.PerformClick
method.
e.g.

public class CancelButton : Button, IButtonControl
{
void IButtonControl.PerformClick()
{
this.Parent.ActiveControl.CausesValidation = false;
base.PerformClick();

}
}
and use it as your form's cancel button

This about it. Do hope was of help.

Nalini Ramadoss Rajan said...

I am building an application development framework for solutions development in my organisation.
In that I have a "Base" form from which most of the other CRUD forms are derived.
In "Base" form, I have implemented two event handlers - Acivated,Deactivate in which I change the AutoValidate property to enable and disable focus changes appropirately.

It solves the issue with minimal code and in a very elegant way.

See code below for more details.

---Code-----
this.Deactivate += new System.EventHandler(BaseWindowPL_Deactivate);
this.Activated += new System.EventHandler(BaseWindowPL_Activated);

void BaseWindowPL_Activated(object sender, System.EventArgs e)
{
this.AutoValidate = System.Windows.Forms.AutoValidate.EnablePreventFocusChange;
}

void BaseWindowPL_Deactivate(object sender, System.EventArgs e)
{
this.AutoValidate = System.Windows.Forms.AutoValidate.EnableAllowFocusChange;
}
-----End of code----

Contact me @ nalinirrajan@gmail.com for more details/comments/queries.