Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GroupBox in ContentControl - support for IDataErrorInfo implemented by the content bound to the ContentControl

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>
like image 978
Daniel Hilgarth Avatar asked Oct 24 '11 13:10

Daniel Hilgarth


2 Answers

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;
    }
}
like image 159
Fredrik Hedblad Avatar answered Nov 05 '22 10:11

Fredrik Hedblad


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

like image 20
Rachel Avatar answered Nov 05 '22 08:11

Rachel