ContentControl中的GroupBox - 支持由绑定到ContentControl的内容实现的IDataErrorInfo。

7
我有一个表示多个选项并实现了“IDataErrorInfo”的ViewModel。只有在选择了其中至少一个选项时,此ViewModel才有效。它绑定到一个“ContentControl”。使用“DataTemplate”将ViewModel可视化为包含“ItemsControl”的“GroupBox”。另一个“DataTemplate”将每个选项可视化为“CheckBox”。
我该怎么做才能使“ContentControl”与“IDataErrorInfo”一起工作,并在选中或取消选中复选框时检查其有效性?
一些代码:
绑定:
<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>

1
你能发布你的绑定吗?我假设在你的绑定中设置了 ValidatesOnDataErrors=True 吗? - blindmeis
3个回答

3
我该怎么做,才能使ContentControl与IDataErrorInfo协同工作,并在选中或取消复选框时检查有效性?添加一点到Rachel的答案中。使用异步数据验证会更容易解决这个问题,但不幸的是,在WPF 4.5发布之前还没有这个功能。Content绑定到MainViewModel中的GeneralInvoiceTypes。由于我们无法进行异步数据验证,因此必须为GeneralInvoiceTypes引发PropertyChanged以进行验证。这样做可以实现,但我会采取Rachel建议的方法,在MultipleOptionsViewModel中引入另一个名为IsValid的属性。
绑定到IsValid可以从Tag(或附加属性)绑定到GeneralInvoiceTypes.IsValid。当Options中的任何一个IsChecked更改时,我们还必须在MultipleOptionsViewModel中得到通知。例如,可以在CheckBoxes中使用命令绑定来完成此操作。

因此,需要进行以下类似的更改。

我还上传了一个已实现此功能的示例项目: 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 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>

多选项视图模型

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

感谢您的回答和示例项目。不幸的是,我无法分割赏金,所以它将给Rachel - 她的解释帮助我理解了潜在的问题。 - Daniel Hilgarth

1

包含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来设置验证触发器。


谢谢你的回答。为什么它不起作用的解释对我很有帮助。这就是为什么我授予了赏金。然而,我正在尝试找到一个更清晰的解决方案来解决这个问题,这就是为什么我还没有接受你的答案。 - Daniel Hilgarth
@DanielHilgarth 两种选项(将 IDataErrorInfo 添加到您的 ParentViewModel 或将 IsValid 属性添加到您的 MultipleOptionsViewModel 类中)都是实现此功能的可接受方式。您选择哪个取决于对您的项目更有意义,但两者都可以工作。 - Rachel
感谢您的评论。在我看来,这两个选项都有点漏洞。选项一(IDataErrorInfo):每个使用MultipleOptionsViewModelParentViewModel(可能有多个)都需要实现此特殊处理。选项二(IsValid属性):每个显示MultipleOptionsViewModel的视图都需要使用那个“特殊绑定”与IsValid一起使用,以确保它正常工作。这意味着:每次使用MultipleOptionsViewModel时都会引入失败的机会。请参阅我的答案。这将其封装在两个不同的位置中:VM本身和DataTemplate。 - Daniel Hilgarth
@DanielHilgarth 我认为只有在视图需要验证 MultipleOptionsViewModel 时才需要实施选项1,而选项2则相当标准。我经常在我的模型上使用一个 IsValid 属性,特别是在我希望基于 Model.IsValid 来设置 RelayCommand.CanExecute 的情况下。不过很高兴你找到了适合自己的方法 :) - Rachel
由于“MultipleOptionsViewModel”的唯一目的是确保至少选择一个选项,因此使用它的每个VM都需要验证它。 “IsValid”本身并不是问题,事实上,我的“MultipleOptionsViewModel”已经有了一个,但是每次想要可视化它时都需要记住在绑定中使用它,这是我不喜欢的部分。我希望我的代码库设计成一种使我难以以错误的方式使用我的对象的方式 :-) - Daniel Hilgarth

0
根据所给的答案,我是这样解决的:
  1. MultipleOptionsViewModelDataTemplate更改为使用ValidatesOnDataErrors=True绑定Options属性:

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

  3. 确保当选中或取消选中其中一个子选项时,MultipleOptionsViewModel会为"Options"引发PropertyChanged事件。
  4. 确保在实现IDataErrorInfo.Item(即其索引器)中,MultipleOptionsViewModel会响应列"Options"

这个解决方案的好处是,它使用了IDataErrorInfo的默认行为,即该ViewModel的使用者不需要特殊处理。

我知道,这个解决方案与我在问题中询问的解决方案并不完全等价 - 错误模板现在显示在组框内部而不是周围,但这是我可以接受的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接