Associate User Control with ViewModel class - wpf

When I define a DataTemplate inline, Visual Studio knows about the type I'm binding to, and properties in that type come up in autocomplete (for example in the code below I was able to select DisplayName from the autocomplete list inside the FirstViewModel template).
<DataTemplate DataType="{x:Type viewmodels:FirstViewModel}">
<StackPanel >
<Label Content="{Binding DisplayName}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:SecondViewModel}">
<views:SecondView/>
</DataTemplate>
However, when the data template references an external control, as for SecondViewModel in the code above, when I'm in the file for the SecondView usercontrol, since it's just a control, the type isn't bound and the editor doesn't help me with anything.
I've tried wrapping my whole control (inside the UserControl element) in the same DataTemplate tag, but then my whole view just shows "System.Windows.DataTemplate".
<UserControl x:Class="Gui.Views.Tabs.ExamsTabViews.ExamInfoView"
xmlns:vm="clr-namespace:Gui.ViewModels"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<DataTemplate DataType="vm:ExamInfoViewModel">
<DockPanel VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<!-- contents of the template -->
</DockPanel>
</DataTemplate>
</UserControl>
Is there a way to achieve this kind of binding for the editor?

<DataTemplate DataType="{x:Type viewmodels:SecondViewModel}">
<views:SecondView/>
</DataTemplate>
when this DataTemplate is instantiated, there will be created SecondView and that SecondView will have a SecondViewModel in DataContext. So there is no need any DataTemplate in SecondViewModel control - bind to DataContext instead ({Binding SecondViewModelProperty}). To have design-time support for such binding use d:DataContext="{d:DesignInstance}:
<UserControl d:DataContext="{d:DesignInstance Type=vm:ExamInfoViewModel,
IsDesignTimeCreatable=True}" ...>

Related

StaticResource found when added during runtime, but not on launch

I have this parent control that displays its CurrentViewModel, and instance of ViewModelWithExam, using the data templates provided.
<UserControl x:Class="Gui.Views.ExamsTabView"
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:views="clr-namespace:Gui.Views.Tabs.ExamsTabViews"
xmlns:viewmodels="clr-namespace:Gui.ViewModels"
xmlns:data="clr-namespace:DataManagement;assembly=DataManagement"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<DataTemplate DataType="{x:Type viewmodels:ExamInfoViewModel}">
<views:ExamInfoView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:ExamAcquireImageViewModel}">
<views:ExamAcquireImageView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:ExamEditImageViewModel}">
<views:ExamEditImageView/>
</DataTemplate>
<!--Reuseable control to display the simple exam header-->
<DataTemplate x:Key="ExamHeader" DataType="{x:Type data:Exam}">
<views:ExamHeaderView/>
</DataTemplate>
</UserControl.Resources>
<Grid VerticalAlignment="Stretch" Margin="20">
<ContentControl Content="{Binding CurrentViewModel}"/>
</Grid>
</UserControl>
Any of the views can use the ExamHeader to display their Exam property:
<ContentControl ContentTemplate="{StaticResource ExamHeader}" Content="{Binding Exam}"/>
The ExamHeader template, however, exhibits odd behavior. If I remove the x:Key property from the template, and remove the ContentTemplate property from the ContentControl, and then launch the app, the header displays, as expected.
While the app is running, if I add back the x:Key, the header disappears and is replaced by the classname of the Exam. As expected. If I then add back the ContentTemplate to point to the header template, the header template shows. It works.
However, if I launch the app with the x:Key and ContentTemplate in place, then loading a page with a header puts the application in break mode, saying "The resource 'ExamHeader' could not be found." Indeed, when the app isn't running, this error appears under the ControlTemplate property.
But, to say again, if I add in the x:Key and ContentTemplate while the app is running, it work fine!
How do I fix this?
It might be a resource ordering issue. Try moving the ExamHeader resource to the top of your resources:
<UserControl.Resources>
<!--Reuseable control to display the simple exam header-->
<DataTemplate x:Key="ExamHeader" DataType="{x:Type data:Exam}">
<views:ExamHeaderView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:ExamInfoViewModel}">
<views:ExamInfoView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:ExamAcquireImageViewModel}">
<views:ExamAcquireImageView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:ExamEditImageViewModel}">
<views:ExamEditImageView/>
</DataTemplate>
</UserControl.Resources>

How to find Button's DataContext in this sample code?

I was going through the excellent blog written by Rachel. Here is the link.
She mentions in "The View" section that " As Button’s DataContext is the PageViewModel, she used a RelativeSource binding to find the ChangePageCommand".
Could any one explain me, how is that Button's DataContext is PageViewModel?
She has written another blog explaining about DataContext here. From this article it seemed to me that DataContext of the Button would be "ApplicationViewModel", because if the element's DataContext is not specified it will inherit DataContext of it's Parent. And as none of the elements specify any DataContext, it seems like DataContext of Button should be of Window element DataContext (which is "ApplicationViewModel" as defined in App.xaml.cs).
Obviously I am wrong here, but what is that I am not thinking correctly?
Other Code snippets can be found in the article, below is the XAML code.
<Window x:Class="SimpleMVVMExample.ApplicationView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SimpleMVVMExample"
Title="Simple MVVM Example" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type local:HomeViewModel}">
<local:HomeView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ProductsViewModel}">
<local:ProductsView />
</DataTemplate>
</Window.Resources>
<DockPanel>
<Border DockPanel.Dock="Left" BorderBrush="Black" BorderThickness="0,0,1,0">
<ItemsControl ItemsSource="{Binding PageViewModels}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}"
Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding }"
Margin="2,5"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
<ContentControl Content="{Binding CurrentPageViewModel}" />
</DockPanel>
Because you're inside of an ItemsControl's ItemTemplate. The DataContext is implicitly defined as the binding of each object provided by the ItemsSource binding collection.
The ItemsControl creates an ItemTemplate for each item in the ItemsSource collection. The DataContext of each ItemTemplate will be bound to the individual object that is being iterated in the collection. You can read more about datatemplate behavior here. (See Remarks)
So, in order to get to the ChangePageCommand provided by the window's DataContext , you have to provide a relative source lookup.

WPF implicit DataTemplate declared in App.xaml won't take effect

In the MainWindow.xaml, I set:
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
In the App.xaml file, I added the following:
<Application.Resources>
<DataTemplate DataType="vm:MainViewModel">
<v:MainView/>
</DataTemplate>
</Application.Resources>
I was hoping the MainWindow will automatically load and show the MainView with its DataContext property set to the windows's one (which was set to MainViewModel at design-time as above), but it won't work - the MainWindow doesn't use the DataTemplate set in App.xaml.
Any better ideas for this scenario?
You should make a minor changes -
First, in your window. Try this:
<Window>
<!-- setup window... -->
<ContentPresenter>
<ContentPresenter.Content>
<vm:MainViewModel/>
</ContentPresenter.Content>
</ContentPresenter>
</Window>
This creates a single content item within your Window. DataTemplates work by mapping content to a new View - in this case, since the Content here is the MainViewModel, it will automatically create and instantiate a new MainView for you. Setting the DataContext will not trigger DataTemplates, since you're never making the ViewModel "content" of an object.
You can shorten this by just setting the Window's Content directly, if you prefer:
<Window>
<Window.Content>
<vm:MainViewModel/>
</Window.Content>
</Window>
Or, even, binding the Content to the DataContext (though this only makes sense if you need the DataContext set for some other purpose):
<Window Content="{Binding}">
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
</Window>
I think you need
<DataTemplate DataType="{x:Type vm:MainViewModel}">
EDIT:
I really don't think I'm wrong, the code
<Window.DataContext>
<WpfApplication1:ViewModel />
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type WpfApplication1:ViewModel}">
<TextBlock>Custom template</TextBlock>
</DataTemplate>
</Window.Resources>
<ContentPresenter Content="{Binding}" />
shows “Custom template”. If I remove the x:Type, what's shown instead is “WpfApplication1.ViewModel”, which is the result of calling ToString() on the view model object. This is used in the absence of a DataTemplate.

Use DataTemplate in WPF with a mocked object

I have following xaml code:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding MainWindow, Source={StaticResource Locator}}">
<Window.Resources>
<DataTemplate DataType="{x:Type vm:KeyboardViewModel}">
<vw:Keyboard />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:WelcomeViewModel}">
<vw:Welcome />
</DataTemplate>
</Window.Resources>
<DockPanel>
<DockPanel>
<ContentControl Content="{Binding Path=Workspace}" />
</DockPanel>
</DockPanel>
</Window>
When Workspace is KeyboardViewModel, then the UserControl Keyboard is shown. When Workspace is Welcome, then the Welcome screen is shown. But when I test I mock the ViewModels with Moq. Workspace then get the type IKeyboardViewModelProxyxxxxxxxxxxxxx (where xxxxxxx is a random string), that don't maps to KeyboardViewModel in the DataTemplate and WPF don't now wish DataTemplate to show.
When I use the real KeyboardViewModel, it is no problem.
Can I fix it somehow, or do I have to redesign it?
I'm having a similar issue (without using Moq however). A PARTIAL solution that I used is to inherit both KeyboardViewModel and KeyboardViewModelMock from abstract KeyboardViewModelAbstract. Then you can do:
<DataTemplate DataType="{x:Type vm:KeyboardViewModelAbstract}">
<vw:Keyboard />
</DataTemplate>
Which will work for both, the real model object and the mock.
Unfortunately this solution doesn't scale when you're dealing with models that already have a base class or have any kind of inheritance involved. I'd be great if DataTemplate could be used with interfaces, but they can't.
You can omit the DataType="{x:Type vm:KeyboardViewModel}". If you do that, it is not expecting an instance of type KeyboardViewModel to bind against anymore but only an object of any type that just has all properties that are used in the template.

WPF - Databind to a StackPanel using DataTemplates

I've modified my question since it has changed focus when trying things out.
I narrowed the problem down to the following...
I try to bind the selected Item of a TreeView to a StackPanel (or some other container that can hold User Controls). This container will then display a UserControl, depending on the type of the selected item.
Here is the xaml of the StackPanel (both treeview and stackpanel are in the same window ==> different grid column)
<StackPanel Grid.Column="2" MinWidth="500" DataContext="{Binding ElementName=myTree, Path=SelectedItem, Mode=OneWay}">
<StackPanel.Resources>
<DataTemplate DataType="{x:Type mvTypes:MyTypeA}">
<controls:UserControlA DataContext="{Binding}" />
</DataTemplate>
<DataTemplate DataType="{x:Type mvTypes:MyTypeB}">
<controls:UserControlB DataContext="{Binding}" />
</DataTemplate>
</StackPanel.Resources>
</StackPanel>
When I place a user control directly under the stackpanel (not in the resources), it displays it with the selected object as their datacontext.
Idem if I place a TextBox in it, it will show the correct type of the selected item.
<TextBox Name="textBox1" Text="{Binding}" />
For some reason, placing it within a DataTemplate (even without setting the DataType) results in nothing to display.
Any sugestions. I'm thinking that maybe a StackPanel is not the right control for this, though I can't seem to find other controls that look suitable as containers like this.
Thanks in advance.
Replace the StackPanel in your example with ContentPresenter and instead of DataContext set the Content property. That should work.
Although you have set the Binding on the second custom control, are you setting the DataContext, as the binding is the route to the information and the DataContext is the information it applies this binding information to.
Andrew
You can create a UserControl to display the TreeView and the selection info on the right, all in one. It saves you from creating any custom control. A custom control is basically unnecessary since you do not create anything which didn't exist before.
<UserControl x:Class="NameSpace.SelectionView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="namespace.Controls"
Height="300" Width="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TreeView Name="customTree">
<!--Items go here-->
</TreeView>
<StackPanel Grid.Column="1" MinWidth="50" DataContext="{Binding ElementName=customTree, Path=SelectedItem, Mode=OneWay}">
<StackPanel.Resources>
<DataTemplate DataType="{x:Type StylingTest:CustomViewModelA}">
<controls:CustomADetailsControl />
</DataTemplate>
<DataTemplate DataType="{x:Type StylingTest:CustomViewModelB}">
<controls:CustomBDetailsControl />
</DataTemplate>
</StackPanel.Resources>
<TextBlock Text="{Binding}"/>
</StackPanel>
</Grid>
</UserControl>
Any other custom behaviour, I'm sure you could create or set in styles/templates here.
Also, you might find one of my other answers useful.
Good luck with wpf, cheers.

Resources