UserControls and viewmodels in View-First-MVVM
An immediate solution is to use a RelativeSource
and set it to look for the DataContext
of a parent UserControl
:
<UserControl>
<UserControl.DataContext>
<local:ParentViewModel />
</UserControl.DataContext>
<Grid>
<local:ChildControl MyProperty="{Binding DataContext.PropertyInParentDataContext, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"/>
</Grid>
</UserControl>
You could also treat the child viewmodels as properties of the parent viewmodel, and propagate it from the parent. That way, the parent viewmodel is aware of the children so it can update their properties. The child viewmodels also may have a "Parent"
property which holds a reference to the parent, injected by the parent itelf upon their creation, which may grant direct access to the parent.
public class ParentViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged values
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
private ChildViewModel childViewModel;
public ChildViewModel ChildViewModel
{
get { return this.childViewModel; }
set
{
if (this.childViewModel != value)
{
this.childViewModel = value;
this.OnPropertyChanged("ChildViewModel");
}
}
}
}
<UserControl>
<UserControl.DataContext>
<local:ParentViewModel />
</UserControl.DataContext>
<Grid>
<local:ChildControl DataContext="{Binding ChildViewModel}"
MyProperty1="{Binding PropertyInTheChildControlledByParent}"
MyProperty2="{Binding Parent.PropertyWithDirectAccess}"/>
</Grid>
</UserControl>
EDIT
Another approach and more complex would be making the parent's DataContext
available to the child UserControl
using an attached property. I have not fully implemented it, but it would consist in an attached property to request the feature (something like "HasAccessToParentDT"
), in which DependencyPropertyChanged
event you would hook up the Load and Unload
events of the ChildUserControl
, access the Parent
property (available if the control is loaded) and bind its DataContext
to a second attached property, "ParentDataContext"
, which could then be used in xaml.
<local:ChildControl BindingHelper.AccessParentDataContext="True"
MyProperty="{Binding BindingHelper.ParentDataContext.TargetProperty}" />
Neutrino
Updated on June 16, 2022Comments
-
Neutrino almost 2 years
I'm forced to use View First MVVM in a WPF application and I'm struggling to see how it can be made to work elegantly.
The root of the problem lies with nested
UserControls
. In an MVVM architecture eachUserControl
needs to have its view model assigned to itsDataContext
, this keeps the binding expressions simple, and what's more this is also the way WPF will instantiate any view generated via aDataTemplate
.But if a child
UserControl
has dependency properties which the parent needs to bind to its own viewmodel then the fact that the childUserControl
has itsDataContext
set to its own viewmodel means that an 'implicit path' binding in the parent XAML file will resolve to the child's viewmodel instead of the parent's.To work around this every parent of every
UserControl
in the application will either need to use explicit named bindings for everything by default (which is verbose, ugly and errorprone), or it will have to know whether a specific control has itsDataContext
set to its own viewmodel or not and use the appropriate binding syntax, (which is equally errorprone, and a major violation of basic encapsulation).After days of research I haven't come across a single half decent solution to this issue. The closest thing to a solution I've come across is setting the
UserControl's
viewmodel to an inner element of theUserControl
(the topmostGrid
or whatever), which still leaves you facing a problem trying to bind properties of theUserControl
itself to its own viewmodel! (ElementName
binding won't work in this case because the binding would be declared before the named element with the viewmodel assigned to itsDataContext
).I suspect that the reason not many other people run into this it that they are either using viewmodel first MVVM which doesn't have this issue, or they are using view first MVVM in conjunction with a dependency injection implementation that amelliorates this issue.
Does anyone have a clean solution for this please?
UPDATE:
Sample code as requested.
<!-- MainWindow.xaml --> <Window x:Class="UiInteraction.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:UiInteraction" Title="MainWindow" Height="350" Width="525" x:Name="_this"> <Window.DataContext> <local:MainWindowVm/> </Window.DataContext> <StackPanel> <local:UserControl6 Text="{Binding MainWindowVmString1}"/> </StackPanel> </Window>
namespace UiInteraction { // MainWindow viewmodel. class MainWindowVm { public string MainWindowVmString1 { get { return "MainWindowVm.String1"; } } } }
<!-- UserControl6.xaml --> <UserControl x:Class="UiInteraction.UserControl6" x:Name="_this" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:UiInteraction"> <UserControl.DataContext> <local:UserControl6Vm/> </UserControl.DataContext> <StackPanel> <!-- Is bound to this UserControl's own viewmodel. --> <TextBlock Text="{Binding UserControlVmString1}"/> <!-- Has its value set by the UserControl's parent via dependency property. --> <TextBlock Text="{Binding Text, ElementName=_this}"/> </StackPanel> </UserControl>
namespace UiInteraction { using System.Windows; using System.Windows.Controls; // UserControl code behind declares DependencyProperty for parent to bind to. public partial class UserControl6 : UserControl { public UserControl6() { InitializeComponent(); } public static readonly DependencyProperty TextProperty = DependencyProperty.Register( "Text", typeof(string), typeof(UserControl6)); public string Text { get { return (string)GetValue(TextProperty); } set { SetValue(TextProperty, value); } } } }
namespace UiInteraction { // UserControl's viewmodel. class UserControl6Vm { public string UserControlVmString1 { get { return "UserControl6Vm.String1"; } } } }
This results in:
System.Windows.Data Error: 40 : BindingExpression path error: 'MainWindowVmString1' property not found on 'object' ''UserControl6Vm' (HashCode=44204140)'. BindingExpression:Path=MainWindowVmString1; DataItem='UserControl6Vm' (HashCode=44204140); target element is 'UserControl6' (Name='_this'); target property is 'Text' (type 'String')
because in
MainWindow.xaml
the declaration<local:UserControl6 Text="{Binding MainWindowVmString1}"/>
is attempting to resolveMainWindowVmString1
onUserControl6Vm
.In
UserControl6.xaml
commenting out the declaration of theDataContext
and the firstTextBlock
the code will work, but theUserControl
needs aDataContext
. InMainWIndow1
using anElementName
instead of an implict path binding will also work, but in order to use theElementName
binding syntax you would either have to know that theUserControl
assigns its viewmodel to itsDataContext
(encapsulation failure) or altenatively adopt a policy of usingElementName
bindings everywhere. Neither of which is appealing. -
Neutrino over 11 yearsThe first approach is an example of exactly what I'm trying to avoid. I want my bindings to look like MyProperty="{Binding PropertyInParentDataContext}" instead of MyProperty="{Binding DataContext.PropertyInParentDataContext, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}". This isn't just a stylistic consideration because errors in XAML are a nightmare to track down.
-
Neutrino over 11 yearsThe problem with the second approach is that if the parent wants to bind to any of the ChildControl's dependency properties in addition to its DataContext then that also requires a convoluted binding expression because if one of the assigned bindings is the ChildControl's DataContext then that affects the implicit binding source for all the other binding expressions.
-
Neutrino over 8 yearsI don't like the idea of referencing a parent's viewmodel. While I agree in some specific situations this may be logical I wouldn't consider it a generic solution, especially when using view first MVVM where it is the view's dependency properties that primarily form the interface between the various view elements rather than a direct reference between their view models.
-
Neutrino over 8 yearsI agree that using RelativeSource in the manner proposed in Arthur Nune's answer is a better approach. On the one hand it doesn't quite answer the question because it can't be considered syntactically elegant, but on the other hand it is a localised generic solution. But how is using RelativeSource better than x:Naming the parent UI element and using an ElementName binding though? E.g. Text="{Binding DataContext.Text, ElementName=_this}"
-
eMko over 8 yearsReferencing parent viewmodel is surely not generic solution - I it here only as an example of a different approach (although you can also use a dependency injection in some cases, but it still doesn't solve the problem).
-
eMko over 8 yearsThe RelativeSource can be better because it doesn't tightly couple one element to other which IMHO is better, however YMMV.