I would go about this quite differently. I've added my comments
in-line with your post below; I hope what I say can be of help.
Tim Mackey said:
hi,
i know validation is normally not done on a button itself, but i have a good
reason to do it in my app. this is not the OK or Cancel button, it's a
button to select a file. when the user does this, the filename is inserted
in a read-only text box.
I have no problem with doing validation when a button is pressed. I do
that for the Save button in my application. However, I would never
have an actual Validating or Validated event handler for a button, but
would embed the validation code in the button's Click event. More on
that later.
i can't apply the validation to the textbox itself
because the errorProvider prevents the focus from shifting to the button
(which is the only way to get the filename into the textbox).
Then the button needs to have CausesValidation set to False so that
you can shift focus to it and click it even if the text box is not
validating. However, if I understand you correctly the text box is
filled only programmatically, never by the user, so "validating"
doesn't strike me as the right description of what you're doing with
this text box.
so if the button fails validation, the errorprovider pops up next to it,
I'm going to make some assumptions here; I hope that they're right. I
assume that you're not really "validating" the text box contents in
any complex way. After all, you filled it in, so it can't be
malformed, can it? Probably what you want to ensure is that the text
box has something in it, that the user did, indeed, select a file.
There is, I think, a better way to go about this.
Think about it in user terms: you're not really "validating" the
button. What's to validate? The button can't be "invalid" or "valid",
can it? Are you really "validating" the contents of the text box? The
user didn't enter any data in it, so it can't contain junk. What you
really want to know, before some critical event happens (such as the
data is saved to the database) is that the user did choose a proper
file. This has nothing to do with the button or the text box, except
that the text box happens to be a convenient place to put the file
name.
What you're really after, I submit, is a method like this:
private bool IsFileChoiceValid()
{
if (this.txtFileName.Text.Trim().Length == 0)
{
this.errorProvider1.SetError(this.btnSelectFile, "Please
select a file");
}
else
{
this.errorProvider1.SetError(this.btnSelectFile, "");
}
}
I had the error provider place the error against the button because
that's what the user needs to manipulate, but one could just as easily
put it against the text box if the effect is more pleasing.
Now you can call this wherever you think it's appropriate to signal
the user that something may be wrong. So, in your btnOK_Click()
method, instead of saying
if (this.Validate()) ...
I would say,
if (ValidateAllControls()) ...
where
private bool ValidateAllControls()
{
bool fileNameValid = IsFileChoiceValid();
bool otherThingValid = ... ;
...
return fileNameValid && otherThingValid && ... ;
}
(By the way, you have to use individual boolean variables in
ValidateAllControls() because otherwise McCarthy evaluation will stop
validating controls as soon as the first invalid control is found, and
only one error provider will flash, leading to the annoying effect of
the user having to correct one problem only to be told about another.
The way I've written it above, all of the offending fields will have
icons flashing beside them.)
private void btn_ValidateRequiredField(object sender,
System.ComponentModel.CancelEventArgs e)
{
// check that all required values are present
Button btn = sender as Button;
TextBox textbox = btn.Tag as TextBox; // this contains the filename
if(textbox.Text == "")
{
e.Cancel = true;
this.errorProvider1.SetError(btn, "Please select a file");
}
}
The problem I have with the above is that I have no idea what
canceling a Validating event on a button does. Does it prevent you
from moving focus away from the button? That seems a little harsh on
the poor user. After all, I may want to go on to fill in other parts
of the form and then press the button and choose a file later. Why
should I have to do it right now, just because I happened to move
focus to the button?
If you want to tell the user when they pressed the button but didn't
choose a file that they will have to choose a file eventually, I would
just do this:
private void btn_Click(object sender, System.EventArgs e)
{
... Do whatever the button does ...
IsFileChoiceValid(); // Will flash an error icon but won't
impede user
}
This will show the user that they didn't do the right thing, but won't
force them to choose a file before moving on.
private void btnOK_Click(object sender, System.EventArgs e)
{
// focus each control to force validation
foreach(Control c in this.panel1.Controls)
c.Focus();
if(this.Validate())
{
this.DialogResult = DialogResult.OK;
this.Close();
}
else
this.DialogResult = DialogResult.None;
}
Down here in the btnOK_Click, NOW you can force the user to correct
ALL outstanding problems on the form:
private void btnOK_Click(object sender, System.EventArgs e)
{
if (ValidateAllControls())
{
this.DialogResult = DialogResult.OK;
this.Close();
}
else
{
this.DialogResult = DialogResult.None;
}
}
I prefer this to going through the controls, changing focus to each
one, because it makes my software more flexible: the method that
determines that there's a problem and flashes the error icon isn't
tied to any particular event, so I can do the check whenever it's
convenient, and I can check them all at the end with relative ease.
In the end, I think that all of this comes down to the user
experience: the idea behind Validating and Validated is to stop the
user from leaving a text box or other input control after having
entered invalid data into it. Overuse of these events leads to forms
that "trap" users in fields and won't let them do other things on the
form, which can turn the form into an old-fashioned
question-and-response style interaction, which defeats the purpose of
having forms in the first place. For example, even my forms which use
Validating sparingly have the annoying property that if I click on a
text box and then decide that I really want to enter data in some
other text box first, I can't get OUT of the text box I'm in until I
type some valid stuff into it. How annoying! However, it seems that
this was the way that Validating events were intended to work for text
boxes.
However, extending this kind of behaviour to buttons is going too far,
IMHO. Not allowing me to shift focus away from a button until I press
it and choose a file... all just because I tabbed past it... well,
that's a bit much. I may want to fill in some other text boxes and
make some choices on the form from information I have at my finger
tips before I go surfing around for the file.
Of course, when I press OK I must have finished filling in all of the
information on the form, and THEN it's fair enough to stop me in my
tracks and refuse to go on until I finish filling everything in.
My quick rules (so far) are these:
Text box: Has Validating events and will not allow the user to leave
until there is valid data in the text box.
Combo box: Never validate, because your program should never offer an
invalid choice. If you have no choice but to offer an invalid choice
(such as None), then validate as would a text box.
Buttons: Never validate.
Check boxes: Never validate. If it's not valid to check (or uncheck)
them, then why are they enabled?
Radio buttons: Never validate. If a choice isn't valid then it should
be disabled or invisible.
OK button: Validates every control individually (using a method as
shown above), and then performs inter-field validation (all fields are
syntactically valid in isolation, but do they make sense together?),
and signals any fields with problems. If two fields have a
relationship problem (e.g. A can't contain X if B contains Y) then I
flash error icons beside both of them.