WPF User Control hell with MVVM and Dependency Properties

27,239

Solution 1

First off, I don't think MVVM is a good choice if you are developing a UserControl that will be consumed by others. A lookless control is what you really should be developing. Jeremiah Morrill has a blog post about this subject.

With that said, you can set the datacontext with XAML if you have a default public constructor.

Inside ControlView.xaml put:

<UserControl.DataContext>
    <local:ControlViewModel />
</UserControl.DataContext>

Solution 2

You should separate the two use cases:

  1. The (user) control that will be consumed by other developers.
  2. The user control that will be consumed by your application.

Importantly, the latter depends on the former - not vice versa.

Use case 1 would use dependency properties, template bindings, all the things that go into making a regular WPF control:

MyControl.cs:

public class MyControl : Control
{
    // dependency properties and other logic
}

Generic.xaml:

<ControlTemplate Type="local:MyControl">
    <!-- define the default look in here, using template bindings to bind to your d-props -->
</ControlTemplate>

You would then define use case 2 as:

MyViewModel.cs:

public class MyViewModel : ViewModel
{
    // properties and business logic
}

MyView.xaml:

<UserControl ...>
    <local:MyControl SomeProperty="{Binding SomePropertyOnViewModel}" .../>
</UserControl>

Best of both worlds with a clean separation. Other developers depend only on the control, which could (and probably should) be in a completely different assembly than your view model and view.

Solution 3

Basically, instead of binding your UserControl's datacontext to the userControlViewModel, it's better to do it on the first child element of the user control. That way, all the references that you make within the control will be bound to the userControlViewModel, but the dependencies properties can be set from the data context set where you want to use your UserControl.

This is from a project I'm working at:

<UserControl x:Class="Six_Barca_Main_Interface.MyUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:Six_Barca_Main_Interface"
             xmlns:System="clr-namespace:System;assembly=mscorlib" 
             mc:Ignorable="d" 
             d:DesignHeight="900" d:DesignWidth="900">

    <DockPanel  x:Name="rootDock" >
        <TextBlock>{Binding SomethingInMyUserControlViewModel}</TabControl>
    </DockPanel>
</UserControl>

Then on the code behind:

public partial class MyUserControl : UserControl
{
    UserControlViewModel _vm;

    public MyUserControl()
    {
        InitializeComponent();

        //internal viewModel set to the first child of MyUserControl
         rootDock.DataContext = new UserControlViewModel();

        _vm = (UserControlViewModel)rootDock.DataContext;


        //sets control to be able to use the viewmodel elements

     }

     #region Dependency properties 
     public string textSetFromApplication
     {
         get{return (string)GetValue(textSetFromApplicationProperty);}
         set{SetValue(textSetFromApplicationProperty, value);}
     }

     public static readonly DependencyProperty textSetFromApplicationProperty = DependencyProperty.Register("textSetFromApplication", typeof(string), typeof(MyUserControl), new PropertyMetadata(null, OnDependencyPropertyChanged));

     private static void  OnDependencyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
     {
        ((MyUserControl)d)._vm.SomethingInMyUserControlViewModel = 
             e.NewValue as string;
     }
#endregion

Then when you use this on your main view, you can set the dependency property with the value you want to pass to MyUSerControl

Solution 4

A UserControl is part of the "View" in "MVVM" just like the TextBox or ListView controls are part of the View.

Whether you decide to use MVVM to develop your UserControl itself or write it in QBASIC (not recommended) it does not break the MVVM pattern for the consumers of your UserControl so long as they can do every thing they need with your UserControl by binding to DependencyProperty's exposed on your UserControl. i.e. Your UserControl should expose the properties it is dependent upon (hence the name). Once you grasp this DependencyProperty's suddenly make a whole lot of sense and you want their helpful on changed event handlers and default values you specify in their constructor.

If your UserControl is in a different assembly or not I cannot see how that makes a difference.

That said many would advocate you build your UserControl using the MVVM pattern itself for all the good reasons MVVM brings e.g. helping another developer looking at your code. However some things simply are not possible and/or much harder more complex and less performant hacking the XAML to do this - I am not talking about your garden variety Add User Form but for example a UserControl handling the layout of thousands of visuals. Furthermore since you are working in your View you do NOT want your UserControl's ViewModels mixed in with you applications!

Basically I am saying it is well within MVVM not to use MVVM on your View!

Share:
27,239
Jon Mitchell
Author by

Jon Mitchell

Owner and techincal director for HiveIntelligence.com, a UK based company that makes campaign management software for the direct marketing industry.

Updated on February 21, 2020

Comments

  • Jon Mitchell
    Jon Mitchell about 4 years

    This is what I'm trying to do:

    • I'm writing a UserControl that I want to be consumed by other developers.
    • I want end users to be able to use my control using Dependency Properties.

      <lib:ControlView ControlsText={Binding Path=UsersOwnViewModelText} />
      
    • I'm using the MVVM pattern.

    • I'm binding my ViewModels to their View's using <DataTemplates>

      <DataTemplate DataType="{x:Type local:ControlViewModel}">  
          <local:ControlView />  
      </DataTemplate>
      

    So I have two questions:

    1. Am I right in thinking that if a UserControl is being consumed in XAML then the UserControl must set the ViewModel as its DataContext when the control's Loaded event fires instead of using the <DataTemplate> method?

    2. How do I allow users to data bind to my control's dependency properties while still being data bound to my ViewModel?