在WPF中同时验证多个控件

5

我有一个表单,其中包含两个密码字段——用户输入密码的一个字段和用户必须重新输入确认密码的另一个字段。验证用于确认两个密码是否匹配——如果匹配,则启用按钮以允许用户继续:

<PasswordBox Name="P1Box" src:PasswordBoxAssistant.BindPassword="True">
    <src:PasswordBoxAssistant.BoundPassword>
        <Binding Source="{StaticResource mybinding}" Path="Password.P1" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
            <Binding.ValidationRules>
                <DataErrorValidationRule ValidatesOnTargetUpdated="True"/>
            </Binding.ValidationRules>
        </Binding>
    </src:PasswordBoxAssistant.BoundPassword>
</PasswordBox>

按钮样式:

<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
    <MultiDataTrigger>
        <MultiDataTrigger.Conditions>
            <Condition Binding="{Binding ElementName=P1Box, Path=(Validation.HasError)}" Value="false"/>
        </MultiDataTrigger.Conditions>
        <Setter Property="IsEnabled" Value="true"/>
    </MultiDataTrigger>
</Style.Triggers>

我的问题是用户可以更改第一个框中的密码,但它不会强制第二个密码框重新验证。例如,如果第一个密码输入为“password”,并且用户在第二个框中输入“password”,则验证通过,并启用按钮。如果用户然后将原始密码框更改为“PASSWORD”,则两个框都保持验证 - 原始框没有约束非空密码,第二个框没有强制验证更新。
我的密码框使用附加属性这里概述的方法允许绑定到密码。因此,我找不到一种在代码后台中访问它的方法(因为PasswordBox.Password本身不是依赖属性),就像这个解决方案所示的方式一样。或者,也许它只适用于附加属性 - 下面的代码什么也没做:
P2Box.GetBindingExpression(PasswordBoxAssistant.BoundPassword).UpdateSource();

我有一个自定义类,继承了IDataErrorInfo接口,以允许在两个控件之间进行验证 - 绑定是一个PasswordData对象,密码框设置为PasswordData.P1和PasswordData.P2:

public class PasswordData : IDataErrorInfo
{
    public string P1 { get; set; }
    public string P2 { get; set; }
    public string Error { get { return string.Empty; } }
    public string this[string propertyName]
    {
        get
        {
            string ret;
            if (propertyName == "P1")
            {
            if (P1 == null || P1 == "")
                ret = "Password cannot be null or empty.";
            else
                ret = "";
            }
            else if (propertyName == "P2")
            {
            if (P2 == null || P2 == "")
                ret = "Password cannot be null or empty.";
            else if (P2 != P1)
                ret = "The passwords do not match.";
            else
                ret = "";
            }
            return ret;
        }
    }
}

我尝试在PasswordChanged事件中插入,创建一个新的PasswordData,并重新分配绑定。这解决了验证问题,但密码框中的插入符号总是在最开始的位置,破坏了任何已输入的数据。
我希望有一个仅限于xaml的解决方案,但是代码后台也完全可以接受。如果有影响,我正在使用.Net 4.0。
编辑:好吧,事实证明我在xaml中打错了事件处理程序,解决方案实际上是可行的。
private void PasswordChanged(object sender, RoutedEventArgs e)
{
    binding.Pass.P1 = ((PasswordBox)sender).Password;
    P2Box.GetBindingExpression(PasswordBoxAssistant.BoundPassword).UpdateSource();
}

由于事件在绑定更新之前触发,因此我必须手动更新绑定。

我很想知道是否有一种适当的、只使用XAML的方法,使用验证规则、IDataErrorInfo或其他方式在控件之间建立绑定,而无需挂钩到事件并手动更新。


+1 很好的问题描述。 - JDB
2个回答

3

我将继续接受这个答案 - 看起来这只是WPF的一个限制,验证在控件之间不起作用。谢谢Cy! - Tom
2
这并不是WPF的限制 - 涉及的问题是:你要验证什么?当你验证一个字符串是否表示数值时,验证是狭义的。但如果你正在验证表单上的一个值是否等于表单上的另一个值,那么你不是在验证单个值,而是在验证整个表单。表单的有效性或无效性基于这些文本框中的值。当然,在WPF中,“表单”通常是视图模型。在验证视图模型时,IDataErrorInfo是一个好的选择。 - JDB

2
这可能会有所帮助。我看到很多帖子中使用数据注释在模型类中。当您将模型绑定到WPF上下文时,您可能会注意到在未输入任何数据之前验证会触发。
这可能会有问题,并且迭代绑定表达式可能会很麻烦。因此,我连接了一些扩展来解决这个问题。 这将获取一个可枚举的类型,例如TextBox。DependencyObject可以是您的窗口或网格。要引用窗口,请使用var window = Window.GetWindow(this)。如果您愿意,也可以使用名称为grid的控件。
  public static IEnumerable<T> FindVisualChildren<T>(DependencyObject obj) where T : DependencyObject
    {
        if (obj != null)
        {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(obj, i);
                if (child != null && child is T)
                {
                    yield return (T)child;
                }

                foreach (T childOfChild in FindVisualChildren<T>(child))
                {
                    yield return childOfChild;
                }
            }
        }
    }   

一旦您有了控件列表,我们使用“Revalidate”扩展程序对它们进行迭代,并触发绑定表达式。我们返回一个布尔值,仅告诉我们表单是否存在任何故障。请注意,T是您要验证的类型,例如TextBox,ListBox等。请注意,我在下面显示了一个TextBox的示例。您需要构建出任何其他控件,然后将“obj”强制转换为正确的类型。
        public static Boolean Revalidate<T>(this Window depObj) where T : DependencyObject
    {
        bool isValid = true;
        foreach (T obj in FindVisualChildren<T>(depObj))
        {
            var name = typeof(T).Name.ToLower();
            BindingExpression exp = null;

            switch (name)
            {
                case "textbox":
                    var tb = obj as TextBox;
                    exp = tb.GetBindingExpression(TextBox.TextProperty);
                    exp.UpdateSource();
                    if (Validation.GetHasError(tb))
                        isValid = false;  
                    break;           
            }                             
        }

        return isValid;
    }

...并使用以下方式调用:

 valid = this.window.Revalidate<TextBox>(); // where this.window is a reference to our window.

从那里我们只需要检查是否为真并在失败时返回。

if(!valid){
   return; // you could update a message or something as well obviously.
}

这个答案帮助了我。 我有一个组合框,需要在选择更改时验证文本框,这是我的工作代码:private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e) { BindingExpression exp = IdTextBox.GetBindingExpression(TextBox.TextProperty); exp.UpdateSource(); } - Master Azazel

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