I have a ViewModel that represents multiple options and implements IDataErrorInfo
. This ViewModel is only valid if at least one of these options is selected. It is bound to a ContentControl
. A DataTemplate
is used to visualize the ViewModel as a GroupBox
containing an ItemsControl
. Another DataTemplate
visualizes each option as a CheckBox
.
What do I have to do, to make the ContentControl
work together with IDataErrorInfo
and check the validity when a check box is checked or unchecked?
Some code:
Binding:
<ContentControl Content="{Binding GeneralInvoiceTypes, ValidatesOnDataErrors=True}"
Margin="0,0,5,0" />
Data templates:
<DataTemplate DataType="{x:Type ViewModels:MultipleOptionsViewModel}">
<GroupBox Header="{Binding Title}">
<ItemsControl ItemsSource="{Binding Options}" />
</GroupBox>
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:OptionViewModel}">
<CheckBox IsChecked="{Binding IsChecked}"
Content="{Binding Name}"
Margin="6,3,3,0" />
</DataTemplate>
Style:
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<Trigger Property="Validation.HasError"
Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}" />
</Trigger>
</Style.Triggers>
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="90*" />
<ColumnDefinition Width="20" />
</Grid.ColumnDefinitions>
<Border BorderBrush="Red"
BorderThickness="1"
CornerRadius="2.75"
Grid.Column="0">
<AdornedElementPlaceholder Grid.Column="0" />
</Border>
<TextBlock Foreground="Red"
Grid.Column="1"
Margin="0"
FontSize="12"
VerticalAlignment="Center"
HorizontalAlignment="Left"
x:Name="txtError">
*
</TextBlock>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
What do I have to do, to make the ContentControl work together with IDataErrorInfo and check the validity when a check box is checked or unchecked?
Adding a bit to Rachels answer.
This problem would be easier to solve with Asynchronous data Validation but unfortunately, that isn't available before WPF 4.5 is released.
Content
is binding to GeneralInvoiceTypes
in MainViewModel
. Since we can't do Asynchronous data Validation, then PropertyChanged
has to be raised for GeneralInvoiceTypes
for the validation to occur. This would work but I would take the approach that Rachel suggested and introduce another property called IsValid
in MultipleOptionsViewModel
The binding to IsValid
can be done from Tag
(or an attached property) to GeneralInvoiceTypes.IsValid
. We would also have to be notified in MultipleOptionsViewModel when IsChecked
is changed in any of the Options
. This can be done by using a command binding in the CheckBoxes
for example.
So some changes along the following lines would be required.
I also uploaded a sample project with this implemented here: https://www.dropbox.com/s/fn8e4n4s68wj3vk/ContentControlValidationTest.zip?dl=0
ContentControl
<ContentControl Content="{Binding Path=GeneralInvoiceTypes}"
Tag="{Binding Path=GeneralInvoiceTypes.IsValid,
ValidatesOnDataErrors=True}" />
OptionViewModel DataTemplate
<DataTemplate DataType="{x:Type ViewModels:OptionViewModel}">
<CheckBox IsChecked="{Binding IsChecked}"
Content="{Binding Name}"
Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContentControl}},
Path=DataContext.IsValidCheckCommand}"
Margin="6,3,3,0" />
</DataTemplate>
MultipleOptionsViewModel
private ICommand m_isValidCheckCommand;
public ICommand IsValidCheckCommand
{
get
{
return m_isValidCheckCommand ??
(m_isValidCheckCommand = new RelayCommand(param => IsValidCheck()));
}
}
private void IsValidCheck()
{
IsValid = CheckIsValid();
}
private bool CheckIsValid()
{
foreach (OptionViewModel option in Options)
{
if (option.IsChecked == true)
{
return true;
}
}
return false;
}
private bool m_isValid;
public bool IsValid
{
get { return m_isValid; }
set
{
m_isValid = value;
OnPropertyChanged("IsValid");
}
}
public string this[string columnName]
{
get
{
if (columnName == "IsValid")
{
if (IsValid == false)
{
return "At least 1 Option must be selected";
}
}
return string.Empty;
}
}
Does the class containing theGeneralInvoiceTypes
property implement IDataErrorInfo
?
Setting ValidatesOnDataErrors=True
will show validation errors for the DataContext that contains the bound property, so in this case it is validing ParentViewModel.GetValidationError("GeneralInvoiceTypes")
, and not GeneralInvoiceTypes.GetValidationError()
In this case, you can either add IDataErrorInfo
to your ParentViewModel
and validate the GeneralInvoiceTypes
property by returning it's Validation Error like this:
public string GetValidationError(string propertyName)
{
if (propertyName == "GeneralInvoiceTypes")
return GeneralInvoiceTypes.GetValidationError();
return null;
}
or you can create an IsValid
property on GeneralInvoiceTypes
which returns GetValidationError() == null
and base your validation Trigger off of {Binding IsValid}
rather then Validation.HasError
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With