button validation preventing click

T

Tim Mackey

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 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).

so if the button fails validation, the errorprovider pops up next to it, and
i go to click the button but the first click does not fire any code. the
second click works as expected. i've spent hours on this trying to get it
to work, every variation of setting the focus to the button etc. i read most
of the posts here about cancel button validation but this doesn't apply i
think.

i've pasted the code below for the validate and validated and btnOK_Click
events.
i appreciate any help that anyone can provide.
cheers
tim

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");
}
}

private void btn_Validated(object sender, System.EventArgs e)
{
this.errorProvider1.SetError(sender as Button, "");
}

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;
}


\\ email: tim at mackey dot ie //
\\ blog: http://tim.mackey.ie //
67d0ebfec70e8db3
 
B

Bruce Wood

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.
 
T

Tim Mackey

Hi Bruce,
thanks a million for your knowledgable reply to my problem. your solution
worked perfectly. i usually try to follow the standards (i.e. not try to
invent my own validation) where possible but i see from your post that .Net
validation is not ideal in every situation and shouldn't be implemented
blindly. i noticed how annoying it was the way it locked users into a
textbox, and also the one-at-a-time notification of errors.

i ended up having 2 methods: IsFileChoiceValid and IsRequiredTextBoxValid.
They follow the approach you gave of simply setting the errorProvider on the
textbox, outside the standard .net validation events. For regular expression
validation, i went with the built-in validation events, because they'll only
trigger if the user enters in some bad input, and presumably they won't mind
being locked into that field until they fix it.

i've included my code below for reference. all my controls are generated
dynamically so that added an extra element of fun to the code. i was able to
get away with only using one boolean variable, as long as i evaluated the
IsRequiredTextBoxValid() expression before ANDing it with the boolean var so
that the expression gets evaluated regardless of the True or Falseness of
the boolean var. oh the subtleties of compiler optimisation..

many thanks again for your help.
tim

private bool ValidateAllFields()
{
bool valid = true;
foreach(Control c in this.panel1.Controls)
{
if(c is TextBox)
{
TextBox txt = c as TextBox;
if(txt.Tag == "Required") // its a required field
{
if(txt.ReadOnly) // it's a file field textbox
valid = IsFileChoiceValid(txt) && valid; // order of operands is
important because False && (EXPR) will never evaluate EXPR
else // its a regular text field
valid = IsRequiredTextBoxValid(txt) && valid;
}
}
}
return valid;
}


private bool IsRequiredTextBoxValid(TextBox txt)
{
if(txt.Text.Trim().Length == 0)
{
this.errorProvider1.SetError(txt, "This textbox requires a value");
return false;
}
else
{
this.errorProvider1.SetError(txt, "");
return true;
}
}
 
B

Bruce Wood

I get the feeling that Microsoft is still working out the kinks in the
whole Validating / ErrorProvider thing. The classes and methods they
supply now seem to lead to some odd situations in which it's not at
all clear how to give the user a pleasant experience. That's why I'd
like to see a white paper or some samples from them of how to use
these things in reasonably complex scenarios, in order to clear up
misconceptions about how to use them.

For example, I've noticed that ErrorProviders and TabControls (and
TabPages) don't work together very well: you can have an ErrorProvider
flashing on some control that currently isn't visible because it's on
a tab page that isn't showing at the moment, but there's no way to
highlight the tab page to indicate that the user should go there to
find a problem.

Obviously ErrorProviders were intended to be able to signal a number
of errors on a number of fields at once: they're vast overkill for
doing nothing more than indicating that field the user is currently in
has a problem. However, the concept doesn't seem to have been carried
through nicely to work in concert with other Windows Forms controls.

(For my part, when the user clicks the OK button and the form fails to
validate, I pop a message box saying something like, "You haven't
filled the form in properly. Mouse over the little red circles for
further details on what is wrong." This at least tells the user that
they should go hunting for little red icons, and would lead most
people to try clicking on tab pages to see if they could find them.
Hokey, but I didn't see much other choice.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top