How to bind custom dependecy property to control's view model?

58 views Asked by At

I need to create a control with few inputs / output with lots of internal functionality. I think the best approach is to create Dependency Properties for interaction with other application parts and have a private view model with hidden functions.

Here is my sample:

Window

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:app="clr-namespace:WpfApplication1">
<StackPanel>
    <DatePicker x:Name="DatePicker" />
    <app:MyControl DateCtrl="{Binding ElementName=DatePicker, Path=SelectedDate}" />
</StackPanel>
</Window>

MyControl

<UserControl x:Class="WpfApplication1.MyControl"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:app="clr-namespace:WpfApplication1">
<UserControl.DataContext>
    <app:ViewModel />
</UserControl.DataContext>
<Grid>
        <TextBlock Text="{Binding DateVM}" />
</Grid>
</UserControl>

Control code-behind

using System;
using System.Windows;

namespace WpfApplication1
{
public partial class MyControl
{
    public MyControl()
    {
        InitializeComponent();
    }

    public static DependencyProperty DateCtrlProperty = DependencyProperty.Register("DateCtrl", typeof(DateTime), typeof(MyControl));
    public DateTime DateCtrl
    {
        get { return (DateTime) GetValue(DateCtrlProperty); }
        set { SetValue(DateCtrlProperty, value); }
    }
}
}

ViewModel

using System;
using System.ComponentModel;

namespace WpfApplication1
{
    public class ViewModel : INotifyPropertyChanged
    {
        private DateTime _dateVM;
        public DateTime DateVM
        {
            get { return _dateVM; }
            set
            {
                _dateVM = value;
                OnPropertyChanged("DateVM");
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string propertyName)
        {
            var handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

I need to achieve is to propagate date chosen in DatePicker down to MyControl's view model. Alternatively, is there any better pattern to use?

2

There are 2 answers

2
Sheridan On BEST ANSWER

What you describe is a common misconception that all views should have a view model. However, it is usually far simpler (and more appropriate) for UserControls that are used as controls to simply use their own DependencyPropertys.

The problem with your method is that you have assigned the UserControl DataContext internally, so it cannot be set from outside the control. The solution is to not set the DataContext internally, but instead to use a RelativeSource Binding to access the UserControl DependencyPropertys like this:

<TextBlock Text="{Binding DateCtrl, RelativeSource={RelativeSource 
    AncestorType={x:Type YourLocalPrefix:MyControl}}}" />

If you really have to use an internal view model, then declare a DependencyProperty of that type and data bind to it in the same way that I showed above:

<TextBlock Text="{Binding YourViewModelProperty.DateVM, RelativeSource={RelativeSource 
    AncestorType={x:Type YourLocalPrefix:MyControl}}}" />
1
James Harcourt On

The DatePicker should have it's own DatePickerViewModel which defines a SelectedDate property or dependency property. You should define the XAML of the DatePicker control to use this dedicated ViewModel.

Then, when you use the control, you can set the binding like so:

<DatePicker SelectedDate="{Binding Path=DateVM, 
            Mode=TwoWay, 
            UpdateSourceTrigger=PropertyChanged}" />

Note that DateVM is not a property of the date picker, but a property on the consuming view model (or in this case, Main Window). When the picker is opened, it will default to the date already set in DateVM.

The other thing is that your picker doesn't allow any picking at the moment - it's just a text block!