绑定用户控件的依赖属性和MVVM

19

我有一个包含UserControl的MainWindow,两者都是用MVVM模式实现的。 MainWindowVM具有我想要绑定到UserControl1VM属性的属性。但这不起作用。

以下是一些代码(视图模型使用某种mvvm框架,该框架在ViewModelBase类中实现了INotifyPropertyChanged,但希望这不是问题):

MainWindow.xaml:

<Window x:Class="DPandMVVM.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:DPandMVVM"
    Title="MainWindow" Height="300" Width="300">
    <Grid>
        <local:UserControl1 TextInControl="{Binding Text}" />
    </Grid>
</Window>

MainWindow.xaml.cs的CodeBehind:

using System.Windows;
namespace DPandMVVM
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new MainWindowVM();
        }
    }
}

主窗口视图模型 MainWindowVM.cs:

namespace DPandMVVM
{
    public class MainWindowVM : ViewModelBase
    {
        private string _text;
        public string Text { get { return _text; } }

        public MainWindowVM()
        {
            _text = "Text from MainWindowVM";
        }
    }
}

以下是 UserControl1.xaml 的内容:

<UserControl x:Class="DPandMVVM.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <TextBlock Text="{Binding TextInTextBlock}" />  
    </Grid>
</UserControl>

Codebehind UserControl1.xaml.cs:

using System.Windows.Controls;    
namespace DPandMVVM
{
    /// <summary>
    /// Interaction logic for UserControl1.xaml
    /// </summary>
    public partial class UserControl1 : UserControl
    {
        public UserControl1()
        {
            InitializeComponent();
            DataContext = new UserControl1VM();
        }
    }
}

并且Viewmodel UserControl1VM.cs文件:

using System.Windows;    
namespace DPandMVVM
{
    public class UserControl1VM : DependencyObject
    {
        public UserControl1VM()
        {
            TextInControl = "TextfromUserControl1VM";
        }

        public string TextInControl
        {
            get { return (string)GetValue(TextInControlProperty); }
            set { SetValue(TextInControlProperty, value); }
        }

        public static readonly DependencyProperty TextInControlProperty =
            DependencyProperty.Register("TextInControl", typeof(string), typeof(UserControl1VM));
    }
}

在这种星座下,DP无法在MainWindow.xaml中找到。

我做错了什么?

5个回答

15

首先,如果您想从外部绑定DependencyProperty TextInControl,则需要在UserControl1内声明它。

将DP的声明移动到UserControl1内部。

public partial class UserControl1 : UserControl
{
    public UserControl1()
    {
        InitializeComponent();
    }

    public string TextInControl
    {
        get { return (string)GetValue(TextInControlProperty); }
        set { SetValue(TextInControlProperty, value); }
    }

    public static readonly DependencyProperty TextInControlProperty =
        DependencyProperty.Register("TextInControl", typeof(string), 
                                       typeof(UserControl1));
}

其次,您已将UserControl的外部DataContext设置为UserControl1VM

    public UserControl1()
    {
        InitializeComponent();
        DataContext = new UserControl1VM(); <-- HERE (Remove this)
    }

因此,WPF绑定引擎在UserControl1VM中寻找Text属性,而不是MainWindowVM。移除设置DataContext并将UserControl1的XAML更新为以下内容:

<UserControl x:Class="DPandMVVM.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300"
             x:Name="userControl1">
    <Grid>
        <TextBlock Text="{Binding TextInTextBlock, ElementName=userControl1}" />  
    </Grid>
</UserControl>

通过在UserControl上设置x:Name,使用ElementName绑定DP。


更新

如果您想保留UserControlViewModel,则必须在MainWindow中更新绑定。在绑定中使用ElementName明确告诉WPF绑定引擎在MainWindow的DataContext中查找属性,如下所示:

<local:UserControl1 TextInControl="{Binding DataContext.Text,
                    ElementName=mainWindow}" />

为此,您需要在窗口根级别上设置x:Name="mainWindow"


使用这种方法后,我的ViewModel不再工作。但我想使用这种模式并在其中使用一些其他属性(而不是DP)。 - joerg
1
视图模型本来就不应该包含依赖属性。它们只应该包含普通的CLR属性。 - Rohit Vats
3
把UserControl想象成为其他人的“黑匣子”,它只是一个其他用户也会使用的控件。因此,我们有一个开发者负责控件(使用mvvm),和一个开发者负责MainWindow(使用mvvm)。MainWindow的开发者只想“轻松”地使用UserControl,而不需要了解其内部细节。他只想将自己的VM属性绑定到UserControl的DP上。 - joerg
2
我该如何从VM中访问UserControl内的这些DP? - joerg
2
你不能从VM中访问UserControl内部的DP。你需要绑定这些DP。例如,Text是在TextBox中声明的DP,你可以在XAML中将其绑定到VM中的某个属性。自定义DP也是如此处理的。 - Rohit Vats
显示剩余11条评论

1
我有一个方法,我相信它更简单,可能更符合MVVM的原则。
在主窗口XAML中:
<myNameSpace:myUserControl DataContext="{Binding Status}"/>

在您的主视图模型中(即主窗口的数据上下文):

public myUserControlViewModel Status { set; get; }

现在你可以在构造函数中(或在任何想要实例化它的地方)使用它:
Status = new myUserControlViewModel();

然后,如果您想设置文本属性:
Status.Text = "foo";

确保您已将绑定设置为名为myUserControlViewModel类内的Text属性:

<TextBox Text="{Binding Text}"/>

并确保属性触发了PropertyChanged事件。

此外,如果您使用Resharper,您可以在XAML中创建UserControl的Design实例,以便它可以链接绑定,并通过执行以下操作而不告诉您该属性从未被使用:

<UserControl x:Class="myNameSpace.myUserControl"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:myNameSpace="clr-namespace:myNameSpace"
         d:DataContext="{d:DesignInstance myNameSpace.myUserControl}"
         mc:Ignorable="d" ...>

这部分内容为:

xmlns:myNameSpace="clr-namespace:myNameSpace"
d:DataContext="{d:DesignInstance myNameSpace.myUserControl}"

1
这是我如何使用MVVM和DP绑定创建用户控件的方式。它与Rohit的答案类似,但有一些微小的变化。基本上,您需要将控件的内部视图模型设置为UserControl中根容器的DataContext,而不是UserControl本身,这样它就不会干扰DP绑定。
例如:
用户控件XAML
<UserControl x:Class="DPandMVVM.UserControl1"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300"
         x:Name="userControl1">
<Grid x:Name="Root">
    <TextBlock Text="{Binding TextFromVM}" />  
</Grid>

用户控件代码后台。
public partial class UserControl1 : UserControl
{
    public UserControl1()
    {
        InitializeComponent();            
        this.ViewModel = new UserControlVM();
    }

    public UserControlVM ViewModel
    {
        get { return this.Root.DataContext as UserControlVM ; }
        set { this.Root.DataContext = value; }
    }

    public string TextFromBinding
    {
        get { return (string)GetValue(TextFromBindingProperty); }
        set { SetValue(TextFromBindingProperty, value); }
    }

    public static readonly DependencyProperty TextFromBindingProperty =
        DependencyProperty.Register("TextFromBinding", typeof(string), typeof(UserControl1), new FrameworkPropertyMetadata(null, OnTextBindingChanged));

    private static void OnTextBindingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var uc = d as UserControl1;
        uc.ViewModel.TextFromVM = e.NewValue as string;
    }
}

这意味着该控件从根元素DataContext中获取其值,而根元素DataContext是我们的ViewModel,但是ViewModel可以通过从控件外部的DP绑定进行更新(在您的情况下,是与父窗口的ViewModel进行绑定,请参见下面的内容) 窗口XAML
<Window  x:Class="DPandMVVM.Window1"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:local="clr-namespace:DPandMVVM"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300"
         x:Name="window1">
<Grid x:Name="Root">
    <local:userControl1 TextFromBinding="{Binding TextFromWindowVM}" />  
</Grid>


1
如果可重用的控件过多地了解其他虚拟机,那么它就失去了其目的。 - user585968

1
您的控件的XAML现在通过DataContext引用属性,而DataContext又“指向”您主窗口的视图模型。引用控件的数据即可完成(顺便说一下,不要为此设置DataContext-绑定将不再起作用):
<UserControl x:Class="DPandMVVM.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300"
             x:Name="self">
    <Grid>
        <TextBlock Text="{Binding TextInTextBlock, ElementName=self}" />  
   </Grid>
</UserControl>

0

这里有一个可能适用于您的工作解决方案。然而,我在上面的评论中指出,这将在代码中起作用,但可能(像我的情况一样)会在设计师中显示为错误(对象未找到):

<local:UserControl1 TextInControl="{Binding DataContext.Text,
                Source={x:Reference <<Your control that contains the DataContext here>>}}" />

我更希望有一个更干净的解决方案,没有任何设计错误。我希望找到正确绑定依赖属性在用户控件中的值来自于所包含窗口。我发现,无论我尝试做什么(除了我上面展示的),如使用ElementName和/或AncestorType/Level等,调试器都会抱怨它找不到源,并显示它正在寻找源于用户控件的上下文内!就像做绑定逻辑时我不能跳出用户控件上下文(除了那个“破坏设计”的解决方案)。
更新: 我注意到这可能对你不起作用,因为你的情况可能会迫使我刚刚注意到的问题,如果我将自己的源更改为引用窗口而不是具有数据上下文的控件。如果我引用窗口,那么我最终会得到一个循环冗余。也许你会想出一种使用Source版本的绑定方法,这样对你来说就可以正常工作。
我还必须补充说,我的情况可能会更加复杂,因为我的用户控件在弹出窗口的上下文中使用。

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