How to pass a UserControl parameter to the ViewModel using MVVM

Viewed 335

I have a UserControl that receives a parameter form the xaml like this:

  <components:MyComponent Sex="MALE"/>

In the MyComponent control I have a ViewModel binded like this:

<UserControl.DataContext>
    <components:MyComponentViewModel/>
</UserControl.DataContext>

Also in MyComponent code behind looks like this:

 public partial class MyComponent: UserControl
 {
        public string Sex
        {
            get => (string)GetValue(SexParamProperty);
            set { SetValue(SexParamProperty, value); }
        }

        public static readonly DependencyProperty SexParamProperty = DependencyProperty.Register(nameof(Sex), typeof(string), typeof(MyComponent));

        public MyComponent()
        {
            InitializeComponent();
        }
 }

MyComponentViewModel looks like this:

public class MyComponentViewModel: ViewModelBase
{
   public string Sex { get; set; }
}

I want the ViewModel to know what the value of Sex from the UserControl is. Is this agains the MVVM pattern or there is a way to do this respecting MVVM? How can I do this?

1 Answers

In order to pass a value from a control to a view model, a property of the control is usually bound two-way. A dependency property can be declared such that it binds two-way by default:

public string Sex
{
    get => (string)GetValue(SexProperty);
    set => SetValue(SexProperty, value);
}

public static readonly DependencyProperty SexProperty =
    DependencyProperty.Register(
        nameof(Sex), typeof(string), typeof(MyComponent),
        new FrameworkPropertyMetadata(
            null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

Note the convention for a dependency property's identifer field: <PropertyName>Property.


A control - be it a UserControl or any other control - must also not have its own private view model object. It would instead only expose bindable properties that are "internally" used as source properties of Bindings in the control's XAML, e.g.

<UserControl x:Class="MyNamspace.MyComponent" ...>
    <Grid>
        <TextBox
            Text="{Binding Sex,
                   RelativeSource={RelativeSource AncestorType=UserControl}}"/>
    </Grid>
</UserControl>

The control must especially not set its own DataContext, because binding its properties like

<components:MyComponent Sex="{Binding SexInViewModel}"/>

would not work as expected. The source property name is resolved against the current DataContext, which would be the private view model of the control instead of the (expected) view model instance in the inherited DataContext.

It is also worth to mention that a control like this does not have a dependency of a specific view model type (or a set of properties). It hence provides better reusability.

Related