views:

126

answers:

2

I've got a button that I need to be disabled when validation errors occur in my window. The items on which these errors can occur are all textboxes.

I've bound my Button's datacontext as such:

DataContext="{Binding ElementName=txtEmail}"

Now with this, I can set the button style to disabled when validation errors occur in the email textbox, but I want to do it also when it occurs in other textboxes in my window?

How can I set this binding to multiple textboxes?

+3  A: 

You can't, at least not directly. You could use a MultiBinding with all of the desired text boxes as inputs, but you will need to provide an IMultiValueConverter to "combine" the various text boxes into one object (such as a list):

<Button>
  <Button.DataContext>
    <MultiBinding Converter="{StaticResource ListMaker}">
      <Binding ElementName="txtEmail" />
      <Binding ElementName="txtFirstName" />
      <Binding ElementName="txtLastName" />
    </MultiBinding>
  </Button.DataContext>
</Button>

And it is then that resulting list object that will be passed to your trigger, so you won't be able to access the Validation.HasError property directly: your DataTrigger will also need to bring in a converter which converts the list object into a boolean indicating whether Validation.HasError is set for anything in the list. At this point you might as well just forget about triggers and bind IsEnabled using a MultiBinding:

<Button>
  <Button.IsEnabled>
    <MultiBinding Converter="{StaticResource AllFalse}">
      <Binding Path="(Validation.HasError)" ElementName="txtEmail" />
      <Binding Path="(Validation.HasError)" ElementName="txtFirstName" />
      <Binding Path="(Validation.HasError)" ElementName="txtLastName" />
    </MultiBinding>
  </Button.DataContext>
</Button>

(Here the AllFalse converter returns true if all inputs are false, and false if any input is true.)

A better approach, however, may be, instead of binding the Button directly to other UI elements, have your data object -- the same object that your text boxes are binding to -- expose an IsValid property (with suitable change notifications), and bind your Button.IsEnabled to that:

<Button IsEnabled="{Binding IsValid}" />

This moves you towards a MVVM-style solution which helps with things like testability (e.g. it's easy to create tests for the IsValid property; it's much harder to create tests for Button.IsEnabled).

itowlson
Thanks a lot for giving me the options! I'd go for your last option, could you elaborate a bit more on how to go about that?
Tony
The exact approach will depend on how you're implementing validation at the moment. Let's assume it's an IDataErrorInfo approach where you know which fields are currently invalid. Then you'd just create a read-only IsValid property that returns true if any fields are currently invalid. And in each of your property setters you'd raise PropertyChanged for the IsValid property, so that WPF would re-query it after each time it pushed a binding to the data object. (Otherwise WPF wouldn't know to refresh the Button.IsEnabled binding.) Does that make sense?
itowlson
Kind of... I am using IDataErrorInfo. But the propertychanged bit I'm confused about? Who raises and who handles it? I though IsValid should raise and other handle? No?
Tony
and another bit, how do you check the fields are invalid? You'd have to keep track of it somehow or is that what propertychanged is supposed to deal with?
Tony
The data object raises PropertyChanged. If you're storing the validity in a backing field, it would make sense to have a private setter for IsValid, and raise PropertyChanged from there. If IsValid is calculated on demand, it won't have a setter, so you'd raise PropertyChanged from the setter of any property that affected the validity.
itowlson
"How do you check the fields are invalid?" I was kind of assuming you were already doing that for IDataErrorInfo purposes -- i.e. you already have an implementation of IDataErrorInfo which checks whether the fields are invalid (in order to generate the appropriate error strings for IDataErrorInfo.Item). So you can hopefully just reuse that.
itowlson
So if the readonly property only returns true for example (when all properties are valid), how will the button get disabled if something is not valid?
Tony
If something is not valid, then IsValid needs to return false. For example, the implementation might be: `public bool IsValid { get { return IsEmailValid() } }` where each of those methods validates a particular field. (Note this is a *computed* property. It's not stored as a separate field.) So the read-only property won't *only* return true; it will return true or false depending on whether all the relevant fields are valid or not. (And the button will get enabled/disabled because you're raising PropertyChanged to tell WPF that the validity has changed.)
itowlson
A: 

For the MVVM approach you could try implementing a command router from ICommand.

<Button Command="{Binding Path=Commands.MyButtonCommand}" Style="{StaticResource MyButtonStyle}" ></Button>

where the Commands property is part of the ViewModel. You then have control over what functionality the command implements as well as whether it is enabled or not. Testing is then a whole lot easier.

Andrew Bienert