1

I would like to be able to bind complex model (many properties) to UserControl through DependencyProperty, and if model would be edited in UserControl I would like to see this edited information inside my binded model.

Example application: Model, UserControl (xaml + cs), MainWindow (xaml + cs). I have no ViewModel to simplify idea.

Model:

public class MyModel : INotifyPropertyChanged
{
    private string _surname;
    private string _name;

    public string Name
    {
        get => _name;
        set
        {
            _name = value;
            OnPropertyChanged();
        }
    }

    public string Surname
    {
        get => _surname;
        set
        {
            _surname = value;
            OnPropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

MyModelEditor.xaml (inside Grid):

<DockPanel>
    <TextBox Text="{Binding MyModel.Name}"/>
    <TextBox Text="{Binding MyModel.Surname}"/>
</DockPanel>

Also contains this line in UserControl root element: DataContext="{Binding RelativeSource={RelativeSource Self}}"

MyModelEditor.xaml.cs:

public partial class MyModelEditor : UserControl
{
    public MyModel MyModel
    {
        get => (MyModel)GetValue(MyModelProperty);
        set => SetValue(MyModelProperty, value);
    }

    public static readonly DependencyProperty MyModelProperty =
        DependencyProperty.Register("MyModel", typeof(MyModel), typeof(MyModelEditor), new FrameworkPropertyMetadata(null));

    public MyModelEditor()
    {
        InitializeComponent();
    }
}

MainWindow.xaml (inside Grid):

<DockPanel>
    <Button DockPanel.Dock="Bottom" Content="Press Me!" Click="ButtonBase_OnClick"/>
    <controls:MyModelEditor MyModel="{Binding MyModel}"/>
</DockPanel>

MainWindow.xaml.cs:

public partial class MainWindow : Window, INotifyPropertyChanged
{
    private MyModel _myModel;

    public MyModel MyModel
    {
        get => _myModel;
        set
        {
            _myModel = value;
            OnPropertyChanged();
        }
    }

    public MainWindow()
    {
        InitializeComponent();
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
        MessageBox.Show(MyModel?.Name);
    }
}

My test scenario: type text in textbox, press button.
Current behavior: Message after pressing button is empty.
Expected behavior: Message after pressing button is same like in textbox.

I wold not like to bind to all properties separately, because in future I will have much more then two properties.

Why current approach does not work? How can I achieve my goal?

senfen
  • 877
  • 5
  • 21
  • 1
    Setting `DataContext = this` in the MainWindow constructor, or `DataContext="{Binding RelativeSource={RelativeSource Self}}"` in the MainWindow's XAML breaks any DataContext-based Bindings on properties of the UserControl, like `MyModel="{Binding MyModel}"`. You must never explicitly set the DataContext of a UserControl. – Clemens May 17 '18 at 06:46
  • Ok, together with the accepted answer, link to other topic and that comment I have answer for all my questions. Thank you for discussion. I just saw in many examples that behavior - if it just breaks binding by definition it is enough for me. – senfen May 17 '18 at 07:57

2 Answers2

3

You are apparently not using the UserControl instance as Binding source in your UserControl's XAML. One way to do this would be to set the Binding's RelativeSource:

<TextBox Text="{Binding MyModel.Name,
                RelativeSource={RelativeSource AncestorType=UserControl}}"/>

However, you don't need a new dependency property at all for this purpose. Just bind the UserControl's DataContext to a MyModel instance, like

<controls:MyModelEditor DataContext="{Binding MyModel}"/>

The Bindings in the UserControl's XAML would automatically work with the MyModel object, like this:

<DockPanel>
    <TextBox Text="{Binding Name}"/>
    <TextBox Text="{Binding Surname}"/>
</DockPanel>
Clemens
  • 123,504
  • 12
  • 155
  • 268
  • What is the difference between yours first code and using in UserControl `DataContext="{Binding RelativeSource={RelativeSource Self}}"` to bind control to itself and simple `` to bind to UserControl property? I would like to fully understand my issue. – senfen May 15 '18 at 22:21
  • Without the RelativeSource, the Binding source is the object in the DataContext. The value of the DataContext is inherited from the parent element of the UserControl, e.g. the MainWindow. You should have seen data binding error messages in the Output Window in Visual Studio, that a MyModel property could not be found in the binding source object. – Clemens May 16 '18 at 05:12
  • In `UserControl` XAML root element I have a line `DataContext="{Binding RelativeSource={RelativeSource Self}}"` so DataContext is binded to UserControl itself which have `MyModel` property. There is no errors binding messages in output, if I would have such errors I would not ask this question :) – senfen May 16 '18 at 06:14
  • That is a relevant information that you should have added to your question in the first place. Anyway, it doesn't make sense to have the MyModel property at all. Go with the alternative approach shown here. Remove the property, remove the DataContext assignment from the UserControl's XAML, and just bind the DataContext when you use the control. – Clemens May 16 '18 at 06:37
  • Even better than that would be to make your UserControl complete independent of a particular view model. Just expose a set of dependency properties that you directly bind, like `` – Clemens May 16 '18 at 06:40
  • I added information about that binding to post. In this example maybe it does not make sens to use dependency property but I would like to know WHY this particular example is not working, not what is better approach for EXAMPLE problem. I would like to understand that issue. Also, I added as simple as possible example of problem like stackoverflow rules suggest to do. If I would like to pass 20 properties you will also suggest to create 20 dependency properties? If this is how WPF is designed then ok, but I still would like to know why this is not working. – senfen May 16 '18 at 21:50
  • 1
    Have you ever seen any control in the WPF framework that has its own view model, e.g. a DatePicker with a DatePickerViewModel? No, you haven't. They always only have dependency properties, and a lot of them. DatePicker for example adds 12 dependency properties. – Clemens May 17 '18 at 06:48
1

For both of your TextBox controls, you should define their Binding with a TwoWay mode (ms docs on binding modes). Which, basically, would assure that the data flow is working in both direction (i.e. from the view model into the view and the other way around):

<DockPanel>
    <TextBox Text="{Binding MyModel.Name, Mode=TwoWay}"/>
    <TextBox Text="{Binding MyModel.Surname, Mode=TwoWay}"/>
</DockPanel>

As a good practice, you should always explicitly define what is the mode of the the Binding (NOTE: by default it's OneWay TwoWay - how to know which is the default?).

Another tip would be to go ahead and use MvvmHelpers nuget (github project), which could spare you the time of implementing INotifyPropertyChanged. Besides, you shouldn't re-invent the wheel

EDIT: Fixes are in your GitHub repo

Two things to note here

  1. You have not instantiated your ViewModel (i.e. MyModel), so it was always null
  2. You don't need to create DependencyPropery every time you want to pass some information to your UserControl. You could simply bind the DataContext itself
Mikolaj Kieres
  • 4,137
  • 1
  • 20
  • 23
  • I'm using Fody and MVVM Light Tools for my projects. I wanted to have simple example without external dependencies. Setting `Mode` or/and `UpdateSourceTrigger=PropertyChanged` don't change behavior. – senfen May 15 '18 at 00:15
  • Sorry, I don't have any other ideas. You could create a sample GitHub repo, so I can clone it and run it myself - that would help figuring out what's wrong this code – Mikolaj Kieres May 15 '18 at 00:19
  • I just had a thought. You could put a break point on the setter of your `Name` or `Surname` property. Start typing something in the `TextBox` and check if these breakpoints are being hit. If it does happen and the `Name` is being changed then you might have two different instances of the same model? If not, there's something wrong with `Binding`. – Mikolaj Kieres May 15 '18 at 00:55
  • repository: https://github.com/senfen2/ModelEditor Setter in MainWindow is not hit, Setter in UserControl also, I don't see binding errors in log. Thank you so much for all the help and advice – senfen May 15 '18 at 01:02
  • I create a PR in your GitHub repo and edited my answer to point out what was wrong and what changes I've made – Mikolaj Kieres May 15 '18 at 01:24
  • 2
    As a note, `Mode=TwoWay` is the default for the `TextBox.Text` property. Setting it again obviously makes no difference. – Clemens May 15 '18 at 07:02
  • @Ale_lipa thanks for PR, it is working properly now, but I would like to also understand what should I do to make it work with DependencyProperty. If I instantiate MyModel, bind it to UserControl by DependencyProperty and bind UserControl DataContext to Self it would still not work. Maybe you know why? – senfen May 15 '18 at 22:25
  • Fixing code in OP's repository on GitHub does not make this post an answer. The central point, i.e. the missing Binding source object, is not explained, not even mentioned here. This post is useless for other readers. – Clemens May 16 '18 at 05:02
  • Agreed. I should have provided an example, instead of just pointing out, that OP could use `DataContext` binding rather then creating a `DependencyProperty`. Your answer mentions that already (and more), so I don't see the point in changing mine. – Mikolaj Kieres May 16 '18 at 05:15