Home > Software engineering >  Handling properties of custom controls in MVVM (WPF)
Handling properties of custom controls in MVVM (WPF)

Time:06-09

I have a custom control which will have properties that can be set which will affect the logic of how the control is handled. How should this be handled in MVVM?

Currently I'm stuck trying to pass a DependencyProperty to the ViewModel.

Example code:

CustomControl.xaml

<UserControl x:Name="Root" ...>
    <UserControl.DataContext>
        <local:CustomControlViewModel SetDefaultValue="{Binding ElementName=Root, Path=SetDefaultValue, Mode=TwoWay}"/>
    </UserControl.DataContext>
...
</UserControl>

CustomControl.xaml.cs

...
public static readonly DependencyProperty SetDefaultValueProperty = DependencyProperty
        .Register("SetDefaultValue",
                typeof(bool),
                typeof(CustomControl),
                new FrameworkPropertyMetadata(false));

public string SetDefaultValue
{
    get { return (string)GetValue(SetDefaultValueProperty ); }
    set { SetValue(SetDefaultValueProperty , value); }
}
...

CustomControlViewModel.cs

...
private bool setDefaultValue;
public bool SetDefaultValue
{
    get { return setDefaultValue; }
    set
    {
        if (setDefaultValue!= value)
        {
            setDefaultValue= value;
            OnPropertyChanged("SetDefaultValue"); // INotifyPropertyChanged
        }
    }
}
...

My goal with this property specifically is to be able to set a default value (getting the default value requires running business logic). So in another view I would use this control like this:

<local:CustomControl SetDefaultValue="True"/>

CodePudding user response:

(Before I answer I want to point out that what you have here is actually a user control, not a custom control. That's not nit-picking on my part; A user control is something derived from the UserControl class and it typically has an associated XAML file. A custom control just derives from the Control class and has no associated XAML file. A custom control requires you set to a control template. Custom controls can be styled. User controls cannot.)


The thing about UserControl is that sometimes we create one assuming one specific DataContext, of one type and then we make all of its XAML bind to that object type. This is good for big, main pages of an application that are not meant to be re-used in too many places

But another approach -- that you have started to do here -- is to give our user controls their own dependency properties. So in this case, why not dispense with the need for this control to have any specific DataContext altogether? This is the first step to making user controls truly re-usable in many places.

Unless this control is huge, there's a good chance that When you are laying out its XAML, it can get everything that XAML needs to bind to in just a few properties. So why not make all those properties into dependency properties and make the control's XAML bind to itself?

Make the class be its own DataContext. Set that property on the root UI element of the control's layout and then every Binding should work well.

To illustrate, I've renamed your control class MyUserControl I've renamed your "SetDefaultValue" property to just be "BoolOption" Let's assume that all it needs to show is a checkbox, representing the bool value and a string label on the checkbox. We can do this with just two dependency properties. (In effect, this entire control is now just a pointless, glorified CheckBox control but please ignore that)

MyUserControl.xaml.cs

public static readonly DependencyProperty BoolOptionProperty = 
    DependencyProperty.Register(nameof(BoolOption),
                    typeof(bool),
                    typeof(MyUserControl),
                    new FrameworkPropertyMetadata(false));

public string BoolOption
{
    get { return (string)GetValue(BoolOptionProperty ); }
    set { SetValue(BoolOptionProperty , value); }
}

public static readonly DependencyProperty CheckBoxLabelProperty =
    DependencyProperty.Register(nameof(CheckBoxLabel),
                    typeof(string),
                    typeof(MyUserControl),
                    new FrameworkPropertyMetadata(string.Empty));

public string CheckBoxLabel
{
    get { return (string)GetValue(CheckBoxLabelProperty ); }
    set { SetValue(CheckBoxLabelProperty , value); }
}

// Constructor.  Here we set the control to be its own UI's DataContext
public MyUserControl()
{
    InitializeComponent();

    // Make us be the UI's DataContext. Note that I've set the
    // x:Name property root Grid in XAML to be "RootUiElement"

    RootUiElement.DataContext = this;  
}

MyUserControl.xaml

<UserControl x:Class="MyCompany.MyApp.MyUserControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:MyCompany.MyApp.Controls"
    x:Name="Root"
    d:DesignHeight="450"
    d:DesignWidth="800"
    d:DataContext="{d:DesignInstance {x:Type local:MyUserControl}}"
    mc:Ignorable="d">
    <Grid x:Name="RootUiElement">
        <CheckBox IsChecked="{Binding BoolOption}"
            Content="{Binding CheckBoxLabel"
            />
    </Grid>

</UserControl>

Finally you could use the control anywhere you wanted, no matter what your current DataContext is, like this

<local:MyUserControl BoolOption="True" CheckBoxLabel="Is Option A enabled?"/>

<local:MyUserControl BoolOption="False" CheckBoxLabel="Is Option B?"/>

Or even bind it to some other DataContext where you're using it like this. Suppose my current DataContext is a view-model that has a boolean UserBoolOptionC property

<local:MyUserControl BoolOption="{Binding UseBoolOptionC}" "Is Option C enabled?"/>
  • Related