Search code examples
c#wpfuser-controls

How to set DataBinding for UserControl with ViewModel


I have a usercontrol with couple of controls inside. So I decide to use ViewModel to do managing for all those bindable value. But I find my binding is always null. So how to setup binding for ViewModel in usercontrol

MainWindows.xaml

<Window x:Class="Test.MainWindow"
        Title="MainWindow" Height="450" Width="800">
    <StackPanel>
        <cus:Wizard WizardModel="{Binding MyModel}"/>
    </StackPanel>
</Window>

MainWindows.xaml.cs

public partial class MainWindow : Window
{
   private ViewModel vm = new ViewModel();
   public MainWindow()
   {
        InitializeComponent();
        DataContext = vm;
   }
}

ViewModel.cs(MainWindow viewmodel)

    public class ViewModel : INotifyPropertyChanged
    {
        private Model _MyModel;
        public Model MyModel
        {
            get
            {
                return _MyModel;
            }
            set
            {
                _MyModel = value;
                NotifyPropertyChanged("MyModel");
            }
        }
    }

Wizard.xaml(my UserControl)

<UserControl mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Something}" />
    </Grid>
</UserControl>

Wizard.xaml.cs

public partial class Wizard : UserControl
{
    private readonly object modelLock = new object();
    private Model CurrentModel = new Model();
    public Wizard()
    {
        InitializeComponent();
        DataContext = CurrentModel;
    }     
    public Model WizardModel
    {
        get { return (Model)this.GetValue(WizardModelProperty); }
        set { this.SetValue(WizardModelProperty, value); }
    }
    public static readonly DependencyProperty WizardModelProperty = DependencyProperty.Register("WizardModel", typeof(Model), typeof(Wizard), new PropertyMetadata(null, new PropertyChangedCallback(ModelChanged)));
    private static void ModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((Wizard)d).OnModelChanged();
    }
    private void OnModelChanged()
    {
        lock (this.modelLock)
        {
            if(CurrentModel != null)
            {
                CurrentModel = null;
            }
            if (WizardModel != null)
            {
                CurrentModel = WizardModel;                    
            }
        }
    }
}

The WizardModel in UserControl is always null. So how to setup this ViewModel in UserControl


Solution

  • A UserControl that is supposed to operate on a particular view model class - or more precisely on a class with a particular set of public properties - may directly bind to the view model properties in its XAML.

    Given a view model like

    public class Model
    {
        public string Something { get; set; }
    }
    

    you may write a UserControl with nothing more than this XAML

    <UserControl ...>
        ...
        <TextBox Text="{Binding Something}" />
        ...
    </UserControl>
    

    and this code behind

    public partial class Wizard : UserControl
    {
        public Wizard()
        {
            InitializeComponent();
        }  
    }
    

    If you now set its DataContext to an instance of Model (or any other class with a Something property), it will just work:

    <local:Wizard DataContext="{Binding MyModel}"/>
    

    Since the value of the DataContext property is inherited from parent to child elements, this will also work:

    <StackPanel DataContext="{Binding MyModel}">
        <local:Wizard/>
    </StackPanel>
    

    However, the UserControl still dependends on the existence of a Something property in its DataContext. In order to get rid of this dependence, your control may expose a dependency property

    public static readonly DependencyProperty MyTextProperty =
        DependencyProperty.Register(nameof(MyText), typeof(string), typeof(Wizard));
    
    public string MyText
    {
        get { return (string)GetValue(MyTextProperty); }
        set { SetValue(MyTextProperty, value); }
    }
    

    and bind the element in its XAML to its own property

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

    Now you would bind the control's property instead of setting its DataContext:

    <local:Wizard MyText="{Binding MyModel.Something, Mode=TwoWay}"/>