【问题标题】:Force validation on bound controls in WPF强制验证 WPF 中的绑定控件
【发布时间】:2010-10-03 18:17:25
【问题描述】:

我有一个 WPF 对话框,上面有几个文本框。 文本框绑定到我的业务对象并附加了 WPF 验证规则。

问题是用户可以完美地单击“确定”按钮并关闭对话框,而无需实际将数据输入文本框。验证规则永远不会触发,因为用户甚至没有尝试将信息输入文本框。

是否可以强制验证检查并确定某些验证规则是否被破坏?

当用户尝试关闭对话框并在任何验证规则被破坏时禁止他这样做时,我将能够做到这一点。

谢谢。

【问题讨论】:

    标签: wpf validation business-objects


    【解决方案1】:

    在 3.5SP1 / 3.0SP2 中,他们还在 ValidationRule 基础中添加了一个新属性,即ValidatesOnTargetUpdated="True"。这将在绑定源对象后立即调用验证,而不是仅在更新目标控件时调用。这可能不是您想要的,但最初看到您需要修复的所有内容也不错。

    像这样工作:

    <TextBox.Text>
        <Binding Path="Amount" StringFormat="C">
            <Binding.ValidationRules>
                <validation:RequiredValidationRule 
                    ErrorMessage="The pledge amount is required." 
                    ValidatesOnTargetUpdated="True"  />
                <validation:IsNumericValidationRule 
                    ErrorMessage="The pledge amount must be numeric." 
                    ValidationStep="ConvertedProposedValue" 
                    ValidatesOnTargetUpdated="True"  />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
    

    【讨论】:

    • 这个属性真的很棒,很简单,完全符合我们的需要。
    • 这可能会导致性能问题。示例 - >=1000 行的网格,其中每行的验证向数据库发出 >=1 的请求。在 DataContext 设置后,每一行都将被验证,它将向数据库发出 >=1000 个请求。此外,如果验证包含某种内存泄漏,它将乘以 >=1000。
    • 应该选择这个作为答案,因为它更适合 MVVM 上下文。
    • 这正是我想要的。谢谢! @JānisGruzis:当然,但这适用于每个代码。如果您的验证包含内存泄漏,那么如果将它乘以 1000,您至少更有可能找到它。所以,是的,虽然您是对的,但您也说明了显而易见的问题,而没有促成这里的个别问题。
    • 工作就像一个魅力!不幸的是,当在 DataGrid 中使用时,它运行在最后一行,即用于插入新行的空行。
    【解决方案2】:

    我们的应用程序中也存在这个问题。验证仅在绑定更新时触发,因此您必须手动更新它们。我们在 Window 的 Loaded 事件中执行此操作:

    public void Window_Loaded(object sender, RoutedEventArgs e)
    {
        // we manually fire the bindings so we get the validation initially
        txtName.GetBindingExpression(TextBox.TextProperty).UpdateSource();
        txtCode.GetBindingExpression(TextBox.TextProperty).UpdateSource();
    }
    

    这将使错误模板(红色轮廓)出现,并设置Validation.HasError属性,我们已触发OK按钮禁用:

    <Button x:Name="btnOK" Content="OK" IsDefault="True" Click="btnOK_Click">
        <Button.Style>
            <Style TargetType="{x:Type Button}">
                <Setter Property="IsEnabled" Value="false" />
                <Style.Triggers>
                    <!-- Require the controls to be valid in order to press OK -->
                    <MultiDataTrigger>
                        <MultiDataTrigger.Conditions>
                            <Condition Binding="{Binding ElementName=txtName, Path=(Validation.HasError)}" Value="false" />
                            <Condition Binding="{Binding ElementName=txtCode, Path=(Validation.HasError)}" Value="false" />
                        </MultiDataTrigger.Conditions>
                        <Setter Property="IsEnabled" Value="true" />
                    </MultiDataTrigger>
                </Style.Triggers>
            </Style>
        </Button.Style>
    </Button>
    

    【讨论】:

    • 这工作并实现了我想要的,但我没有代码隐藏。表单逻辑封装在 ModelView 中。由于 ModelView 不应该引用特定的屏幕元素,如何做到这一点并且仍然没有代码隐藏? XAML 中是否有强制绑定的方法?
    • 如果没有命名元素怎么办?如果它是 ItemsControl 中模板的一部分怎么办?
    • 您好,我是新手,但我确定这是我想要的功能。现在我的问题是我的应用程序正在连接到 Web 服务以实现其任何功能。这意味着我不我的应用程序中没有数据模型。老实说,我正在寻找一种无需数据绑定即可进行验证的方法。有人可以告诉我要遵循的方法吗?谢谢
    • 工作,但我不想直接使用它。相反,可以通过附加属性将其作为“行为”附加到绑定控件。
    • 这违反了标准的 UI 准则。只有当用户单击保存时,空表单才应显示多个验证错误,并且还可以选择在用户选项卡 OUT 字段时一一显示它们。您应该在保存之前而不是在加载之后执行此操作。
    【解决方案3】:

    这是一种不需要调用“UpdateSource()”或“UpdateTarget()”的替代方法:

    var binding = thingToValidate.GetBinding(propertyToValidate);
    foreach (var rule in binding.ValidationRules)
    {
        var value = thingToValidate.GetValue(propertyToValidate);
        var result = rule.Validate(value, CultureInfo.CurrentCulture);
        if (result.IsValid) 
             continue;
        var expr = BindingOperations.GetBindingExpression(thingToValidate, propertyToValidate);
        if (expr == null)  
            continue;
        var validationError = new ValidationError(rule, expr);
        validationError.ErrorContent = result.ErrorContent;
        Validation.MarkInvalid(expr, validationError);
    }
    

    【讨论】:

      【解决方案4】:

      使用 Robert Macnee 提出的上述方法。例如:

      //force initial validation
      foreach (FrameworkElement item in grid1.Children)
      {
          if (item is TextBox)
          {
              TextBox txt = item as TextBox;
              txt.GetBindingExpression(TextBox.TextProperty).UpdateSource();
          }
      }        
      

      但是,请确保在此代码运行之前绑定的控件是可见的!

      【讨论】:

      • 很好奇,我是如何让 cmets 回答我 3.5 年前提出的一个问题的,从那以后我就不再在 Windows 上编程了。不过还是谢谢
      • @ValentinVasilyev 可能是因为有答案的观众想要帮助更多的人(除了提问的人)。您现在可能已经知道了,我仍然回复的原因(再次,几年后)与我提到的原因相同)。
      【解决方案5】:

      以防万一有人碰巧发现这个老问题并正在寻找解决 Monstieur 对 UI 指南的评论的答案,我做了以下事情:

      Xaml

      <TextBox.Text>
          <Binding Path="TextValue" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
              <Binding.ValidationRules>
                  <local:RequiredFieldValidationRule>
                          <local:RequiredFieldValidationRule.IsRequiredField>
                          <local:BoolValue Value="{Binding Data.Required, Source={StaticResource proxy}}" />
                      </local:RequiredFieldValidationRule.IsRequiredField>
                      <local:RequiredFieldValidationRule.ValidationFailed>
                          <local:BoolValue Value="{Binding Data.HasValidationError, Mode=TwoWay, Source={StaticResource proxy}}" />
                      </local:RequiredFieldValidationRule.ValidationFailed>
                  </local:RequiredFieldValidationRule>
              </Binding.ValidationRules>
          </Binding>
      </TextBox.Text>
      

      RequiredFieldValidationRule:

      public class RequiredFieldValidationRule : ValidationRule
      {
          private BoolValue _isRequiredField;
          public BoolValue IsRequiredField
          {
              get { return _isRequiredField; }
              set { _isRequiredField = value; }
          }
          private BoolValue _validationFailed;
          public BoolValue ValidationFailed
          {
              get { return _validationFailed; }
              set { _validationFailed = value; }
          }
      
          public override ValidationResult Validate(object value, CultureInfo cultureInfo)
          {
              ValidationFailed.Value = IsRequiredField.Value && (value == null || value.ToString().Length == 0);
              return new ValidationResult(!ValidationFailed.Value, ValidationFailed.Value ? "This field is mandatory" : null);
          }
      }
      

      在 Xaml 绑定到的类中

      private bool _hasValidationError;
      public bool HasValidationError
      {
          get { return _hasValidationError; }
          set { _hasValidationError = value; NotifyPropertyChanged(nameof(HasValidationError)); }
      }
      
      
      public void InitialisationMethod() // Or could be done in a constructor
      {
          _hasValidationError = Required; // Required is a property indicating whether the field is mandatory or not
      }
      

      然后,如果我的任何对象具有 HasValidationError = true,我将使用绑定属性隐藏我的保存按钮。

      希望这对某人有所帮助。

      【讨论】:

        【解决方案6】:

        在您的数据对象上使用 INotifyPropertychanged

        public class MyObject : INotifyPropertyChanged
        {
            string _MyPropertyToBind = string.Empty;
            public string MyPropertyToBind
            {
                get
                {
                    return _MyPropertyToBind;
                }
                set
                {
                    _MyPropertyToBind = value;
                    NotifyPropertyChanged("MyPropertyToBind");
                }
            }
        
            public void NotifyPropertyChanged(string property)
            {
                if (this.PropertyChanged != null)
                {
                    this.PropertyChanged(this, new PropertyChangedEventArgs(property));
                }
            }
            #region INotifyPropertyChanged Members
        
            public event PropertyChangedEventHandler PropertyChanged;
        
            #endregion
        
        }
        

        您可以将以下代码添加到您的控件中

        <TextBox Text="{Binding MyPropertyToBind, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
        

        文本框订阅 datacontext 对象(在我们的示例中为 MyObjet)的 propertychanged 事件,并假定它在源数据更新时被触发

        它会自动强制刷新控件

        无需调用自己的 UpdateTarget 方法

        【讨论】:

        • 你没有注意这个问题。没有属性被更改。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-05-05
        • 1970-01-01
        • 2011-05-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多