【问题标题】:Validate Multiple Controls Simultaneously in WPF在 WPF 中同时验证多个控件
【发布时间】:2012-07-04 06:12:22
【问题描述】:

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

<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”,则两个框都将保持验证状态 - 原始框是因为对非空密码没有限制,第二个框是因为没有强制更新验证。

我的密码框使用here 列出的附加属性来允许绑定到密码。因此,我无法以this solution. 中表达的方式在代码隐藏中访问它(因为 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();
}

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

我很好奇是否存在使用验证规则、IDataErrorInfo 或其他一些方法在控件之间进行绑定而无需挂钩事件并手动更新的正确的、仅 XAML 的方法。

【问题讨论】:

  • +1 写得很好。

标签: c# wpf validation mvvm passwords


【解决方案1】:

对于更复杂的验证,通常需要将验证推送到 ViewModel,在很多情况下甚至是 Model。 IDataErrorInfo 是一个好的开始。

这是一篇关于该主题的优秀文章的链接:
http://msdn.microsoft.com/en-us/magazine/ff714593.aspx

【讨论】:

  • 我将继续并接受这个作为答案 - 它似乎只是 WPF 的一个限制,即验证在控件之间不起作用。谢谢赛!
  • 这并不是 WPF 的真正限制 - 所涉及的问题是:您要验证什么?当您验证一个字符串是否代表一个数值时,验证的重点是狭窄的。但是,如果您要验证表单上的一个值是否等于表单上的另一个值,那么您不是在验证单个值,而是验证整个表单。根据这些文本框中的值,表单是有效还是无效。当然,在 WPF 中,“表单”通常是视图模型。在验证视图模型时,IDataErrorInfo 是一个不错的选择。
【解决方案2】:

这可能会有所帮助。我看到很多帖子在您的模型类中使用了数据注释。当您将模型绑定到 WPF 中的上下文时,您可能会注意到验证在未输入任何数据之前触发。

这可能会出现问题,并且迭代绑定表达式可能会很痛苦。所以我连接了几个扩展来做到这一点。

这将获得一个 Enumerable 您的类型,如 TextBox。 DependencyObject 可能是您的窗口或网格。要引用您的窗口,请使用 var window = Window.GetWindow(this)。如果您愿意,您还可以按名称使用网格等控件。

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

获得控件列表后,我们使用“重新验证”扩展来迭代它们并触发绑定表达式。我们返回一个布尔值,它只会告诉我们表单是否有任何失败。请注意, 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(); }
猜你喜欢
  • 2023-03-04
  • 2011-01-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-14
相关资源
最近更新 更多