19

I have a MainWindow containing a UserControl, both implemented in MVVM-pattern. The MainWindowVM has properties that I want to bind to properties in the 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):

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

And here the 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:

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:

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.

What am I doing wrong?

joerg
  • 717
  • 2
  • 8
  • 18

5 Answers5

15

First of all you want DependencyProperty TextInControl to be declared inside UserControl1 if you want to bind it from outside.

Move the declaration of DP inside of 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));
}

Second you have externally set DataContext of UserControl to UserControl1VM,

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

So WPF binding engine looking for property Text in UserControl1VM instead of MainWindowVM. Remove setting DataContext and update XAML of UserControl1 to this:

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


UPDATE

In case you want to have ViewModel intact for UserControl, you have to update binding in MainWindow. Explicitly tell WPF binding engine to look for property in MainWindow's DataContext using ElementName in binding like this:

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

For this you need to set x:Name="mainWindow" on window root level.

Rohit Vats
  • 79,502
  • 12
  • 161
  • 185
  • With this approach I don't have a working ViewModel anymore. But I would like to have this pattern and use some other properties (not DP's) in there, too. – joerg Mar 07 '14 at 10:50
  • 1
    Viewmodels anyhow are not supposed to contain DP's. They should contain normal CLR properties only. – Rohit Vats Mar 07 '14 at 10:51
  • But if you insist to have DataContext set, you have to update MainWindow.xaml code. Let me update an answer for you. – Rohit Vats Mar 07 '14 at 10:51
  • _Viewmodels anyhow are not supposed to contain DP's. They should contain normal CLR properties only._ But how can I have these properties bindable by the control that uses this control? How can I bind to the TextInTextBlock-Property of the UserControl-VM from the MainWindow.xaml? – joerg Mar 07 '14 at 10:57
  • Target properties should be DP and not source properties. `VM is supposed to contain only source properties whereas Target properties are defined at View level` like `TextInControl` which is supposed to be defined at UserControl level. And to bind from MainWindow.xaml i have updated the answer, it should work now. I would suggest to read more of WPF binding overview [here](http://msdn.microsoft.com/en-us/library/ms752347(v=vs.110).aspx). – Rohit Vats Mar 07 '14 at 11:03
  • 3
    Imagine the UserControl as "Black Box" for others. It's just a control that other users will use, too. So we have a developer for the control (that works in mvvm) and a developer for the MainWindow (that works in mvvm). The developer of the MainWindow just want to use the UserControl "easily" and without knowing details of the internals. He just wants to bind his VM-Properties to the DP of the UserControl. – joerg Mar 07 '14 at 11:03
  • `How can I bind to the TextInTextBlock-Property of the UserControl-VM from the MainWindow.xaml?` - TextInTextBlock like I mentioned in first point should be declared as DP inside UserControl and not in its VM. – Rohit Vats Mar 07 '14 at 11:04
  • 2
    And how do I access these DP's inside the UserControl from the VM? – joerg Mar 07 '14 at 11:06
  • 2
    You don't access DP's inside UserControl from VM. You bind with those DP's. Like `Text` is DP declared inside `TextBox` to which you bind to some property in your VM from XAML. That's how you do for custom DP's as well. – Rohit Vats Mar 07 '14 at 11:08
  • Yes, I have tried it but I am still struggeling. Sorry for not understanding the binding mechanism in its completeness. I do still not understand how to access the UC-DPs from the UC-VM. You say `You don't access DP's inside UserControl from VM`, but what if it is a more complex UC implemented in MVVM and I need to access the DPs from the VM? – joerg Mar 07 '14 at 11:29
  • You need to define more complex scenario. Question posted here is not complex. May be raise separate question describing in detail. Can you let me know where are you struggling in the posted question? – Rohit Vats Mar 07 '14 at 11:31
  • I have setup another question [here](http://stackoverflow.com/questions/22249388/how-to-setup-usercontrol) trying to describe the scenario better – joerg Mar 07 '14 at 11:54
  • Ok great. Still `Can you let me know where are you struggling in the posted question with the answer i provided?` Isn't it working for small sample you have posted here? – Rohit Vats Mar 07 '14 at 11:55
  • I will try: the main problem is that I don't see a way to edit the DP `TextInControl` in the UC from the UC-VM. – joerg Mar 07 '14 at 11:59
  • This problem is still not solved, did you have a look at my other question or my last comment here? Would be great if you could help me! Thanks! – joerg Mar 10 '14 at 13:13
  • I actually have the same nightmare issue going on. I do have a workaround that I'm not thrilled with because the designer sees an error even though the code runs fine. I have a dependency property defined in the user control. I use the user control in a main window. I try to bind a property from the data context of the main window to the dependency property of the control and (other than using {Binding source...} which gives me my designer error), the debug output shows the binding is failing because it keeps trying to find the source, not from the main window, but rather the uc datacontext. – Prethen Apr 24 '15 at 15:59
1

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. 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):

<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>
gomi42
  • 2,449
  • 1
  • 14
  • 9
1

This is how I do UserControls with MVVM and DP binding. It's similar to Rohit's answer but with some slight changes. 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.

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 Code-behind

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)

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>

  • 1
    Sort of defeats the purpose of a re-usable control if it knows too much about other VMs –  Aug 02 '16 at 04:35
1

I have a method that I believe is a lot simpler, and probably more true to MVVM.

In the main window 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:

Status.Text = "foo";

and make sure you have the binding setup to a property named Text inside your myUserControlViewModel class:

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

and make sure the property fires PropertyChanged, of-course.

Plus, if you use 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:

<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}"
phillk6751
  • 188
  • 15
0

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:

<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! 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).

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.

I must also add that my situation is probably a bit more complex since my usercontrol is used in the context of a popup.

Prethen
  • 269
  • 1
  • 11