How to retrieve a control's viewmodel after creation? - wpf

I have a Window and a UserControl. The UserControl creates its own viewmodel like this:
<UserControl x:Class="UiInteraction.UserControl3"
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:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:UiInteraction"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<local:UserControl3Vm/>
</UserControl.DataContext>
<StackPanel>
<TextBlock Text="{Binding String1}"/>
</StackPanel>
</UserControl>
When the Window instantiates the UserControl I want the viewmodel of the Window to be able to retrieve the viewmodel of the UserControl.
<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">
<Window.DataContext>
<local:MainWindowVm/>
</Window.DataContext>
<StackPanel>
<local:UserControl3 DataContext="{Binding UserControl3Vm, Mode=OneWayToSource}"/>
</StackPanel>
</Window>
The Window's viewmodel has a publicly settable property of type object. With the DataContext binding I'm expecting that once the UserControl3 is created the value of its DataContext (which is a reference to its viewmodel) would be assigned to the UserControl3Vm property on the Window's viewmodel.
What actually happens is that the Window.UserControl3Vm property setter is called with the value null.
Why is this happening, and what is the best way to achieve what I have in mind?
I know it would be easier to instantiate the viewmodel for the UserControl as a property on the viewmodel for the Window and have the UserControl simply bind onto that, (and that would also minimize the coupling of the views to their viewmodels). But where I work they are a bit nutty and prefer view first MVVM instead of viewmodel first, so I'm looking for the most decoupled way to enable viewmodels to collaberate effectively when the viewmodels are instead created by their views.

I don't think it will work to use a OneWayToSource binding without some code-behind.
Initially, your UserControl.DataContext is set to an instance of UserControl3vm, however you are replacing UserControl3vm with a Binding, so your original UserControl3vm is no longer referenced anywhere.
For a OneWayToSource binding to work, you'd have to first set the DataContext to your OneWayToSource binding, and then set the binding's Source to a new instance of UserControl3vm from inside your UserControl.
If I remember correctly, you can obtain the binding using BindingOperations.GetBindingExpression, and update it's DataItem property. You can't simply set the UserControl.DataContext because it would overwrite your OneWayToSource binding.
Personally I would just do it in the code-behind the Loaded event
If they're insisting on View-First MVVM, then the View is controlling the application flow and I don't see any reason why you should keep application logic out of the View's code-behind.
So just set your Window.DataContext.UserControl3Vm property to UserControl3.DataContext in the Loaded event :)
<Window x:Name="MyWindow"
Loaded="MyWindow_Loaded"
... >
<StackPanel>
<local:UserControl3 x:Name="MyUserControl" />
</StackPanel>
</Window>
void MyWindow_Loaded(object sender, EventArgs e)
{
((MainWindowVm)MyWindow.DataContext).UserControl3Vm
= MyUserControl.DataContext;
}

This is possible in XAML using some workaround(to hack access of DataContext of host element). The approach is mentioned here. It uses Freezables.
The XAML is
<Window x:Class="VM2VMBindingInXaml.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vw="clr-namespace:VM2VMBindingInXaml.View"
xmlns:vm="clr-namespace:VM2VMBindingInXaml.ViewModel"
Title="MainWindow" Height="350" Width="525" >
<Window.Resources>
<vm:UserControl1ViewModel x:Key="childVM"></vm:UserControl1ViewModel>
<vm:DataResource x:Key="childVmBinding" BindingTarget="{Binding ElementName=child, Path=DataContext}"/>
</Window.Resources>
<Window.DataContext>
<vm:MainWindowViewModel x:Name="mainViewModel" >
<vm:MainWindowViewModel.ChildViewModel>
<vm:DataResourceBinding DataResource="{StaticResource childVmBinding}">
</vm:DataResourceBinding>
</vm:MainWindowViewModel.ChildViewModel>
</vm:MainWindowViewModel>
</Window.DataContext>
<Grid>
<vw:UserControl1 x:Name="child" DataContext="{Binding Source={StaticResource ResourceKey=childVM}}">
</vw:UserControl1>
</Grid>
</Window>

Related

Associating View and ViewModel pairs in XAML without repetitive code

I have read a very nice tutorial about letting ViewModels do their stuff while the views just switch themselves via databinding with DataTemplates.
I succesfully made a window which has an ApplicationViewModel, which in turn has a hardcoded DummyViewModel as its CurrentScreen property
<Window x:Class="MyProject.Aplication"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:MyProject.ViewModels"
xmlns:v="clr-namespace:MyProject.Views"
xmlns:System="clr-namespace:System;assembly=mscorlib"
Title="Killer Application" Height="900" Width="1440"
WindowState="Maximized">
<!-- This view has its viewmodel as data context, which has a CurrentScreen property -->
<Window.DataContext>
<vm:AplicationViewModel/>
</Window.DataContext>
<Window.Resources>
<!-- each ViewModel will explicitly map to its current View - while this should happen EXPLICITLY, no? -->
<DataTemplate DataType="{x:Type vm:DummyViewModel}">
<v:DummyView/>
</DataTemplate>
<!-- Doc, are you telling me I'll have to fill this up for EVERY VIEW/VIEWMODEL? -->
<DataTemplate DataType="{x:Type vm:YetAnotherViewModel}">
<v:YetAnotherView/>
</DataTemplate>
</Window.Resources>
<!-- the content is bound to CurrentScreen property of the data context -->
<ContentControl Content="{Binding CurrentScreen}" />
</Window>
I would like to have some (very simple and direct, if possible) way to get the same result WITHOUT having to exhaustively code one DataTemplate Window Resource for every possible screen the window can show. Is there a way?
If you're doing MVVM, then you should be using an MVVM framework. For example, Caliburn.Micro will do view location and automatic binding based on conventions.
I resolved this by using the DataTemplateManager found in this article.
It enables to declare the View-to-ViewModel relation like this:
manager.RegisterDataTemplate<ViewModelA, ViewA>();
manager.RegisterDataTemplate<ViewModelB, ViewB>();
This is still explicit, but it reduces a LOT of overhead of having to do it in XAML. Think about namespaces for instance.

RadBusyIndicator always selected when designer is clicked

I have a RadBusyIndicator on my UserControl like so:
<Grid>
<!-- Other Content -->
<t:RadBusyIndicator IsBusy="{Binding IsBusy}"></t:RadBusyIndicator>
</Grid>
And whenever I click in the design view it goes to the BusyIndicator.
I can set the Panel.ZIndex to be negative to select the "Other Content", but this will cause the RadBusyIndicator to be behind the "Other Content"
I tried using a binding for the ZIndex like so:
<t:RadBusyIndicator Panel.ZIndex="{Binding BusyZIndex}" IsBusy="{Binding IsBusy}"></t:RadBusyIndicator>
But it doesn't help.
So the question is:
How do I have the RadBusyIndicator on "Top" of all the "Other Content" but still be able to click(in the designer) and go to the xaml line for that control?
The BusyIndicator needs to be "on top" to be in front of the controls. That makes it also on top in the designer.
There may be better ways of solving this, but what comes into mind is to make the BusyPanel a Resource on the UserControl, and then add it in the Grid control OnApplyTemplate or Loaded by code.
Here is the UserControl's XAML.
<UserControl x:Class="WpfApplication2.UserControl1"
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:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:t="REFERENCE.TO.THE.RAD.ASSEMBLY"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<t:RadBusyIndicator x:Key="TheBusyIndicator" IsBusy="{Binding IsBusy}">
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<Button Content="Some content to the button"
Height="25"
Width="200"/>
</Grid>
</UserControl>
I have added the BusyIndicator as a Resource with the Key "TheBusyIndicator".
I have also added x:Name="LayoutRoot" to the Grid which will contain the BusyIndicator.
The Grid can of course have another name if it in fact is not the layout root control.
By adding the BusyIndicator to the Children collection last, it will appear in front of all other controls that are added by the markup code.
Here is the code
UserControl's Constructor:
public UserControl1()
{
this.Loaded += new RoutedEventHandler(UserControl1_Loaded);
}
The execting code:
private void UserControl1_Loaded(object sender, RoutedEventArgs e)
{
this.LayoutRoot.Children.Add(this.Resources["TheBusyIndicator"] as RadBusyIndicator);
}
I never use UserControls anymore, only CustomControls where the XAML goes to "Generic.xaml" and I have no editor to work in. So I have not seen this problem for a while.

Dynamically load different UserControls based on object type via data binding in xaml

Is there some way in WPF to get the same functionality DataTemplateSelector gives you, but for UserControls?
Say I have a StackView to which I want to bind an IEnumerable of objects. What I'd like to do is somehow have a mapping that, for each object type in the bound IEnumerable, looks at the object type and determines what UserControl to add to the StackView.
So, given three classes:
public class House : Building{}
public class Apartment : Building{}
public class Tent : Building{}
where each class inherits from Building and has its own defined UserControl, I'd like to set DataContext to an IEnumerable<Building> and somehow get the StackView to populate its set of children with the type-specific UserControl.
I'd like to do this with as little code behind as possible. The more data binding and XAML duct tape the better.
You can use complex user controls in a DataTemplate; just declare the DataTemplate as your UserControl.
Example:
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication4"
Title="MainWindow" Height="300" Width="300" Name="UI" >
<Window.Resources>
<DataTemplate DataType="{x:Type local:House}" >
<local:HouseUserControl DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Apartment}">
<local:ApartmentUserControl DataContext="{Binding}"/>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding ElementName=UI, Path=ListOfBuildings}" />
</Grid>
</Window>
I'm not sure I see the problem. Just create DataTemplates for each type in your resources somewhere and WPF will use them automatically to render each type.

Binding datacontext to another viewmodel when design mode

I would like to bind my silverlight view to another datacontext when I'm in design mode. With the locator pattern, I can do something like :
<UserControl or Window Or Else
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
DataContext="{Binding MyViewModelStatic, Source={StaticResource Locator}}"
d:DataContext="{Binding Path=MyViewModelDesign, Source={StaticResource Locator}}">
If I'm not using this pattern and use a declarative way like this :
<UserControl.DataContext>
<local:MyViewModel />
</UserControl.DataContext>
How can I set the d: to another viewmodel ?
Thanks in advance for any help
This should do the trick:
<d:DesignProperties.DataContext>
<sample:SampleViewModel />
</d:DesignProperties.DataContext>

Where to create parametrized ViewModel?

I have recently parametrized my ViewModel's contructor. Before that, I was doing this in my window:
<Window.DataContext>
<vm:MyViewModel />
</Window.DataContext>
The framework instantiated the ViewModel for me.
I know I can set DataContext in code but I would prefer a XAML way so designer can display my test data when designing.
Is this possible?
Use an ObjectDataProvider if you want to specify constructor parameters:
<Window.DataContext>
<ObjectDataProvider ObjectType="vm:MyViewModel"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<ObjectDataProvider.ConstructorParameters>
<sys:String>A string parameter</sys:String>
<sys:Int32>42</sys:Int32>
</ObjectDataProvider.ConstructorParameters>
</ObjectDataProvider>
</Window.DataContext>
I have no idea how to pass a contructor-parameter, I think it can't be done (but it would be nice if someone proved me wrong).
What you can do is set properties on your ViewModel, as in
<Window.DataContext>
<vm:MyViewModel MyProperty="Hello" />
</Window.DataContext>

Resources