支持IDataErrorInfo由内容绑定到ContentControl实现

本文关键字:ContentControl 实现 绑定 IDataErrorInfo 支持 | 更新日期: 2023-09-27 18:12:18

我有一个表示多个选项并实现IDataErrorInfo的ViewModel。这个ViewModel只有在这些选项中至少有一个被选中时才有效。它连着一个ContentControlDataTemplate用于将ViewModel可视化为包含ItemsControlGroupBox。另一个DataTemplate将每个选项可视化为CheckBox

我要做什么,使ContentControlIDataErrorInfo一起工作,并在复选框被选中或未选中时检查有效性?


一些代码:

绑定:

<ContentControl Content="{Binding GeneralInvoiceTypes, ValidatesOnDataErrors=True}"
                Margin="0,0,5,0" />
数据模板:

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

支持IDataErrorInfo由内容绑定到ContentControl实现

我要做什么,使ContentControl与IDataErrorInfo,并在选中或选中复选框时检查有效性未经检查的吗?

rachel 的答案中添加了一点。

这个问题可以更容易地通过异步数据验证来解决,但不幸的是,在WPF 4.5发布之前,这是不可用的。

ContentMainViewModel中的GeneralInvoiceTypes绑定。由于我们不能进行异步数据验证,因此必须为GeneralInvoiceTypes引发PropertyChanged以进行验证。这是可行的,但我会采用Rachel建议的方法并在MultipleOptionsViewModel

中引入另一个名为IsValid的属性

IsValid的绑定可以从Tag(或附加属性)到GeneralInvoiceTypes.IsValid。当IsChecked在任何Options中改变时,我们也必须在MultipleOptionsViewModel中得到通知。这可以通过在CheckBoxes中使用命令绑定来完成,例如。

因此需要对以下行进行一些更改。

我还上传了一个示例项目,在这里实现: https://www.dropbox.com/s/fn8e4n4s68wj3vk/ContentControlValidationTest.zip?dl=0

继承自ContentControl <<p> /em>
<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;
    }
}

包含GeneralInvoiceTypes属性的类是否实现了IDataErrorInfo ?

设置ValidatesOnDataErrors=True将显示包含绑定属性的DataContext的验证错误,因此在本例中它验证的是ParentViewModel.GetValidationError("GeneralInvoiceTypes"),而不是GeneralInvoiceTypes.GetValidationError()

在这种情况下,您可以将IDataErrorInfo添加到ParentViewModel中,并通过返回验证错误来验证GeneralInvoiceTypes属性,像这样:

public string GetValidationError(string propertyName)
{
    if (propertyName == "GeneralInvoiceTypes")
        return GeneralInvoiceTypes.GetValidationError();
    return null;
}

或者你可以在GeneralInvoiceTypes上创建一个IsValid属性,它返回GetValidationError() == null,并基于{Binding IsValid}而不是Validation.HasError的验证触发器

根据给出的答案,我是这样解决的:

  1. 更改MultipleOptionsViewModelDataTemplate,将Options属性与ValidatesOnDataErrors=True绑定:

    <DataTemplate DataType="{x:Type ViewModels:MultipleOptionsViewModel}">
        <GroupBox Header="{Binding Title}">
            <ItemsControl ItemsSource="{Binding Options,
                                        ValidatesOnDataErrors=True}"/>
        </GroupBox>
    </DataTemplate>
    
  2. 将错误样式从ContentControl改为ItemsControl

  3. 确保当一个子选项被选中或未选中时,MultipleOptionsViewModel引发"Options"的PropertyChanged
  4. 确保MultipleOptionsViewModelIDataErrorInfo.Item的实现中响应"Options"列,即在其索引器中。

这个解决方案的好处是,它使用了IDataErrorInfo的默认行为,也就是说,这个ViewModel的消费者不需要处理它任何特殊的。

我知道这个解决方案不是100%等同于我在问题中所问的-错误模板现在显示组框内,而不是在它周围,但这是我可以忍受的。