简体   繁体   English

绑定UserControl依赖属性和MVVM

[英]Binding UserControl Dependency Property and MVVM

I have a MainWindow containing a UserControl, both implemented in MVVM-pattern. 我有一个包含UserControl的MainWindow,它们都是用MVVM模式实现的。 The MainWindowVM has properties that I want to bind to properties in the UserControl1VM. MainWindowVM具有我想要绑定到UserControl1VM中的属性的属性。 But this doesn't work. 但这不起作用。

Here's some code (the viewmodels use some kind of mvvm-framework that implement the INotifyPropertyChanged in a ViewModelBase-class but that's hopefully no problem): 这里是一些代码(viewmodels使用某种mvvm框架,在ViewModelBase类中实现INotifyPropertyChanged,但希望没问题):

MainWindow.xaml: 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: CodeBehind MainWindow.xaml.cs:

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

MainWindow-ViewModel MainWindowVM.cs: 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";
        }
    }
}

And here the UserControl1.xaml: 这里是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>

The Codebehind UserControl1.xaml.cs: 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();
        }
    }
}

And the Viewmodel UserControl1VM.cs: 和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));
    }
}

With this constellation the DP cannot be found in MainWindow.xaml. 使用此星座,无法在MainWindow.xaml中找到DP。

What am I doing wrong? 我究竟做错了什么?

First of all you want DependencyProperty TextInControl to be declared inside UserControl1 if you want to bind it from outside. 首先 ,如果要从外部绑定它,则需要在UserControl1声明DependencyProperty TextInControl

Move the declaration of DP inside of UserControl1 . UserControl1移动DP的声明。

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

Second you have externally set DataContext of UserControl to UserControl1VM , 其次,你将UserControl1VM DataContext外部设置为UserControl1VM

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

So WPF binding engine looking for property Text in UserControl1VM instead of MainWindowVM . 所以WPF绑定引擎在UserControl1VM寻找属性Text而不是MainWindowVM Remove setting DataContext and update XAML of UserControl1 to this: 删除设置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>

Bind DP using ElementName by setting x:Name on UserControl. 通过在UserControl上设置x:Name ,使用ElementName绑定DP。


UPDATE UPDATE

In case you want to have ViewModel intact for UserControl , you have to update binding in MainWindow. 如果您希望UserControl ViewModel完好无损,则必须更新MainWindow中的绑定。 Explicitly tell WPF binding engine to look for property in MainWindow's DataContext using ElementName in binding like this: 明确告诉WPF绑定引擎在MainWindow的DataContext中使用ElementName查找属性,如下所示:

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

For this you need to set x:Name="mainWindow" on window root level. 为此,您需要在窗口根级别设置x:Name="mainWindow"

The XAML of your control right now reference the property TextInTextBlock via the DataContext which in turn "Points" to your main window's view model. 您的控件的XAML现在通过DataContext引用属性TextInTextBlock ,DataContext又“点”到主窗口的视图模型。 Reference the data of the control and you are done (btw do not set the DataContext for that reason - the binding won't work any more): 引用控件的数据,你就完成了(顺便说一下,因为这个原因,不会设置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>

I have a method that I believe is a lot simpler, and probably more true to MVVM. 我有一种方法,我相信它更简单,并且可能更适用于MVVM。

In the main window XAML: 在主窗口XAML中:

<myNameSpace:myUserControl DataContext="{Binding Status}"/>

In your main view model (the data context of the main window: 在主视图模型中(主窗口的数据上下文:

public myUserControlViewModel Status { set; get; }

now you can in the constructor (or whenever you want to instantiate it): 现在你可以在构造函数中(或者只要你想实例化它):

Status = new myUserControlViewModel();

then if you want to set the text property: 那么如果你想设置text属性:

Status.Text = "foo";

and make sure you have the binding setup to a property named Text inside your myUserControlViewModel class: 并确保您在myUserControlViewModel类中具有名为Text的属性的绑定设置:

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

and make sure the property fires PropertyChanged, of-course. 当然,请确保该属性触发PropertyChanged。

Plus, if you use Resharper. 另外,如果你使用Resharper。 You can create a Design instance of the UserControl in your XAML so that it can link the bindings and not tell you that the property is never used by doing this: 您可以在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" ...>

This part: 这部分:

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

This is how I do UserControls with MVVM and DP binding. 这就是我用MVVM和DP绑定做UserControls的方法。 It's similar to Rohit's answer but with some slight changes. 它与Rohit的答案类似,但有一些细微的变化。 Basically you need to set the Control's internal view model to be the DataContext of the root container within the UserControl rather than the UserControl itself, that way it will not interfere with DP bindings. 基本上,您需要将Control的内部视图模型设置为UserControl中根容器的DataContext而不是UserControl本身,这样就不会干扰DP绑定。

Eg 例如

UserControl XAML 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 Code-behind 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;
    }
}

This means that the control derives it's values from the Root element DataContext which is our ViewModel but the ViewModel can be updated via a DP binding from outside the control (in your case a binding to the parent Window's ViewModel, see below) 这意味着控件从Root元素DataContext派生它的值,这是我们的ViewModel,但ViewModel可以通过控件外部的DP绑定进行更新(在您的情况下绑定到父Window的ViewModel,见下文)

Window XAML 窗口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>

Here's a possible working solution for you. 这是一个可行的解决方案。 However, I've noted in a comment above that this will work in code and perhaps (like my situation) will show up as an error (Object Not Found) in the designer: 但是,我在上面的评论中已经注意到这将在代码中起作用,并且可能(就像我的情况)将在设计器中显示为错误(Object Not Found):

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

I'd rather to have a cleaner solution, though, without any designer errors. 不过,我宁愿有一个更清洁的解决方案,没有任何设计师错误。 I wish to find out how to properly bind a dependency property in a user control to a value coming from the window it's contained in. What I'm finding is that whatever I try to do (short of what I showed above), such as using ElementName and/or AncestorType/Level, etc., the debugger complains that it can't find the source and shows that it's looking for the source inside the context of the user control! 我希望了解如何将用户控件中的依赖项属性正确绑定到来自其所包含的窗口的值。我发现的是我尝试做的任何事情(不论我在上面展示的内容),例如使用ElementName和/或AncestorType / Level等,调试器抱怨它无法找到源并显示它正在用户控件的上下文中查找源代码! It's like I can't break out of the user control context when doing Binding logic in the use of that control (other than that "designer-breaking" solution above). 这就像我在使用该控件(除了上面的“设计师破解”解决方案)之外的Binding逻辑时,我不能打破用户控制上下文。

UPDATE: I noticed that this might not work for you as your situation might force a problem I just noticed if I change my own source to reference the window instead of a control that has the data context. 更新:我注意到这可能对你不起作用,因为你的情况可能会强迫我注意到如果我改变自己的源来引用窗口而不是具有数据上下文的控件的问题。 If I reference the window then I end up with a cyclical redundancy. 如果我引用窗口,那么我最终会得到循环冗余。 Perhaps you'll figure out a way to use the Source version of the binding that will work okay for you. 也许你会找到一种方法来使用可以正常使用的绑定的Source版本。

I must also add that my situation is probably a bit more complex since my usercontrol is used in the context of a popup. 我还必须补充一点,因为我的用户控件是在弹出窗口的上下文中使用的,所以我的情况可能有点复杂。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM