Can the MVVM-Light ViewModelLocator be used in nested ViewModels? - wpf

The Visual Studio 2008 Designer doesn't seem to like UserControls that reference the MVVM-Light ViewModelLocator. I get an error message like:
Could not create an instance of type 'MyUserControl'.
For example, the following XAML will cause this behavior if MyUserControl uses the ViewModelLocator to establish its DataContext.
<Page x:Class="MyProject.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Views="clr-namespace:MyProject.Views"
>
<Grid>
<Views:MyUserControl/>
</Grid>
</Page>
MyUserControl is extremely simple:
<UserControl x:Class="MyProject.Views.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding MyNestedViewModel, Source={StaticResource Locator}}"
>
<Grid>
<TextBlock>Hello</TextBlock>
</Grid>
</UserControl>
And the "MyNestedViewModel" property simply instantiates an instance of the MyNestedViewModel class, which has absolutely no code in its default constructor.
Two questions:
Am I using the ViewModelLocator correctly? That is, can it be used in nested views or is it only meant for top-level Views?
Could this just be another bug in Cider, the Visual Studio 2008 designer?
Note that everything works perfectly at runtime. I only have problems at design time. But I hate coding XAML blind.

I encounter the same situation in VS 2010. A partial workaround I JUST discovered...
In your UserControl, change DataContext to d:DataContext
<UserControl x:Class="MyProject.Views.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
d:DataContext="{Binding MyNestedViewModel, Source={StaticResource Locator}}"
>
<Grid>
<TextBlock>Hello</TextBlock>
</Grid>
</UserControl>
Unfortunately, I can't get it to display data in the UserControl YET, just the UserControl itself.

Related

ControlTemplate TargetType does not match templated type

I have created this usercontrol in WPF
<UserControl x:Class="WpfApp1.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<UserControl.Template>
<ControlTemplate TargetType="{x:Type local:UserControl1}">
<Grid x:Name="LayoutRoot" Background="White" Margin="7,7,7,0">
</Grid>
</ControlTemplate>
</UserControl.Template>
When I compile this I get the following error.
'UserControl1' ControlTemplate TargetType does not match templated type 'UserControl'.
But when I debug the application it works fine.
What does this error mean? And how do I fix it?
The error most likely only shows up when you have the XAML editor open, right? This is because it is a XAML Designer problem, not a WPF or .NET problem. That is why it works fine when you run, but shows an error in Visual Studio.
I am having similar issues because the XAML Designer also does not support polymorphism on ControlTemplates (template's TargetType="baseclass", but is applied to a derived class) - yet this also work at runtime.
The main workaround is to not define your custom control using XAML. Instead, create a templated control, where the template is selected by an external style resource. See http://mrbool.com/how-to-create-a-custom-control-in-xaml-and-c/26447 for more information.

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.

How to retrieve a control's viewmodel after creation?

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>

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.

How do I use Sketchflow sample data for a ListBoxItem Template at design time?

I am using Expression Blend 4 and Visual Studio 2010 to create a Sketchflow prototype.
I have a Sample Data collection and a ListBox that is bound to it. This displays as I would expect both at design time and at run time. However, the ListBoxItem template it just complex enough that I wanted to pull it out into its own XAML file. Even though the items still render as expected in the main ListBox where the template is used, when I open the template itself, all of the databound controls are empty.
If I add a DataContext to the template, I can see and work with the populated objects while in the template, but then that local DataContext overrides the DataContext set on the listbox.
A bit of code will illustrate. Start by creating a Sketchflow project (I am using Silverlight, but it should work the same for WPF), then add a project data source called SampleDataSource. Add a collection called ListData, with a single String property called Title.
Here is the (scaled down) code for the main Sketchflow screen, which we'll call Main.xaml:
<UserControl
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:local="clr-namespace:DemoScreens"
mc:Ignorable="d"
x:Class="DemoScreens.Main"
Width="800" Height="600">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ProjectDataSources.xaml"/>
</ResourceDictionary.MergedDictionaries>
<DataTemplate x:Key="ListBoxItemTemplate">
<local:DemoListBoxItemTemplate d:IsPrototypingComposition="True" Margin="0,0,5,0" Width="748"/>
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="#5c87b2" DataContext="{Binding Source={StaticResource SampleDataSource}}">
<ListBox Background="White" x:Name="DemoList" Style="{StaticResource ListBox-Sketch}" Margin="20,100,20,20" ItemTemplate="{StaticResource ListBoxItemTemplate}" ItemsSource="{Binding ListData}" ScrollViewer.HorizontalScrollBarVisibility="Disabled"/>
</Grid>
</UserControl>
You can see that it references the DemoListBoxItemTemplate, which is defined in its own DemoListBoxItemTemplate.xaml:
<UserControl
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:local="clr-namespace:DemoScreens"
mc:Ignorable="d"
x:Class="DemoScreens.DemoListBoxItemTemplate">
<Grid x:Name="LayoutRoot">
<TextBlock Text="{Binding Title}" Style="{StaticResource BasicTextBlock-Sketch}" Width="150"/>
</Grid>
</UserControl>
Obviously, this is way simpler than my actual listbox, but it should be enough to illustrate my problem. When you open Main.xaml in the Expression designer, the list box is populated with sample data. But when you open DemoListBoxItemTemplate.xaml, there is no data context and therefore no data to display—which makes it more difficult to identify controls visually.
How can I have sample data displayed when I am working with the template, while still allowing the larger set of sample data to be used for the ListBox itself?
I believe this should work for you, I just tried it with SL and Blend 4:
Instead of making your template into
a usercontrol to put it into a
separate file, move the template
into its own resource dictionary.
To add a new resource dictionary, there is a button in the upper right of the resources panel.
Find the template (might be named ItemTemplate by default) in the resources of your usercontrol, right click it to copy, paste it into the new resource dictionary.
Delete the original resource, this will likely give you a warning about references, if you leave them the same they may still work because the names will be the same, if not:
Right click your listbox, edit additional templates, items template, apply resource and pick the ItemTemplate from your new resource dictionary.
Now your template is in a separate file, and should still be editable with a datacontext, although it will not be in place on the artboard like it is if the data template is in the same usercontrol.
Hope that helps, let me know if it doesn't.

Resources