Where to create parametrized ViewModel? - wpf

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>

Related

How to bind to type Window as a DataTemplate

My WPF application uses a Resource Dictionary. I'm also using MVVM.
I am binding to a ResourceDictionary, but want to bind my MainWindow ViewModel to the MainWindow (of type Window) but MVVM won't let me as MainWindow it's not type UserControl.
<Grid.Resources>
<ResourceDictionary Source="Resources\ResourceDictionary.xaml" />
</Grid.Resources>
<Grid.DataContext>
<Binding Source="{StaticResource Mwvm}" />
</Grid.DataContext>
</Grid>
This means I can't do this
<DataTemplate DataType="{x:Type viewModel:MainWindowViewModel}">
<root:MainWindow x:Key="Mwvm" />
</DataTemplate>
Does any one know how I can do the same thing but when the object is a Window and only using XAML (I know I can do this with code behind in the app.xaml onstartup())?
EDIT
To make this very clear, I know that within my MainWindow I can declare a namespace to my ViewModel, but is this the correct way when the namespace is already referenced in my ResourceDictionary and I'm referencing my ResourceDictionary.
Uhm how about?
<Window>
<Window.DataContext>
<someNs:YourVmClass /> <!-- calls the empty c_tor of the class-->
</Window.DataContext>
</Window>
(I'm not sure, if I understood your question. But I guess, that's what you really want.)
According to your edit:
Sure you could do something like
<!-- Define an instance of your VM in the ResourceDictionary -->
<ResourceDictionary>
<someNs:YourVmClass x:Key="InstOfYourVmClass" />
</ResourceDictionary>
In your view you could do something like this.
<Grid>
<Grid.Resources>
<ResourceDictionary Source="Resources\ResourceDictionary.xaml" />
</Grid.Resources>
<Grid.DataContext>
<StaticResource ResourceKey="InstOfYourVmClass" />
</Grid.DataContext>
</Grid>
But I would strongly recommend not to choose this approach. Problem is, everytime you're referencing this ResourceDictionary the current instance InstOfYourVmClass will be overwritten by a new instantiated version.

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>

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>

Databinding Enum to ComboBox

I'm getting a very unusual error on a project that use to work where I'm trying to bind an ENUM to a combo box. To ensure I've not made coding errors, I've made a new usercontrol using SO Question 58743 and ageektrapped as samples for a self contained user control. I'm using .Net4 Client Framework as the environment and VS2010. The xaml is -
<UserControl x:Class="Barcode.Views.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:System="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<ObjectDataProvider MethodName="GetValues"
ObjectType="{x:Type System:Enum}"
x:Key="AlignmentValues">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="HorizontalAlignment" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</UserControl.Resources>
<Grid>
<ComboBox Name="myComboBox" SelectedIndex="0" Margin="8"
ItemsSource="{Binding Source={StaticResource AlignmentValues}}" />
</Grid>
</UserControl>
The error that I'm getting on the ComboBox is Error 144 Unable to cast object of type 'System.String' to type 'System.Windows.DataTemplate' after building the project.
I'm at a loss as to what could be causing this error.
Looks like you may be missing the namespace on your HorizontalAlignment. Add a relevant namespace where the HorizontalAlignment type resides.
xmlns:local="clr-namespace:Barcode.Views"
Then modify your XAML to make use of the newly defined namespace...
...
<x:Type TypeName="local:HorizontalAlignment"/>
...
EDIT:
With this being the HorizontalAlignment enum type from within the framework then your code should work as is. I tested it to be certain and it indeed works; as I placed an instance of the UserControl on my Window and it worked without a hitch. Set up an empty project and start from scratch to see if the problem still exists as you may have other factors causing the issue.

Resources