绑定UserControl依赖属性和MVVM

时间:2014-03-07 10:32:36

标签: wpf mvvm dependency-properties

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

这是一些代码(viewmodels使用某种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>

CodeBehind MainWindow.xaml.cs:

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

MainWindow-ViewModel 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));
    }
}

使用此星座,无法在MainWindow.xaml中找到DP。

我做错了什么?

5 个答案:

答案 0 :(得分:9)

首先如果你想从外面绑定它,你想要在TextInControl内声明DependencyProperty 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绑定引擎在Text而不是UserControl1VM中寻找属性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上设置ElementName,使用x:Name绑定DP。


<强>更新

如果您希望ViewModel保持UserControl完整,则必须更新MainWindow中的绑定。显式地告诉WPF绑定引擎在MainWindow的DataContext中使用ElementName在绑定中查找属性,如下所示:

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

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

答案 1 :(得分:1)

您的控件的XAML现在通过DataContext引用属性TextInTextBlock,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>

答案 2 :(得分: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}"

答案 3 :(得分:0)

这就是我使用MVVM和DP绑定进行UserControls的方法。它与Rohit的答案类似,但有一些细微的变化。基本上,您需要将Control的内部视图模型设置为UserControl中根容器的DataContext而不是UserControl本身,这样就不会干扰DP绑定。

E.g。

UserControl 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>

UserControl代码隐藏

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;
    }
}

这意味着控件从Root元素DataContext(它是我们的ViewModel)派生它的值,但ViewModel可以通过控件外部的DP绑定进行更新(在您的情况下绑定到父Window的ViewModel,见下文)< / p>

Window 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>

答案 4 :(得分:0)

这是一个可行的解决方案。但是,我在上面的评论中指出,这将在代码中起作用,并且可能(就像我的情况)将在设计器中显示为错误(Object Not Found):

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

我宁愿有一个更清洁的解决方案,但没有任何设计师错误。我希望了解如何将用户控件中的依赖项属性正确绑定到来自其所包含的窗口的值。我发现的是我尝试做的任何事情(不论我在上面展示的内容),例如使用ElementName和/或AncestorType / Level等,调试器抱怨它无法找到源并显示它正在用户控件的上下文中查找源代码!这就像我在使用该控件进行绑定逻辑时不能打破用户控制上下文(除了上面的“设计师破解”解决方案)。

更新: 我注意到这可能不适合你,因为你的情况可能会强迫我注意到如果我改变自己的源来引用窗口而不是具有数据上下文的控件的问题。如果我引用窗口,那么我最终会得到循环冗余。也许你会找到一种方法来使用适用于你的绑定的Source版本。

我还必须补充一点,因为我的用户控件是在弹出窗口的上下文中使用的,所以我的情况可能有点复杂。