【问题标题】:WPF Data Binding exception handlingWPF 数据绑定异常处理
【发布时间】:2022-04-28 12:48:21
【问题描述】:

我有一个绑定到 Integer 属性的文本框。当用户在文本框中输入无法转换为整数的内容(例如名称)时,将引发异常并且原始属性值不会更改。我想捕获异常,以便我可以禁用连接到该属性的命令?一般来说,如果可能的话,我该如何从定义属性的视图模型中做到这一点?

【问题讨论】:

    标签: c# .net wpf data-binding


    【解决方案1】:

    我最近遇到了同样的问题,我使用行为来解决它(但如果你不想要它们就不需要它们,它只是为了在不同的视图中重用我需要的一些代码)。主要思想是在 ViewModel 中定义一些方法,允许视图通知 ViewModel 无法检测到的输入中的错误。

    所以,首先在您的 ViewModel 中定义这些方法。为简单起见,我只会记录错误的数量,但您可以存储有关它们的更多信息(如实际错误):

    private int _errorCount = 0;
    void AddUIValidationError()
    {
       _errorCount++;
    }
    
    void RemoveUIValidationError()
    {
       _errorCount--;
    }
    

    然后,在您的视图中注册System.Windows.Controls.Validation.ErrorEvent,这是一个路由事件,可让您知道组件(之前配置为通知数据错误)何时检测到错误(如异常验证错误):

    public partial class MyView : UserControl // or whatever it is
    {
        public MyView(MyViewModel viewModel)
        {
            // Possibly ensure that viewModel is not null
            InitializeComponent();
            _myViewModel = viewModel;
    
            this.AddHandler(System.Windows.Controls.Validation.ErrorEvent, new RoutedEventHandler(OnValidationRaised));
        }
    
        private MyViewModel _myViewModel;
    
        private void OnValidationRaised(object sender, RoutedEventArgs e)
        {
            var args = (System.Windows.Controls.ValidationErrorEventArgs)e;
    
            if (_myViewModel != null)
            {
    
                // Check if the error was caused by an exception
                if (args.Error.RuleInError is ExceptionValidationRule)
                {
                    // Add or remove the error from the ViewModel
                    if (args.Action == ValidationErrorEventAction.Added)
                        _myViewModel.AddUIValidationError();
                    else if (args.Action == ValidationErrorEventAction.Removed)
                        _myViewModel.RemoveUIValidationError();
                }
            }
        }
    }
    

    在 Command 的 CanExecute 方法中,您将检查 ViewModel 的 _errorCount 字段是否大于 0,在这种情况下,应禁用该命令。

    请注意,您必须将ValidatesOnExceptions=True, NotifyOnValidationError=True 添加到您的绑定中,这样才能正常工作。例如:

    <TextBox Text="{Binding Path=MyProperty, ValidatesOnExceptions=True, NotifyOnValidationError=True}" />
    

    编辑:

    除了 Riley 提到的(这也很好,但要求您将模型中的每个整数属性映射到 ViewModel 中的新字符串属性)之外,另一种方法是使用 ValidationRules。您可以添加在解析和调用属性设置器之前检查的ValidationRules。因此,例如,您可以从 ValidationRule 继承并实现 Validate 方法以确保可以将字符串解析为整数。 示例:

    public class IntegerValidationRule : ValidationRule
    {
        public override ValidationResult Validate(object value, CultureInfo cultureInfo)
        {
            int number;
            if(Int32.TryParse((string)value, out number) == false)
                return new ValidationResult(false, "It is not a valid number");
            return new ValidationResult(true, null);
        }
    }
    

    然后,在您的视图中定义定义 IntegerValidationRule 的命名空间:

    <UserControl 
    ...
        xmlns:rules="clr-namespace:MyApplication.ValidationRules"
    ...>
    

    并在您的绑定中使用该规则:

    <TextBox>
        <TextBox.Text>
           <Binding Path="MyProperty">
               <Binding.ValidationRules>
    
                  <rules:IntegerValidationRule/>
               </Binding.ValidationRules>
           </Binding>
        </TextBox.Text>
    </TextBox>
    

    但无论如何,您需要为每个要验证的非字符串类型创建类,而且我认为绑定语法现在看起来有点长。

    问候

    【讨论】:

    • 谢谢,我喜欢你已经找到了解决问题的办法,但我暂时不接受这个答案,因为我想看看是否有更简单的解决方案。
    • 没问题,我也有兴趣。无论如何,这并不复杂:)。很高兴你喜欢它
    • 我认为转换解决方案是最正确的,所以我接受了这个答案。尽管如此,我将在未来进一步研究这个问题,看看我是否能找到一个更强大的解决方案,而不是仅仅为了实现一个简单的方法而添加另一个类。
    【解决方案2】:

    我在网上找到的“最佳”解决方案在http://www.wpfsharp.com/2012/02/03/how-to-disable-a-button-on-textbox-validationerrors-in-wpf/ 进行了解释

    简而言之,Validation 错误是在 View 中处理的,而不是在 ViewModel 中。您不需要自己的 ValidationRule。只需在 XAML 中为您的按钮添加样式:

    <Button.Style>
                <Style TargetType="{x:Type Button}">
                    <Setter Property="IsEnabled" Value="false" />
                    <Style.Triggers>
                        <MultiDataTrigger>
                            <MultiDataTrigger.Conditions>
                                <Condition Binding="{Binding ElementName=TextBox1, Path=(Validation.HasError)}" Value="false" />
                                <Condition Binding="{Binding ElementName=TextBox2, Path=(Validation.HasError)}" Value="false" />
                            <Setter Property="IsEnabled" Value="true" />
                        </MultiDataTrigger>
                    </Style.Triggers>
                </Style>
            </Button.Style>
    

    希望这对偶然发现这个问题的人有所帮助(我知道多年后它不太可能对 OP 有所帮助)。

    【讨论】:

      【解决方案3】:

      考虑从视图模型而不是视图调用命令。

      private int _myProperty;
      public int MyProperty
      {
          get
          {
              return _myProperty;
          }
          set
          {
              _myProperty = value;
              // See if the value can be parsed to an int.
              int potentialInt;
              if(int.TryParse(_myProperty, out potentialInt))
              {
                  // If it can, execute your command with any needed parameters.
                  yourCommand.Execute(_possibleParameter)
              }
          }
      }
      

      这将允许您处理用户键入无法解析为整数的内容, 并且您只会在用户键入的内容是整数时触发命令。

      (我没有测试此代码,但我认为它可能会有所帮助。)

      【讨论】:

      • 我认为您的意思是 public string MyProperty_myProperty = potentialInt;if 中,而不是命令的东西。不要认为 OP 想要在每组中执行该方法。相反,您应该以某种方式禁用该命令。无论如何,这是个好主意:)
      • 我的回答具有误导性。我已经对其进行了编辑,以更好地反映我的意图。我建议仅在需要时才从视图模型调用命令,而不是使用绑定。很容易忘记绑定到命令与调用 command.Execute() 是一回事
      • @Riley,谢谢。我以前没有仔细看过你的答案。但我想告诉你,我在一篇文章(我不记得了)中读到的是,你永远不应该在属性设置器中抛出异常。这可能会导致很多麻烦,尤其是如果该属性是数据绑定的。
      • 我接受了你的回答,因为使用字符串虽然不是最漂亮,但最快且最容易实现。
      • 您是正确的,在属性设置器中抛出异常是一个坏主意。除非触发它们的条件确实异常,否则最好不要生成异常。确保使用 int.TryParse() 而不是 int.Parse()。如果您传入的不是整数, int.TryParse() 方法将不会抛出异常,因此如果您的用户键入无法解析为整数的内容,您不必抛出任何异常。 See here
      【解决方案4】:

      我们甚至可以直接尝试避免用户在文本框中输入任何字符

      <TextBox Name="txtBox" PreviewTextInput="NumberValidationTextBox"/>
      

      在 xaml.cs 中

      private void NumberValidationTextBox(object sender, TextCompositionEventArgs e)
      {
              e.Handled = Regex.IsMatch(e.Text, "[^0-9]+");
      }
      

      我们可以在模型中检查这个属性,在设置它之前检查值以避免任何空值或空值

      private int _number;
      public int Number
      {
           get => _number;
           set
           {
              if(!string.IsNullOrEmpty(value.ToString())
                  _number = value;
           }
      }
      

      【讨论】:

        猜你喜欢
        • 2016-09-15
        • 1970-01-01
        • 2019-01-10
        • 1970-01-01
        • 1970-01-01
        • 2012-05-05
        • 2020-02-14
        • 2010-11-02
        • 2011-01-05
        相关资源
        最近更新 更多