WPF User Control hell with MVVM and Dependency Properties
Asked Answered
M

4

16

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?

Milkweed answered 14/4, 2010 at 13:23 Comment(1)
While I was preparing this question I see it here.Thanks for asking :)Titular
S
10

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>
Sciatica answered 14/4, 2010 at 13:44 Comment(4)
It does look like MVVM isn't ideal for creating user controls. So with that mental hurdle out the way I was able to sort my issue. I had to set the DataContext of the child controls in my UserControl to be the UserControl itself, which means that a consuming control can provide the DataContext for my UserControl and hence bind to my DependencyProperties. (I think that's right...) Anyway, it works so I'm happy!!Milkweed
-1 Assigning the UserControl's viewmodel to the UserControl's DataContext in the XAML will break bindings for parents attempting to use the UserControl's DependencyProperties. Specifically for this binding in the OP <lib:ControlView ControlsText={Binding Path=UsersOwnViewModelText} /> it will cause the binding to refer to the UserControl's viewmodel instead of the parent's. And the only work around is for the parent to somehow know this and use an ElementName binding instead.Mullet
I think MVVM is fine for a usercontrol if it say goes and grabs a list of items from a database and must be reflected in controls in your user control. But I think if you want to expose properties directly to consumers of the control, you would expose properties in code behind that reroute possibly some child control properties casted to the controls related data types.Lamb
As already noted, setting UserControl.DataContext inside the UserControl (however it is done) overrides the context that would otherwise be inherited from where the control is ultimately used, which causes binding problems. If a ViewModel is desired, I usually set the DC within the control on the outermost container (usually the Grid), rather than the UserControl element itself. This allows the DataContext of the control itself to be inherited (no surprises for a user trying to bind to its DPs) and allows a ViewModel to be used as normal for all the elements inside the control.Incoordination
P
27

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.

Patric answered 14/4, 2010 at 13:34 Comment(2)
Thanks for the reply. I'm creating a composite control, think of cascading combo boxes. It seams a little heavy weight to create a control this way for my needs. Thanks for the reply though, as it was still useful to know.Milkweed
Very good pattern,thanks. It's ok, but If your component meaningless without designed ViewModel you need to manage things. e.g. In your ViewModel there are CollectionViewSource's they filter or sort items. Without viewmodel inner variable movement other dep.properties lose meaning. So the answer of this question changes depending on what you intent.Titular
S
10

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>
Sciatica answered 14/4, 2010 at 13:44 Comment(4)
It does look like MVVM isn't ideal for creating user controls. So with that mental hurdle out the way I was able to sort my issue. I had to set the DataContext of the child controls in my UserControl to be the UserControl itself, which means that a consuming control can provide the DataContext for my UserControl and hence bind to my DependencyProperties. (I think that's right...) Anyway, it works so I'm happy!!Milkweed
-1 Assigning the UserControl's viewmodel to the UserControl's DataContext in the XAML will break bindings for parents attempting to use the UserControl's DependencyProperties. Specifically for this binding in the OP <lib:ControlView ControlsText={Binding Path=UsersOwnViewModelText} /> it will cause the binding to refer to the UserControl's viewmodel instead of the parent's. And the only work around is for the parent to somehow know this and use an ElementName binding instead.Mullet
I think MVVM is fine for a usercontrol if it say goes and grabs a list of items from a database and must be reflected in controls in your user control. But I think if you want to expose properties directly to consumers of the control, you would expose properties in code behind that reroute possibly some child control properties casted to the controls related data types.Lamb
As already noted, setting UserControl.DataContext inside the UserControl (however it is done) overrides the context that would otherwise be inherited from where the control is ultimately used, which causes binding problems. If a ViewModel is desired, I usually set the DC within the control on the outermost container (usually the Grid), rather than the UserControl element itself. This allows the DataContext of the control itself to be inherited (no surprises for a user trying to bind to its DPs) and allows a ViewModel to be used as normal for all the elements inside the control.Incoordination
B
2

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

Berry answered 20/2, 2018 at 18:28 Comment(0)
M
1

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!

Midweek answered 10/4, 2014 at 0:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.