在WPF MVVM中,在ShowDialog中将UpdateSourceTrigger设置为Explicit

7
我看到了这个例子 - Binding.UpdateSourceTrigger 属性
在这个例子中,UpdateSourceTrigger设置为Explicit,然后在视图代码中调用TextBox名称的UpdateSource。
但是如果我使用MVVM dp,我不想给我的控件命名,源属性在VM中而不是在视图中,那么将控件绑定到VM属性并将UpdateSourceTrigger设置为explicit的正确方法是什么?
我想这样做是因为在我的情况下,它是ShowDialog窗口,我希望只有当用户点击“确定”时才更新源。
预先感谢!
2个回答

15
如果您真正使用 MVVM,则 OK 按钮单击必须由某个“命令”处理。此命令必须来自您的“ViewModel”。显式绑定的属性也必须再次来自您的“ViewModel”。那么是什么阻止了您呢?
  1. 不要使用明确的绑定,而是使用单向绑定。
  2. 在您的按钮中,绑定一个命令,并将命令参数绑定到“单向绑定”依赖项属性。
  3. 在您的命令的执行处理程序中(它必须是来自您的 ViewModel 的某个方法),使用传入的参数更改 ViewModel 的属性。
  4. 从您的“ViewModel”引发该属性的“NotifyPropertyChanged”。
例如。
假设我需要在单击 OK 按钮时将 TextBox 的文本更新回我的模型。
因此,我有一个 EmployeeViewModel 类,其中包含 EmployeeName 属性。该属性具有 getter 和 setter。setter 引发属性更改通知。视图模型还具有另一个类型为 ICommand 的属性,命名为 SaveNameCommand,它返回一个要执行的命令。 EmployeeViewModel 是我的视图的数据上下文类型。我的视图有一个名为 x:Name="EmployeeNameTxBx" 的 TextBox,单向绑定到 EmployeeName,并具有一个名为“ OK” 的按钮。我将 Button.Command 属性绑定到 EmployeeViewModel.SaveNameCommand 属性,而 Button.CommandParameter 则绑定到 EmployeeNameTxBx.Text 属性。
      <StackPanel>
          <TextBox x:Name="EmployeeNameTxBx"
                   Text="{Binding EmployeeName, Mode=OneWay}" />
          <Button Content="OK"
                  Command="{Binding SaveNameCommand}"
                  CommandParameter="{Bidning Text, ElementName=EmployeeNameTxBx}" />
      </StackPanel>

在我的EmployeeViewModel内,我有一个OnSaveNameCommandExecute(object param)方法来执行我的SaveNameCommand

在这段代码中执行以下操作...

     var text = (string)param;
     this.EmployeeName = text;

仅通过单击“确定”按钮,将文本框中的文本更新回模型的EmployeeName属性。

编辑

根据您在下面的评论中所述,我发现您正在尝试在UI上实现验证。这样有点改变了事情。

IDataErrorInfo和相关的验证仅在您的输入控件(如文本框)是双向绑定时才有效。是的,这就是它的意图。那么现在你可能会问“如果我们使用IDataErrorInfo,这是否意味着不允许无效数据传递到模型的整个概念在MVVM中是徒劳的?”

实际上并非如此!

请注意,MVVM不强制规定仅应返回有效数据。它接受无效数据,这就是IDataErrorInfo工作并引发错误通知的方式。关键点是ViewModel只是您的View的纯粹副本,因此它可以是脏的。它应该确保这种肮脏不被提交到外部接口,如服务或数据库。

这种无效数据流应由ViewModel通过测试无效数据来限制。如果启用了TwoWay绑定,则该数据将传递。因此,如果您正在实现IDataErrorInfo,则需要使用TwoWay绑定,这在MVVM中是完全允许的。

方法1:

如果我想在单击按钮时显式验证某些UI上的项目怎么办?

为此,请使用延迟验证技巧。在ViewModel中有一个名为isValidating的标志。默认情况下将其设置为false。

IDataErrorInfo.this属性中,通过检查isValidating标志来跳过验证...

    string IDataErrorInfo.this[string columnName]
    {
      get
      {
        if (!isValidating) return string.Empty;

        string result = string.Empty;
        bool value = false;

        if (columnName == "EmployeeName")
        {
            if (string.IsNullOrEmpty(AccountType))
            {
                result = "EmployeeName cannot be empty!";
                value = true;
            }
        }
        return result;
      }
    }

然后在您执行OK命令处理程序时,检查员工姓名,然后为同一属性引发属性更改通知事件...

    private void OnSaveNameCommandExecute(object param)
    {
         isValidating = true;
         this.NotifyPropertyChanged("EmployeeName");
         isValidating = false;
    }

只有在单击“确定”时才会触发验证。请记住,EmployeeName必须包含无效数据,以便验证能够生效。

方法2:

如果我想在MVVM中不使用TwoWay模式来显式更新绑定怎么办?

那么您就必须使用附加行为。该行为将附加到“确定”按钮,并将接受需要刷新其绑定的所有项的列表。

       <Button Content="OK">
           <local:SpecialBindingBehavior.DependentControls>
                <MultiBinding Converter="{StaticResource ListMaker}">
                    <Binding ElementName="EmployeeNameTxBx" />
                    <Binding ElementName="EmployeeSalaryTxBx" />
                    ....
                <MultiBinding>
           </local:SpecialBindingBehavior.DependentControls>
       </Button>
ListMaker 是一个 IMultiValueConverter,它简单地将值转换为列表。
       Convert(object[] values, ...)
       {
            return values.ToList();
       }
在您的 SpecialBindingBehavior 中有一个 DependentControls 属性更改处理程序...
      private static void OnDependentControlsChanged(
          DependencyObject depObj,
          DependencyPropertyChangedEventArgs e) 
      {
           var button = sender as Button;
           if (button != null && e.NewValue is IList)
           {
               button.Click 
                    += new RoutedEventHandler(
                         (object s, RoutedEventArgs args) =>
                         {
                              foreach(var element in (IList)e.NewValue)
                              {
                                 var bndExp
                                   = ((TextBox)element).GetBindingExpression(
                                       ((TextBox)element).Textproperty);

                                 bndExp.UpdateSource();
                              }
                         });
           }
      }

但我仍然建议您使用我之前的纯MVVM基于**方法1


首先感谢您详细的回答!关于我的问题,我使用纯MVVM模式,在我的ViewModel中有一个命令,并且我使用NotifyPropertyChanged。但是如果我使用OneWay绑定,在窗口中有10个文本框,这意味着我需要在命令参数中发送10个元素名称?然后再手动更新它们?难道没有使用ToWay绑定的方法吗? - Maya
现在我正在使用IDataError来验证文本框中的文本,如果我使用OneWay绑定,我仍然可以验证文本吗? - Maya
如果我理解你的回答,我需要进行双向绑定,但然后我会回到之前的问题,即我不想立即更新源代码,而只是在用户单击“确定”时进行更新,所以我需要将UpdateSourceTrigger设置为显式,然后怎么做?我无法在TextBox实例上使用UpdateSource,因为我在VM中而不是在View中... - Maya
我有什么遗漏吗?让我告诉你我的理解,如果我实现了IDataErrorInfo,我需要进行双向绑定,然后我就可以避免将错误数据实际更新到源。到目前为止一切都好。现在,即使没有错误输入,如果用户不点击“确定”按钮,我也不想更新源。例如,用户单击“取消”或按Esc键。因此,如果我使用ToWay和IDataErrorInfo没有引发任何错误,即使用户单击“取消”,源也会立即更新。 - Maya

1
这是一个老问题,但我仍然想为其他遇到此问题的用户提供替代方法... 在我的视图模型中,我不直接公开模型属性获取/设置属性方法。我为所有属性使用内部变量。然后我双向绑定所有属性。因此,我可以像“通常”一样进行所有验证,因为只有内部变量会发生更改。在视图模型构造函数中,我将模型对象作为参数,并将内部变量设置为我的模型的值。现在,当我单击“保存”按钮(->保存命令在我的视图模型中触发)且没有错误时,我将我的模型的所有属性设置为相应内部变量的值。如果我单击“取消/撤消”按钮(->在我的视图模型中触发Cancel-Command),我将内部变量设置为我的未更改模型的值(使用视图模型属性的setter以便调用NotifyPropertyChanged并显示更改=旧值)。
另一种方法是在模型中实现备忘录支持,这样在开始编辑之前,您可以调用模型中的函数来保存当前值,如果取消编辑,则调用函数以恢复这些值...这样您就可以在任何地方拥有撤销/取消支持,而不仅仅是在一个视图模型中...我已经在不同的项目中实现了这两种方法,两种方法都很好,具体取决于项目的要求...

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