如何将视图的DependencyProperty双向绑定到ViewModel的属性?

24
多个网络来源告诉我们,在 MVVM 中,视图和视图模型之间的通信/同步应该通过依赖属性进行。如果我理解正确,视图的依赖属性应该使用双向绑定将其绑定到视图模型的属性上。现在,类似的问题以前已经被问过了,但没有得到足够的答案。
在我开始分析这个相当复杂的问题之前,这是我的问题:
如何将自定义视图的 Dependency Property 与视图模型的属性同步?
在理想的情况下,您可以简单地将其绑定为:
<UserControl x:Class="MyModule.MyView" MyProperty="{Binding MyProperty}">

这不起作用,因为MyProperty不是UserControl的成员。我尝试了不同的方法,但都没有成功。
一种解决方案是定义一个基类UserControlEx,具有必要的依赖属性以使上述工作。然而,这很快变得非常混乱。不够好!

包含MyProperty的对象应设置为控件的DataContext。 - Scroog1
@Clemens,您的意思是绑定应该发生在视图实例化的地方吗?如果是这样,您如何告诉它使用自己的 DataContext 而不是父级(实例化它的地方)的 DataContext - l33t
@l33t 只是为了确保我们谈论的是同一件事。bound 属性(第一个 MyProperty)是 UserControl 类 MyView 中的依赖属性。绑定源属性(绑定声明中的第二个 MyProperty)是 ViewModel 类中的属性,通常是实现 INotifyPropertyChanged 的类中的 CLR 属性。 - Clemens
@Clemens,是的。这个想法是通过它们绑定的属性在视图和视图模型之间进行通信。这种技术已经在许多网站/论坛上提到过(但从未演示:P)。 - l33t
“但从未展示过”?那下面的答案呢?它完全符合你的要求。也许可以通过在XAML中声明绑定来改进它,而不是在代码中。而且你可能还会在ViewModelString属性中省略对旧值和新值的比较。 - Clemens
显示剩余2条评论
3个回答

18
如果您想在XAML中实现此操作,可以尝试使用样式来实现。
以下是一个示例:
<UserControl x:Class="MyModule.MyView"
             xmlns:local="clr-namespace:MyModule">
    <UserControl.Resources>
        <Style TargetType="local:MyView">
            <Setter Property="MyViewProperty" Value="{Binding MyViewModelProperty, Mode=TwoWay}"/>
        </Style>
    </UserControl.Resources>
    <!-- content -->
</UserControl>

在您的情况下,MyViewPropertyMyViewModelProperty都将被命名为MyProperty,但是我使用不同的名称只是为了清楚每个属性的含义。


1
有趣的想法!也许下次遇到这个问题时我会尝试一下。 - l33t
谢谢,我一直在寻找这个。 - Mohsen Afshin
我已经尝试过这个,但是当我在主窗口xaml中设置用户控件的DependencyProperty时,在应用程序/窗口/控件初始化期间我的视图模型上的setter没有被使用。有什么想法吗? - Kamil

8

我使用Caliburn.Micro将ViewModel与View分开。不过,在MVVM中也可能以同样的方式工作。我猜想MVVM也会将视图的DataContext属性设置为ViewModel的实例。

视图

// in the class of the view: MyView
public string ViewModelString // the property which stays in sync with VM's property
{
    get { return (string)GetValue(ViewModelStringProperty); }
    set
    {
        var oldValue = (string) GetValue(ViewModelStringProperty);
        if (oldValue != value) SetValue(ViewModelStringProperty, value);
    }
}

public static readonly DependencyProperty ViewModelStringProperty =
    DependencyProperty.Register(
        "ViewModelString",
        typeof(string),
        typeof(MyView),
        new PropertyMetadata(OnStringValueChanged)
        );

private static void OnStringValueChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
    // do some custom stuff, if needed
    // if not, just pass null instead of a delegate
}    

public MyView()
{
    InitializeComponent();
    // This is the binding, which binds the property of the VM
    // to your dep. property.
    // My convention is give my property wrapper in the view the same
    // name as the property in the VM has.
    var nameOfPropertyInVm = "ViewModelString"
    var binding = new Binding(nameOfPropertyInVm) { Mode = BindingMode.TwoWay };
    this.SetBinding(SearchStringProperty, binding);
}

VM

// in the class of the ViewModel: MyViewModel
public string ViewModelStringProperty { get; set; }

请注意,这种实现完全缺乏对INotifyPropertyChanged接口的实现。您需要适当更新此代码。

调用 SetBinding 会与在 XAML 中定义的任何现有的 ViewModelString 绑定发生冲突。只要不在 XAML 中绑定到 ViewModelString,它就可以正常工作。您知道是否可以在 XAML 中完成这个操作吗? - l33t
什么意思?XAML和代码后端之间的绑定只有在视图中执行DataContext = this时才有效。MVVM将DataContext属性设置为ViewModel的实例。我不知道这可能会引起什么冲突? - Michael Schnerring
我必须回复您关于那个问题。不过,以上内容能在XAML中实现吗? - l33t
我提到的冲突在这里描述:https://groups.google.com/forum/?fromgroups=#!topic/microsoft.public.windows.developer.winfx.avalon/uXjLOsSAsVI - l33t
我认为(至少在我的情况下)将发送者转换为控件类型,将控件.DataContext转换为VM类型,并在OnStringValueChanged回调中将所需属性设置为e.newValue更容易。无论哪种方式都很丑陋,似乎是WPF绑定系统中的阻抗不匹配。 - user3230660
显示剩余2条评论

5

假设您在视图中定义了DependencyProperty“DepProp”,并且想要在ViewModel中使用完全相同的值(实现了INotifyPropertyChanged而不是DependencyObject)。您应该能够在XAML中执行以下操作:

<UserControl x:Class="MyModule.MyView"
         xmlns:local="clr-namespace:MyModule"
             x:Name="Parent">
    <Grid>
        <Grid.DataContext>
            <local:MyViewModel DepProp="{Binding ElementName=Parent, Path=DepProp}"/>
        </Grid.DataContext>
    ...
    </Grid>
</UserControl>

对我来说 - 它说 'System.Windows.Data.Binding' 无法转换为类型 'System.Int32'(Int32 是我的 ViewModel 中的类型) - Kamil

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