ViewModel 中的 DependencyProperty 注册

Posted

技术标签:

【中文标题】ViewModel 中的 DependencyProperty 注册【英文标题】:DependencyProperty Registration in ViewModel 【发布时间】:2014-04-13 06:33:35 【问题描述】:

我发现很多关于 ViewModel 及其属性的讨论比较了两种方法:INotifyPropertyChanged 的实现或通过 依赖属性 的实现。

虽然我经常使用 INotifyPropertyChanged(并且它正在工作),但我在实施 DP 方法时遇到了困难。

当我像这样在 ViewModel 中注册 DP 时

    public static readonly DependencyProperty SomePropertyProperty =
        DependencyProperty.Register("SomeProperty", typeof(string), typeof(MyUserControl));

并尝试在某处使用它:

<myNameSpace:MyUserControl SomeProperty="Binding ..."/>

编译器出错:

The property 'SomeProperty' does not exist in XML namespace 'clr-namespace:myNameSpace'.

我做错了什么??


编辑1

ViewModel 如下所示:

public class MyUserControlVM : DependencyObject


    public string SomeProperty
    
        get  return (string)GetValue(SomePropertyProperty); 
        set  SetValue(SomePropertyProperty, value); 
    

    public static readonly DependencyProperty SomePropertyProperty =
        DependencyProperty.Register("SomeProperty", typeof(string), typeof(MyUserControl));     

【问题讨论】:

你还有一个名为 SomeProperty 的属性吗? 请看我的编辑... 是的,因为您试图在MyUserControl 上设置SomeProperty,它没有声明此属性。您混淆了 UserControl 和 ViewModel 类。 问题是,为什么要在视图模型中使用依赖属性?为什么不只保留 INotifyPropertyChanged? 但是您必须在您的 UserControl 类中声明该属性,而不是在您的视图模型中。无论如何,这将是典型的 WPF 方法,视图中的 DP,视图模型中的 INotifyPropertyChanged。 【参考方案1】:

您是否实现了标准的属性访问器?完整的 DP 签名如下所示:

public static readonly DependencyProperty PropertyNameProperty =
        DependencyProperty.Register("propertyName", typeof (PropertyType), typeof (MyUserViewModel), new PropertyMetadata(default(PropertyType)));

    public PropertyType PropertyName
    
        get  return (PropertyType) GetValue(PropertyNameProperty); 
        set  SetValue(PropertyNameProperty  value); 
    

那么你的代码应该可以工作了。关于 DP 与 INotifyPropertyChanged 的​​更多信息:对我来说,主要的权衡是速度与可读性。用依赖属性声明乱扔 ViewModel 很痛苦,但通知管道的速度提高了 30%。

编辑:

你在View的类型上注册属性,它应该是ViewModel的类型,即

public static readonly DependencyProperty PropertyNameProperty =
        DependencyProperty.Register("propertyName", 
        typeof (PropertyType), 
        typeof (MyUserViewModel), 
        new PropertyMetadata(default(PropertyType)));

而不是

public static readonly DependencyProperty PropertyNameProperty =
        DependencyProperty.Register("propertyName", 
        typeof (PropertyType),
        typeof (MyUserControl), 
        new PropertyMetadata(default(PropertyType)));

编辑 2:

好的,你在这里混淆了一些东西:你可以在你的 ViewModel 和你的 View 上同时拥有依赖属性。对于前者,您在控件的代码隐藏(即 MyUserControl.xaml.cs)中定义 DP。对于后者,您可以在 ViewModel 中定义它,正如我在上面展示的那样。您的代码的问题在于用法:

您正在尝试将 DataContext 的某些值绑定到名为 SomeProperty 的属性 观点:

<myNameSpace:MyUserControl SomeProperty="Binding SomePropertyBindingValue"/>

当您在视图模型上定义依赖属性时,视图上没有属性SomeProperty,因此您会收到编译器错误。要使上述用法生效,您需要将 DP 放在 View 的 代码隐藏中,并在 ViewModel 上定义一个普通属性SomePropertyBindingValue

要在 ViewModel 上定义 DP 并在视图中使用,需要绑定到这个属性:

<myNameSpace:MyUserControl Width="Binding SomeProperty"/>

假设您已正确连接 ViewModel 和 View,这会将视图宽度绑定到 ViewModel 的属性SomeProperty。现在,如果在 ViewModel 上设置了SomeProperty,UI 将会更新,尽管您还没有实现 INPC。

编辑 3:

据我了解,您的问题是 - 要获得所需的行为 - 您需要将 control 上的一个依赖项属性绑定到单独 ViewModels 上的两个属性: MainWindowVM 上的一个属性应该绑定到 UserControl,然后 - 从 UserControl - 返回到另一个 ViewModel ( UserControl1VM)。这里的设计有点扭曲,在不知道确切上下文的情况下,我不明白为什么你不能在 ViewModel 级别处理属性同步:

我让我的 ViewModel 或多或少类似于 View 的嵌套结构:

假设你有一个视图(伪代码):

<Window>
    <UserControl1 />
</Window>

让窗口的数据上下文为MainWM,不管它来自哪里,这都不是正确的XAML(!):

<Window DataContext="[MainVM]">
    <UserControl1 />
</Window>

问题 1 是,为什么用户控件需要它自己的 ViewModel?您可以简单地将其绑定到 MainVM 的属性“SomeProperty”:

<Window DataContext="[MainVM]">
    <UserControl Text="Binding SomeProperty" />
</Window>

好的,假设您确实有充分的理由需要一个 UserControlViewModel,它拥有自己的属性“UCSomeProperty”:

public class UserControlVM

    public string UCSomeProperty  get; set;  // Let INPC etc. be implemented

UserControlVM 属性添加到 MainVM

public class MainVM

    public UserControlVM UserControlVM  get; set;  // INPC etc.

现在,您可以设置绑定了:

<Window DataContext="[MainVM]">
    <UserControl DataContext="Binding UserControlVM" 
                 Text="Binding UCSomeProperty" />
</Window>

最后,在不知道您的具体情况以及它是否有意义的情况下,假设您现在想要一个与用户控件的 ViewModel 属性上的属性同步的“MainVM”上的属性:

public class MainVM

    public string SomeProperty
    
         get  return UserControlVM.UCSomeProperty; 
         set  UserControlVM.UCSomeProperty = value; 
    

    public UserControlVM UserControlVM  get; set;  // INPC etc.

    public MainVM()
    
         UserControlVM = new UserControlVM();
         UserControlVM.NotifyPropertyChanged += UserControlVM_PropertyChanged;
    

    private void UserControlVM_PropertyChanged(object sender, BlaArgs e)
    
         if (e.PropertyName == "UCSomeProperty")
              RaisePropertyChanged("SomeProperty");
    
 

你可以像这样使用绑定,例如:

 <Window DataContext="[MainVM]">
    <UserControl DataContext="Binding UserControlVM" 
                 Text="Binding UCSomeProperty" />
    <TextBlock Text="Binding SomeProperty" />
</Window>
MainVM 上的

SomeProperty 和 USerControlVM 上的 UCSomeProperty 现在始终相同,并且在两个 ViewModel 上都可用。我希望这会有所帮助...

【讨论】:

请检查您注册 DP 的类型(请参阅我的编辑)。那它有用吗? 好的,但是我不能将我的属性PropertyName 放在虚拟机中,对吧?那将是我的目标...... 这是可能的。如果您只是将 typeof(MyUserControl) 更改为 typeof(MyUserViewModel),则 DP 位于 ViewModel 上。并且传统的属性定义也需要在 VM 上。 很抱歉,我无法将其设置为运行。您能否提供一些源代码来展示整个实现? 好的,我想我理解的很慢(但不完全,抱歉)。我认为我的理解问题在于控件的嵌套结构和相应的虚拟机。我再试一次:我在这个 MainWindow 中有 MainWindow 及其 DataContext MainWindowVM 和一个 UserControl1 及其 DataContext UserControl1VM。我想将 MainWindowVM 中的(普通)属性绑定到 UserControl1 上的 DP,该 DP 再次绑定到 UserControl1VM 中的(普通)属性。我现在缺少的连接是 UserControl1 中的 DP 和 UserControl1VM 中的属性之间的连接。再次感谢!!【参考方案2】:

除此之外:

public static readonly DependencyProperty SomePropertyProperty =
        DependencyProperty.Register("SomeProperty", typeof(string), typeof(MyUserControl));

你还需要这个:

public string SomeProperty 

    get 
    
        return (string)GetValue(SomePropertyProperty);
    
    set
    
        SetValue(SomePropertyProperty, value);
    

【讨论】:

【参考方案3】:

当你说

<myNameSpace:MyUserControl SomeProperty="Binding ..."/>

您的意思是创建一个 MyUserControl 类的对象并使用绑定设置其属性 SomeProperty...

你做错了什么—— 如果你看这条线

("SomeProperty", typeof(string), typeof(MyUserControl))

仅声明 typeof(MyUserControl) 不足以使该属性可用于 MyUserControl

MyUserControl 类没有SomeProperty 属性,而是在视图模型中,因此会出现编译器错误。

理想情况下 DependencyProperty 应该在 UserControl 后面的代码中声明,而不是像下面的代码那样在 ViewModel 中声明。

public class MyUserControl

    public string SomeProperty
    
        get  return (string)GetValue(SomePropertyProperty); 
        set  SetValue(SomePropertyProperty, value); 
    

    public static readonly DependencyProperty SomePropertyProperty =
        DependencyProperty.Register("SomeProperty", typeof(string), 
        typeof(MyUserControl));     


public class MyUserControlVM

  public string SomePropertyBindingValueget;set;

然后您可以将 Dependency 属性与 viewModel 属性绑定。像这样

<myNameSpace:MyUserControl SomeProperty="Binding SomePropertyBindingValue"/>

编辑 - 从您的评论中提出您的问题

我有 MainWindow 及其 DataContext MainWindowVM 和一个 UserControl1 及其在此 MainWindow 中的 DataContext UserControl1VM。我要绑定 MainWindowVM 中的(正常)属性到 UserControl1 上的 DP,即 再次绑定到 UserControl1VM 中的(正常)属性。连接 我现在缺少的是 UserControl1 中的 DP 之间的那个 以及 UserControl1VM 中的属性。

我建议您重新考虑您的要求,这可能不是解决您问题的最佳方法。原因如下:

您希望 MainWindowVM 和 UserControl1VM 共有一个属性,并且您试图通过将 ViewModel 绑定到 View 的 Dependency 属性并将其从 View 传递回子 ViewModel 来实现这一点。这打破了 MVVM 模式的目的 - View 与其 ViewModel 之间的职责分离。

如果将它作为构造函数参数从 MainWINdowVM 传递给 UserControl1VM 会更容易。这样你就可以将它们绑定到任何你喜欢的视图上,而不是通过视图传递它。

【讨论】:

以上是关于ViewModel 中的 DependencyProperty 注册的主要内容,如果未能解决你的问题,请参考以下文章

如何处理 ViewModel 中的配置更改

ViewModel 中的 INotifyPropertyChanged 与 DependencyProperty

Catel 中的 ViewModel 集合

ViewModel 中的可绑定字段 [重复]

Kotlin 中的 ViewModel:未解决的参考

ViewModel 中的 DependencyProperty 注册