3

I have added a DependencyProperty to my View, binding to the DependencyProperty works, but only if I do not also set the DataContext.

GenericView.xaml

<UserControl x:Class="GenericProject.View.GenericView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <StackPanel>
        <Button Command="{Binding VMFactory.CreateViewModelCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
        <TextBox IsEnabled="False" Text="{Binding SomeProperty, Mode=OneWay}" />
    </StackPanel>
</UserControl>

GenericView.xaml.cs

public partial class GenericView : UserControl
{
    // The DependencyProperty for VMFactory.
    public static readonly DependencyProperty VMFactoryProperty = DependencyProperty.Register("VMFactory", typeof(VMFactoryViewModel<GenericViewModel>), typeof(GenericView));

    public VMFactoryViewModel<GenericViewModel> VMFactory
    {
        get { return (VMFactoryViewModel<GenericViewModel>)GetValue(VMFactoryProperty); }
        set { SetValue(VMFactoryProperty, value); }
    }

    public GenericView()
    {
        InitializeComponent();
    }
}

Here I am creating two views to illustrate the issue at hand. The VMFactory binding in the first view will fail because I have DataContext set. The second view will succeed, what is the cause of this behavior?

MainPage.xaml

<vw:GenericView DataContext="{Binding Generic}" VMFactory="{Binding GenericFactory}" />
<vw:GenericView VMFactory="{Binding GenericFactory}" />
Derrick Moeller
  • 4,808
  • 2
  • 22
  • 48
  • 1
    (Please ignore this ranty comment if it does not apply!) You aren't creating ViewModels to encapsulate the logic of your UserControls, are you? Because no. You do NOT create a ViewModel for your UserControls. UserControls should only contain UI logic, and they should be designed just like any other control. Does a TextBox have a TextBoxViewModel? **NO.** The PITA you are experiencing is because you are trying to implement an antipattern. Stop it. Here's an answer of mine with more details http://stackoverflow.com/a/25796096/1228 –  Dec 17 '14 at 15:46
  • @Will I am not, the UserControl simply needs to provide access to a command that is outside of it's DataContext. – Derrick Moeller Dec 17 '14 at 15:54
  • 1
    ... ... wut? The UserControl shouldn't have a command it's exposing, it should have a public DP of type ICommand that it uses in order to allow other VMs to bind *their* commands to it. You should not have a VMFactoryUserControl and a VMFactoryViewModel. That's the code smell right there. So I'm not clear on what you're doing here, I guess. –  Dec 17 '14 at 16:21
  • @Will The UserControl can be thought of as a simple View, it provides a display for the properties in the DataContext(ViewModel). In my opinion this View is also the ideal place to create the ViewModel and I am attempting to inject the ability to do so in the form of the VMFactoryViewModel. – Derrick Moeller Dec 17 '14 at 16:50
  • 1
    If you start following the actual MVVM pattern instead of creating an anti-pattern that looks similar but actually makes things more complex, then the bindings will start working better. – Murven Dec 17 '14 at 17:14
  • @Murven is right. Your UserControl should expose properties and events on it's surface that *andybody* can bind to and use, just like any other control. Doing it any other way is guaranteed to make you suffer. But, go ahead, keep on hitting yourself in the head with a hammer. It isn't like all of us have done it in the past and have realized our mistakes... –  Dec 17 '14 at 17:44
  • @Will I don't see my implementation as any different then what you're describing? Anbody can bind and use VMFactory? It's not significantly different the setting a header, all I want to do is inject content outside of the native DataContext? – Derrick Moeller Dec 18 '14 at 13:07
  • Could be. Hard to tell from this context. It just smells. But as long as you understand what we're saying, and you're sure that you're not doing it, then great. No problems. –  Dec 18 '14 at 14:12
  • 1
    I'm not sure i agree with this @Will. Composite UI's are built with View Models and User Controls. Microsoft's Patterns & Practices team ship Prism for both WPF and Windows Store apps using User Controls and View Models for modularity. Your "A textbox doesn't have a view model" isn't a fair argument. A Textbox is a Control, not a User Control. There's a difference. – Johnathon Sullinger Jan 06 '15 at 04:25
  • @JohnathonSullinger I don't want to discuss this here, as it will fill up OP's inbox. And if you're interested in arguing the validity of creating ViewModels for your UserControls, I don't want to discuss that anywhere, as IRL it does not work. All you need to do is try it once or twice to realize how it fails miserably. And a UserConrol is an easy way to create a Control, so the analogy is valid. Good day to you, sir. –  Jan 06 '15 at 14:32
  • 1
    @i have done it. Built two enterprise apps using prism & user controls. They run great and haven't had any problems maintaining them. – Johnathon Sullinger Jan 06 '15 at 15:18

3 Answers3

4

This is a fairly common Binding "gotcha"...

In order to access VMFactory, you need to bind your UserControl to itself using...

DataContext="{Binding RelativeSource={RelativeSource Self}}"

You would not then bind DataContext on a GenericView item to anything elsewhere.

However, if you are intending to bind other values to VMFactory external to the UserControl (i.e. <vw:GenericView VMFactory={Binding ...}"/>), you should use RelativeSource with mode FindAncestor for type UserControl.

<!-- Shortened to show pertinent Binding -->
<ctrl:CommandTextBox Command="{Binding VMFactory.CreateViewModelCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}"/>
toadflakz
  • 7,764
  • 1
  • 27
  • 40
  • Your second example looks like my initial attempt which was not working for me, I have tried it again and learned that it does work but only if I do not set both the DataContext and the VMFactory properties of the UserControl. Thoughts? – Derrick Moeller Dec 17 '14 at 13:54
  • DataContext makes sense, but not VMFactory. Have you tried setting just VMFactory? – toadflakz Dec 17 '14 at 13:58
  • That's what I was attempting to convey, it does work if I only set VMFactory. If I also set DataContext then the command is broken. – Derrick Moeller Dec 17 '14 at 14:00
  • You need to remember that DataContext at UserControl level is inherited so once you bind it you can think of your Command binding as being `{Binding UserControl.DataContext.VMFactory.CreateViewModelCommand...}`. Why are trying to bind VMFactory and DataContext? – toadflakz Dec 17 '14 at 14:04
  • I thought I understood inheritance? I was under the impression RelativeSource, ElementName, etc would allow me set my Command to {Binding UserControl.VMFactory.CreateViewModelCommand...} and still use {Binding UserControl.DataContext.SomeProperty} for textboxes and other items. – Derrick Moeller Dec 17 '14 at 14:06
  • I may be wrong on the inheritance, but you should be getting Binding Errors in the Output view in VS - they should tell you exactly why it's not found... From my experience, it doesn't work and I now only use the RelativeSource method without binding DataContext. – toadflakz Dec 17 '14 at 14:22
  • When you have to start writing things like DataContext="{Binding RelativeSource={RelativeSource Self}}" then you know that something fishy is going on with this implementation. – Murven Dec 17 '14 at 17:16
  • I have updated my example, the binding appears to be resolving but the property seems to be null? – Derrick Moeller Dec 17 '14 at 17:50
1

I've got a working solution, it seems as though properties of a control are bound relative to the DataContext of the control?

I was certainly aware items within the control would be bound relative to the DataContext, but I apparently have never used a control in this way before and did not understand that properties of the control would also inherit the scope of the set DataContext. Essentially everything within my View was correct, but the binding to my DependencyProperty was failing.

GenericView.xaml

<!-- Everything in here was correct. -->
<UserControl x:Class="GenericProject.View.GenericView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <StackPanel>
        <Button Command="{Binding VMFactory.CreateViewModelCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
        <TextBox IsEnabled="False" Text="{Binding SomeProperty, Mode=OneWay}" />
    </StackPanel>
</UserControl>

MainPage.xaml

<!-- This is where I messed up earlier, VMFactory requires a relative binding. -->
<vw:GenericView DataContext="{Binding Generic}"
                VMFactory="{Binding DataContext.GenericFactory, RelativeSource={RelativeSource AncestorType={x:Type Page}}}" />
Derrick Moeller
  • 4,808
  • 2
  • 22
  • 48
0

As @toadflakz said, this is a very common issue in WPF and one that took me a while to get my head around when I was learning WPF. Luckily, the solution is simple. Let's say that we have a UserControl that has an object set as its DataContext and another set as the value of a DependencyProperty that is declared within the UserControl... your situation.

From within the UserControl XAML, you can data bind to a property of the object set as the DataContext as normal:

<TextBlock Text="{Binding PropertyFromDataContextObject}" />

If you want to data bind to an object from the object set as the value of the DependencyProperty, you can simply use a RelativeSource Binding:

<TextBlock Text="{Binding PropertyFromDependencyPropertyObject, RelativeSource={
    RelativeSource AncestorType={x:Type YourPrefix:YourUserControl}}}" />

Note that both of these Bindings can be used together in the same UserControl as long as both of the DataContext and DependencyProperty properties have been set.

Sheridan
  • 68,826
  • 24
  • 143
  • 183
  • I understand the binding, but it isn't working as suggested? The DependencyProperty(VMFactory) appears to be null if the DataContext is set? If you look at my MainPage.xaml you can see I have created two instances of the GenericView for troubleshooting, if I add a Loaded eventhandler to the UserControl I can see that the instance with a DataContext binding will have a null VMFactory and will not function as expected. While the instance without a DataContext will have a VMFactory object and the command will execute as expected. Thoughts? – Derrick Moeller Dec 18 '14 at 13:03
  • It should work if you just follow my examples... you've clearly over complicated your code. When accessing properties from the `DataContext` object, you shouldn't be using any `RelativeSource Binding` as that changes the data source away from the `DataContext`. Experiment in a new project. Also, you should have `Binding` errors in the Output Window in Visual Studio that tell you what the problem is. – Sheridan Dec 18 '14 at 13:27
  • I'm not using a RelativeSource to access the DataContext? The TextBox binding accesses the DataContext, the Command binding accesses the VMFactory. The syntax for each is identical to your example? – Derrick Moeller Dec 18 '14 at 13:31