1

我遇到了一个棘手的问题,即 WPF 数据网格和验证错误工具提示在验证消息更改时未更新。这是 .Net 4 代码,所以我不能使用INotifyDataErrorInfo

我有一个绑定到数据网格的ObservableCollection 。集合中的对象类型实现了IDataErrorInfo以便我们可以支持验证并突出显示具有无效值的字段。这在大多数情况下都可以正常工作。但是,在以下情况下,工具提示中显示的消息存在问题:

  1. 字段 A 有两条规则 Rule 1 和 Rule S(共享规则)
  2. 字段 B 有一个规则 Rule S(共享规则)
  3. 规则 S 是共享规则,而不是同时引用字段 A 和字段 B
  4. 如果规则 1 和规则 S 都无效,我们会为每个字段显示以下验证工具提示,这是我们想要的行为:

    Field A < "Rule 1 is invalid. Rule S is invalid"
    Field B < "Rule S is invalid"
    
  5. 如果我们现在编辑字段 B 以使规则 S 有效。我们希望两个工具提示消息都更新如下:

    Field A < "Rule 1 is invalid."
    Field B < (valid - no tooltip)
    

注意,字段 A 的验证状态没有改变(Validation.HasError 没有改变值),只有工具提示绑定的消息。

我们实际看到的是:

Field A < "Rule 1 is invalid. Rule S is invalid"
Field B < (valid - no tooltip)

注意此时类实例上的基础 ValidationError 数据是正确的。

UI 似乎不会更新字段 A 的工具提示文本,除非我们强制它重新查询状态并再次调用 IDataErrorInfo.this[string columnName]。我发现强制这种情况发生的唯一方法是手动引发字段 A 的属性更改事件。但是,我不想这样做,因为字段 A 的值实际上并没有改变,只有边界错误信息。虽然此解决方案有效,但额外和不必要的属性更改事件会影响大量数据的性能。

我可以做些什么来强制为字段 B 调用 IDataErrorInfo.this[string columnName] 而不必引发属性更改事件?

注意这里是我们用来显示验证消息的错误数据模板。

    <!-- ERROR HANDLING Data Template -->
    <Style x:Key="controlBaseStyle"
           TargetType="{x:Type Control}">

        <Setter Property="Validation.ErrorTemplate">
            <Setter.Value>
                <ControlTemplate>
                    <Border BorderBrush="Red" 
                            BorderThickness="2"
                            Visibility="{Binding ElementName=placeholder, Path=AdornedElement.IsVisible, Converter={StaticResource BooleanToVisibilityConverter}}">
                        <AdornedElementPlaceholder x:Name="placeholder"/>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>

        <Setter Property="ToolTipService.ShowOnDisabled" Value="true"/>

        <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="true">
                <Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors).CurrentItem, Converter={StaticResource ErrorContentConverter}}"/>
            </Trigger>


            <!--We don't want to see the validation if the control is disabled.  This doesn't affect it if the control is read only. -->
            <Trigger Property="IsEnabled" Value="False">
                <Setter Property="Validation.ErrorTemplate">
                    <Setter.Value>
                        <ControlTemplate>
                            <AdornedElementPlaceholder x:Name="placeholder"/>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Trigger>
        </Style.Triggers>

    </Style>
4

2 回答 2

0

您只需PropertyChanged在每个 setter 中为这两个属性引发,即:

  • 在和PropertyChangedA二传手中加注AB
  • 在和PropertyChangedB二传手中加注AB

这是我知道的唯一方法。

于 2013-05-13T16:02:01.997 回答
0

I usually include an IsValid property on my objects that will go through the validation rules and return true/false if the object is valid or not.

Typically my code looks like this. Your solution may look different depending on how you implement IDataErrorInfo

public class MyObject : ValidatingObject
{
    public MyObject()
    {
        // Add Properties to Validate here
        this.ValidatedProperties.Add("FieldA");
        this.ValidatedProperties.Add("FieldB");
    }

    // Implement validation rules here
    public override string GetValidationError(string propertyName)
    {
        if (ValidatedProperties.IndexOf(propertyName) < 0)
        {
            return null;
        }

        string s = null;

        switch (propertyName)
        {
            case "FieldA":
            case "FieldB":
                if (FieldA <= FieldB)
                    s = "FieldA must be greater than FieldB";
                break;
        }

        return s;
    }

}

And my ValidatingObject base class which implements IDataErrorInfo usually contains the following:

#region IDataErrorInfo & Validation Members

/// <summary>
/// List of Property Names that should be validated
/// </summary>
protected List<string> ValidatedProperties = new List<string>();

public abstract string GetValidationError(string propertyName);

string IDataErrorInfo.Error { get { return null; } }

string IDataErrorInfo.this[string propertyName]
{
    get { return this.GetValidationError(propertyName); }
}

public bool IsValid
{
    get
    {
        return (GetValidationError() == null);
    }
}

public string GetValidationError()
{
    string error = null;

    if (ValidatedProperties != null)
    {
        foreach (string s in ValidatedProperties)
        {
            error = GetValidationError(s);
            if (error != null)
            {
                return error;
            }
        }
    }

    return error;
}

#endregion // IDataErrorInfo & Validation Members

Then you just trigger the validation by calling IsValid whenever you want, such as

if (SomeObject.IsValid)
    CanSave = true;

It's also not uncommon for me to have a PropertyChange notification that re-evaluates a command's CanSave() when a property changes, such as

if (e.PropertyName == "FieldA" || e.PropertyName == "FieldB")
    ((DelegateCommand)SaveCommand).RaiseCanExecuteChanged();

and

void CanSave()
{
    return SomeObject.IsValid;
}

But with all that said, if the only time you will be re-evaluating if a field is valid or not is when another field changes, then it's probably best to just go with what Daniel said and raise a PropertyChange notification for FieldB when FieldA changes.

于 2013-05-13T16:30:08.633 回答