在ViewModel中注册DependencyProperty

7

我希望你能帮忙翻译有关 IT 技术的文本。以下是需要翻译的内容:

我看到很多有关 ViewModel 及其属性的讨论,比较了两种实现方法:通过 INotifyPropertyChanged 实施或通过 Dependency Properties 实施。

虽然我经常使用 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的属性? - Håkan Fahlstedt
依赖属性的所有者类型由Register调用中的第三个参数指定,必须是声明该属性的类型。这里是MyUserControlVM,而不是MyUserControl - Clemens
1
是的,因为您正在尝试在未声明此属性的"MyUserControl"上设置"SomeProperty"。 您混淆了UserControl和ViewModel类。 - Clemens
3
为什么你想在视图模型中使用依赖属性?为什么不只使用 INotifyPropertyChanged 呢? - Clemens
1
但是您必须在 UserControl 类中声明属性,而不是在 ViewModel 中声明。这也是典型的 WPF 方法,即在视图中使用 DP,在 ViewModel 中使用 INotifyPropertyChanged。 - Clemens
显示剩余5条评论
3个回答

7
你是否已经实现了标准属性访问器?一个完整的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%的速度提升。

编辑:

你应该在ViewModel的类型上注册属性,而不是View的类型。

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: 据我所知,您的问题是为了获得所需的行为,您需要在控件上绑定一个依赖属性到两个独立的 ViewModel 上的属性:一个属性在 MainWindowVM 上应该绑定到 UserControl ,然后从 UserControl 返回至另一个 ViewModel ( UserControl1VM )。 这里的设计有点扭曲,并且不知道确切的上下文,我不明白为什么您不能在ViewModel级别处理属性同步:
我让我的ViewModels更或多或少地类似于View的嵌套结构:
假设您有一个视图(伪代码):
<Window>
    <UserControl1 />
</Window>

将窗口的数据上下文设为MainWM,无论数据来自何处,这都不是正确的XAML写法(!):

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

第一个问题是,为什么用户控件需要有自己的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
}

MainVM 中添加一个 UserControlVM 属性:

public class MainVM
{
    public UserControlVM UserControlVM { get; set; } // INPC etc.
}

现在,您可以设置绑定:
<Window DataContext="[MainVM]">
    <UserControl DataContext="{Binding UserControlVM}" 
                 Text="{Binding UCSomeProperty}" />
</Window>

最后,假设你现在想让'MainVM'上的属性与用户控件的ViewModel属性同步,但不了解你具体的情况和是否有意义。
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时使用的类型(请参见我的编辑)。然后它能正常工作吗? - Marc
抱歉,我无法设置运行。您能提供一些源代码来展示整个实现吗? - joerg
2
好的,我觉得我慢慢开始明白了(但还没有完全理解,抱歉)。我认为我理解上的问题在于控件和相应 VM 的嵌套结构。我再试一次:我有一个 MainWindow 和它的 DataContext MainWindowVM,以及一个 UserControl1 和它的 DataContext UserControl1VM 在这个 MainWindow 中。我想将 MainWindowVM 中的一个(普通)属性绑定到 UserControl1 上的 DP,然后再将其绑定到 UserControl1VM 中的一个(普通)属性。我现在缺少的连接是 UserControl1 中的 DP 和 UserControl1VM 中的属性之间的连接。再次感谢! - joerg
@joerg:抱歉,我现在有很多工作要做。我会编辑我的答案并感谢您的点赞! - Marc
即使这不是我寻找的“理想”解决方案,但我将其标记为已解决,因为它解释得很好并且有效。我又在这里提出了一个更一般性的问题。谢谢,Marc! - joerg
显示剩余6条评论

1
除此之外:
public static readonly DependencyProperty SomePropertyProperty =
        DependencyProperty.Register("SomeProperty", typeof(string), typeof(MyUserControl));

你还需要这个:

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

1
当你说的时候
<myNameSpace:MyUserControl SomeProperty="{Binding ...}"/>

你的意思是创建一个 MyUserControl 类的对象,并通过绑定设置它的属性 SomeProperty...

你做错了什么 - 如果你看一下这行代码

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

仅声明typeof(MyUserControl)是不足以使属性对MyUserControl可用。

MyUserControl类没有属性SomeProperty,相反它在视图模型中,因此您会得到编译器错误。

理想情况下,依赖属性应该在用户控件的代码后台声明,而不是在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 SomePropertyBindingValue{get;set;}
}

然后,您可以使用以下方式将依赖属性与viewModel属性绑定。
<myNameSpace:MyUserControl SomeProperty="{Binding SomePropertyBindingValue}"/>

编辑 - 从您的评论发布问题

我有一个MainWindow和它的DataContext MainWindowVM以及一个UserControl1和它的DataContext UserControl1VM在这个MainWindow中。我想将MainWindowVM中的一个(普通)属性绑定到UserControl1上的DP,然后再将其绑定到UserControl1VM中的一个(普通)属性。我现在缺少的连接是UserControl1中的DP与UserControl1VM中的属性之间的连接。

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

您希望一个属性对于MainWindowVM和UserControl1VM都是共同的,并且您正在尝试通过将ViewModel绑定到View的Dependency属性并将其传递回子ViewModel来实现这一点。这破坏了MVVM模式的目的 - 在View和其ViewModel之间分离责任。

如果您将其作为构造函数参数从MainWIndowVM传递给UserControl1VM,则会更容易。这样,您可以在喜欢的地方将它们绑定到View,而不是通过View传递它们。


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