【问题标题】:Binding WPF UserControl to View Model and Code Behind绑定 WPF UserControl 以查看模型和代码
【发布时间】:2015-09-11 21:05:56
【问题描述】:

我试图了解连接自定义控件以使用依赖属性和视图模型的最佳方式。实现依赖属性以公开可在 XAML 中用于初始化视图模型中的属性的属性。例如,在自定义控件的代码中,我可以定义以下依赖属性:

public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register(
         "MyProperty", typeof(string), typeof(MyControl), new PropertyMetadata(null));

public string MyProperty
{
  get { return (string)GetValue(MyPropertyProperty); }
  set { SetValue(MyPropertyProperty, value); }
}

视图模型定义为

public class MyControlViewModel : INotifyPropertyChanged
   {
      public MyControlViewModel()
      {
         _myProperty = "Default View Model string";
      }

      private string _myProperty;

      public string MyProperty
      {
         get
         {
            return _myProperty;
         }
         set
         {
            _myProperty = value;
            OnPropertyChanged("MyProperty");
         }
      }

      public event PropertyChangedEventHandler PropertyChanged;

      protected virtual void OnPropertyChanged(string propertyName = null)
      {
         var handler = PropertyChanged;
         if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));

      }
   }

自定义控件绑定到 View Model MyProperty 如下

<UserControl x:Class="MyProject.MyControlView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:MyProject">

    <UserControl.DataContext>
        <local:MyControlViewModel/>
    </UserControl.DataContext>

    <Grid>
        <TextBlock Text="{Binding MyProperty}"/>                
    </Grid>

</UserControl>

现在,由于我已经在自定义控件的代码中定义了依赖属性 MyProperty,我希望能够使用它来初始化 ViewModel 中的 MyProperty。所以像这样的

 <Window x:Class="MyProject.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:MyProject"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <local:MyControlView MyProperty="This was set in XAML"/>
    </Grid>
</Window>

运行上述将显示在视图模型的构造函数中设置的字符串“默认视图模型字符串”。如何连接依赖属性值,以便正确初始化视图模型中的字符串?即它应该显示“这是在 XAML 中设置的”。

更新

我可以在后面的代码中设置属性更改回调,并在视图模型中设置值,即

public static readonly DependencyProperty MyPropertyProperty =
          DependencyProperty.Register("MyProperty", typeof(string), typeof(MyControlView), new PropertyMetadata("Default", OnMyPropertyChanged));

      private static void OnMyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
      {
         var view = d as MyControlView;
         if (view != null)
         {
            var viewModel = view.DataContext as MyControlViewModel;
            if (viewModel != null)
            {
               viewModel.MyProperty = e.NewValue as string;
            }
         }
      }

这是正确的,还是有味道?

【问题讨论】:

    标签: wpf mvvm binding viewmodel dependency-properties


    【解决方案1】:

    您已经创建了很好的依赖属性。但是,您已经通过实例化您的视图模型并将其属性使用到后面的代码中来实现紧密耦合。方法不对。

    您应该从“Control”继承您的类并为绑定值创建一个DP,如下面的custom control class

    public static readonly DependencyProperty myValueProperty = DependencyProperty.Register(
                "MyProperty", typeof(object), typeof(FieldControl), new FrameworkPropertyMetadata(null, myValueChanged));
    

    那么,

    private static void myValueChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
            {
                var fc = dependencyObject as FieldControl;
                if (fc != null)
                {
                    fc.SetValue(BindingExpression1Key, fc.GetBindingExpression(ValueProperty));
                    fc.RaiseValueChangedEvent(dependencyPropertyChangedEventArgs);
                }
            }
    

    现在,使用它并像下面这样绑定你的属性:

     <local:MyControlView MyProperty="{Binding MyProperty, Mode=TwoWay}"/>
    

    【讨论】:

    • 抱歉,我很困惑。这如何在我的 ViewModel 中设置值? ViewModel 特定于控件(一对一的关系),如果它是耦合的,这有关系吗?您是否能够更新您的答案以复制问题中的类?
    • 把控制权放到其他类。你不能在视图模型中创建你的控件和依赖属性。
    • 如果我不能在视图模型中创建我的控件和依赖属性,那么如何对执行某些复杂逻辑的视图进行单元测试?
    • 它违反了规则。您正在创建自定义控件。因此,自定义控件应该是单独的类。 ViewModel 应该独立于此。
    • 谢谢古尔。但是,我不明白为什么自定义控件不能拥有自己的视图模型。如果您正在定义一个复杂的复合控件并希望编写单元测试以确保该控件按预期工作,则必须将要测试的任何视图逻辑放在 POCO 中,即 ViewModel。我认为如果它在这里紧密耦合并不重要,因为自定义控件将始终与其视图模型并行存在 - 两者作为控件不可分割,现在可以测试该控件。
    【解决方案2】:

    您可以使用混合行为基于视图的依赖属性 (DP) 更新 ViewModel (INPC) 属性。此解决方案避免了在代码隐藏中添加属性更改回调:

    <UserControl x:Class="MyProject.MyControlView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"             
             xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
             xmlns:ic="clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions"
             xmlns:local="clr-namespace:MyProject"
             x:Name="MyControl">
    
    <UserControl.DataContext>
        <local:MyControlViewModel/>
    </UserControl.DataContext>
    
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Loaded">
            <ic:ChangePropertyAction
                TargetObject="{Binding}"
                PropertyName="MyProperty"
                Value="{Binding ElementName=MyControl, Path=MyProperty}" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
    
    <Grid>
        <TextBlock Text="{Binding MyProperty}"/>
    </Grid>
    

    您必须在项目中添加对 System.Windows.Interactivity 和 Microsoft.Expression.Interactivity.Core 程序集的引用才能使用混合行为(注意我添加的 iic 命名空间)。

    在这种情况下,我使用调用ChangePropertyActionEventTrigger 挂钩到UserControl Loaded 事件。通过为TargetObject 指定{Binding},我通知行为使用绑定DataContext 中的视图模型。 PropertyName 指的是视图模型上的属性。 Value 指的是视图上的 DP。我给你的 UserControl 起了一个名字,这样我在查询 MyProperty 的值时可以很容易地引用它。

    这样做的好处是,您可以在 DataContext 中替换您的视图模型(只要新模型也具有 MyProperty 属性),而无需更新代码隐藏。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-10-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-13
      • 1970-01-01
      • 2015-11-29
      相关资源
      最近更新 更多