I have to use View First MVVM in a WPF application, and I'm struggling to figure out how it can be made to work elegantly.
The root of the problem is the nested UserControls . In the MVVM architecture, each UserControl must have its own view model assigned to its DataContext , which simplifies binding expressions, as well as how WPF will instantiate any view generated using the DataTemplate .
But if the UserControl child has dependency properties that the parent must associate with its own view model, then the fact that the UserControl child has its own DataContext set to its own view model means that the "implicit path" binding in the parent XAML file child viewmodel instead of parent will be resolved.
To get around this, each parent of each UserControl in the application must either use explicit named bindings for everything by default (which is verbose, ugly and erroneous), or it will need to know if its control has its DataContext set to its own view model or no and uses the appropriate binding syntax (which is equally erroneous and serious violation of basic encapsulation).
After several days of research, I have not seen a single decent solution to this problem. The closest to the solution I came across sets the UserControl's view mode for the internal UserControl element (the topmost Grid or any other), which still leaves you with a problem trying to relate the UserControl properties to its own view model! ( ElementName binding will not work in this case, because the binding will be declared before the named element using the viewmodel assigned to its DataContext ).
I suspect that the reason why not many other people are faced with this is because they either use the first MVVM with a viewmodel that does not have this problem, or use the first MVVM for viewing in combination with the implementation of dependency injection, which improves this problem.
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 own viewmodel. --> <TextBlock Text="{Binding UserControlVmString1}"/> <!-- Has its value set by the UserControl 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 viewmodel. class UserControl6Vm { public string UserControlVmString1 { get { return "UserControl6Vm.String1"; } } } }
This leads to:
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 'UserControl6' (Name = '_ this'); target property - "Text" (type 'String')
because in MainWindow.xaml declaration <local:UserControl6 Text="{Binding MainWindowVmString1}"/> trying to resolve MainWindowVmString1 to UserControl6Vm .
In UserControl6.xaml , commenting on the declaration of the DataContext and the first TextBlock , the code will work, but the UserControl needs a DataContext . In MainWIndow1 using an ElementName instead of an ElementName path binding will also work, but to use the ElementName binding ElementName you will need to know that UserControl sets its view model to its DataContext style (no encapsulation) or smooth the policy of using ElementName bindings everywhere. None of them are attractive.