MVVM - datatemplate creates a new view - c#

I have two DataTemplates that gets switched depending on the current ViewModel. However whenever I switch my ViewModel, it seems to call the respective View's constructor and calls the InitializeComponent() call within the constructor, which means that whenever I switch the DataTemplate, it generates a new view that is bound to the respective DataTemplate. I am not sure why this is happening but is there a way to prevent the creation of a new View when switching ViewModels?
Below is the DataTemplates located at my MainView.
<Window.Resources>
<DataTemplate DataType="{x:Type viewModels:FirstPanelViewModel}">
<views:FirstPanelView />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:SecondPanelViewModel}">
<views:SecondPanelView />
</DataTemplate>
</Window.Resources>
The template is being displayed in a ContentControl.
<ContentControl Grid.Row="1" Content="{Binding CurrentViewModel}" />
This is my SecondPanelView which is the same as my FirstPanelView, it's very simple.
public partial class FirstPanelView
{
public FirstPanelView()
{
InitializeComponent();
}
}
public partial class SecondPanelView
{
public SecondPanelView()
{
InitializeComponent();
}
}
My Ioc makes sure that I generate only one instance of the SecondPanelView
container.Register<IFirstPanelViewModel, FirstPanelViewModel>(new PerContainerLifetime())
container.Register<ISecondPanelViewModel, SecondPanelViewModel>(new PerContainerLifetime());
DataContext is being bounded in each view by a custom markup extension.
DataContext="{Binding Source={common:Locate}, Path=FirstPanelViewModel}"
DataContext="{Binding Source={common:Locate}, Path=SecondPanelViewModel}"
Which is just calling GetInstance of the respective ViewModel.
public IFirstViewModel FirstViewModel
{
get { return _container.GetInstance<IFirstPanelViewModel>(); }
}
public ISecondViewModel SecondViewModel
{
get { return _container.GetInstance<ISecondPanelViewModel>(); }
}

This is an old issue, but I was also struggling with this issue. The answer is to place the view instances directly in the resources and bind them to content controls in the data templates. If you do so, the view is instantiated only once.
<Window.Resources>
<views:FirstPanelView x:Key="FirstPanelViewKey"/>
<views:SecondPanelView x:Key="SecondPanelViewKey"/>
<DataTemplate x:Key="DT1">
<ContentControl Content="{StaticResource FirstPanelViewKey}" />
</DataTemplate>
<DataTemplate x:Key="DT2">
<ContentControl Content="{StaticResource SecondPanelViewKey}" />
</DataTemplate>
</Window.Resources>

I wasn't able to solve my problem even after extending ContentControl. The issue I ran into using that approach is that ContentControl's dependency property was not directly interfacable/overridable which forced me to hack on the existing dependency property. Also the intialization of a DataTemplate seems to fall deeper than the simple ContentControl.
So I decided to change the way that my views are being displayed by simply toggling their visibility. This approach worked for me since I essentially want my views to stay in the background doing its own thing and ready to be interfaced at its previous state in any moment.

Related

WPF/MVVM: switching between views

I'm trying to learn WPF/MVVM, and I'm currently working on how to switch between views. I've started by finding some example to study in the interntet. The one I'm using is quite simple: two views (named "Home and "Account") that only display a label, to keep the xaml and VM simple, and a main window with two buttons to switch between the views.
The DataTemplates are declared in the App.xaml file (together with the namespaces), so they should be global to the whole project:
<Application.Resources>
<DataTemplate DataType="{x:Type viewmodels:HomeViewModel}">
<views:HomeView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:AccountViewModel}">
<views:AccountView/>
</DataTemplate>
</Application.Resources>
The way I've understood it, the trick is done by a third VM (called MainViewModel.cs) that implements a SelectedViewModel attribute that keeps track of the VM that must be displayed, plus and ICommand bound to the buttons:
private BaseViewModel _selectedViewModel;
public BaseViewModel SelectedViewModel
{
get { return _selectedViewModel; }
set
{
_selectedViewModel = value;
OnPropertyChanged(nameof(SelectedViewModel));
}
}
public ICommand UpdateViewCommand { get; set; }
MainWindow.xaml looks like this:
<ContentControl Grid.Row="0" Content="{Binding SelectedViewModel}"/>
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center">
<Button Margin="10" Width="200" Content="Home" Command="{Binding UpdateViewCommand}" CommandParameter="Home"/>
<Button Margin="10" Width="200" Content="Account" Command="{Binding UpdateViewCommand}" CommandParameter="Account"/>
</StackPanel>
In a separate class file (UpdateViewModel.cs) the UpdateViewModel class implements the Execute method as follows:
public void Execute (object parameter)
{
if (parameter.ToString() == "Home")
{
viewModel.SelectedViewModel = new HomeViewModel();
}
else if (parameter.ToString() == "Account")
{
viewModel.SelectedViewModel = new AccountViewModel();
}
}
I hope I've given the idea without boring you. It all works and lets me understand the basics. Now I wanted to try a variant, i.e. take one view (the "Account" one) and implement a button that would switch directly to the other view. I thought all I had to do was to bind the button to the UpdateViewModel class, and initially I modified the Account.xaml code as follows:
<Button Content="Button" Command="{Binding Path=UpdateViewCommand}" CommandParameter="Home"/>
The program runs, but when I click on the button in the Account view, nothing happens. So I changed this to something more complex:
<UserControl.DataContext>
<src:MainViewModel/>
</UserControl.DataContext>
...
<Button Content="Button" Command="{Binding Path=UpdateViewCommand}" CommandParameter="Home"/>
But the result is the same. I suspect it has to do with the binding, but can't see how to change it. Anybody can help?
Welcome to SO!
Your button bindings are done relative to the current DataContext, so binding to UpdateViewCommand in your AccountView (say) will try to bind to the UpdateViewCommand in your AccountViewModel, rather than your MainViewModel.
There are two ways to solve this. The first is to change your button bindings to bind to the parent's view model instead:
<Button Content="Button" Command="{Binding Path=DataContext.UpdateViewCommand, RelativeSource={RelativeSource AncestorType={x:Type local:MainWindow}}}" CommandParameter="Home"/>
A major disadvantage here, of course, is that the visual hierarchy your child views reside in may not always be as predictable as this.
The second (better) way is to add UpdateViewCommand handlers to each of your child view models, and then have them pass control on to whatever you actually want to handle it. In practice you would typically create a base class for all your children, to reduce code duplication, and you would create a service (e.g. INavigationService) for them to call. Your MainViewModel (say) would then implement this interface, and you would use dependency injection to inject that reference into the child view models at their moment of creation (or just have the parent pass itself directly into their constructors, if you don't want to use a full DI framework).

WPF autoscale changing control [duplicate]

I have a WPF application with multiple views. I want to switch from view 1 to view 2 and from there I can switch to multiple views. So I want a button on view 1 that loads view2 in the same window.
I tried those things, but can't get it to work.
How to navigate through windows with MVVM Light for WPF?
https://galasoft.ch/posts/2011/01/navigation-in-a-wp7-application-with-mvvm-light
From the first link, the problem is that I don't understand the ViewModelLocator code. They call the CreateMain(); function but where is this defined, and how can I switch to another view from inside a view.
Firstly, you don't need any of those toolkits/frameworks to implement MVVM. It can be as simple as this... let's assume that we have a MainViewModel, and PersonViewModel and a CompanyViewModel, each with their own related view and each extending an abstract base class BaseViewModel.
In BaseViewModel, we can add common properties and/or ICommand instances and implement the INotifyPropertyChanged interface. As they all extend the BaseViewModel class, we can have this property in the MainViewModel class that can be set to any of our view models:
public BaseViewModel ViewModel { get; set; }
Of course, you'd be implementing the INotifyPropertyChanged interface correctly on your properties unlike this quick example. Now in App.xaml, we declare some simple DataTemplates to connect the views with the view models:
<DataTemplate DataType="{x:Type ViewModels:MainViewModel}">
<Views:MainView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:PersonViewModel}">
<Views:PersonView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:CompanyViewModel}">
<Views:CompanyView />
</DataTemplate>
Now, wherever we use one of our BaseViewModel instances in our application, these DataTemplates will tell the framework to display the related view instead. We can display them like this:
<ContentControl Content="{Binding ViewModel}" />
So all we need to do now to switch to a new view is to set the ViewModel property from the MainViewModel class:
ViewModel = new PersonViewModel();
Finally, how do we change the views from other views? Well there are several possible ways to do this, but the easiest way is to add a Binding from the child view directly to an ICommand in the MainViewModel. I use a custom version of the RelayComand, but you can use any type you like and I'm guessing that you'll get the picture:
public ICommand DisplayPersonView
{
get { return new ActionCommand(action => ViewModel = new PersonViewModel(),
canExecute => !IsViewModelOfType<Person>()); }
}
In the child view XAML:
<Button Command="{Binding DataContext.DisplayPersonView, RelativeSource=
{RelativeSource AncestorType={x:Type MainView}}, Mode=OneWay}" />
That's it! Enjoy.
When i first started wiht MVVM I also struggled with the different MVVM-frameworks and especially the navigation part. Therefore I use this little tutorial i found, that Rachel Lim has created. It's very nice and well explained.
Have a look at it on the following link:
http://rachel53461.wordpress.com/2011/12/18/navigation-with-mvvm-2/
Hope it helped you :)
Maybe this link will help you. Just set the NavigateTo property to the view which you need to display on the window.
As an example you can do something like
<Window x:Class="MainWindowView" 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:meffed="http:\\www.codeplex.com\MEFedMVVM"
meffed:ViewModelLocator.NonSharedViewModel="YourViewModel"
WindowStartupLocation="CenterScreen">
<Button meffed:NavigationExtensions.NavigateTo="firstview"
meffed:NavigationExtensions.NavigationHost="{Binding ElementName=_viewContainer}"
meffed:NavigationExtensions.NavigateOnceLoaded="False"
Visibility="Visible" />
<ContentControl x:Name="_viewContainer" Margin="0,0,0,10" />
<Window>
Then the class file would be
public partial class MainWindowView : Window
{
public MainWindowView()
{
InitializeComponent();
}
public ContentControl ViewContainer { get { return _viewContainer; } }
}
Then you can define each view as UserControl and then using the link I gave above bind the button's meffed:NavigationExtensions.NavigateTo="secondView". To target the ContentControl of the Window just use a RelativeSource binding. For e.g
meffed:NavigationExtensions.NavigationHost="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}},Path=ViewContainer}"
In each of the view just see that you annotate the code behind class definition with the [NavigationView("firstview")] and so on.
It is complicated for first time but it will be very easy once you understand the idea.
<ContentControl x:Name="K.I.S.S" Content="{Binding ViewModel, Converter={StaticResource ViewLocator}}"/>

Accessing a static property of a view model is null

in my app I need to call a method in my main view model from a sub settings view model to change a tab control in the main view. Basically its a list of viewsmodels in a tab control. The settings viewmodel has checkboxes to turn tabs on or off.
Anyway, I setup a static property in the main viewmodel of the instance. In my settings viewmodel constructor I get a null response but if I retrieve the instance on each checkbox property change it works. Seems like a timing issue on when the instances are created. Is there an event or something that can tell me when the main viewmodel instance isn't null?
<Window.Resources>
<DataTemplate DataType="{x:Type skyTelescope:SkyTelescopeVM}">
<skyTelescope:SkyTelescopeV />
</DataTemplate>
<DataTemplate DataType="{x:Type rotator:RotatorVM}">
<rotator:RotatorView />
</DataTemplate>
<DataTemplate DataType="{x:Type focuser:FocuserVM}">
<focuser:FocuserView />
</DataTemplate>
<DataTemplate DataType="{x:Type settings:SettingsVM}">
<settings:SettingsV />
</DataTemplate>
</Window.Resources>
There's a lot to learn with wpf and the mvvm approach is very different from event driven code.
Your markup looks like you're doing viewmodel first... or something like that.
If you have a MainWindowViewModel exposes say a SubVM property.
Bind SubVM to the content property of a contentcontrol in mainwindow.
Set SubVM to an instance of a viewmodel such as SettingsVM.
This is then templated out in the view using the matching datatemplate.
You see a SettingsV appear.
The datacontext of SettingsV is your SubVM.
Bind controls in SettingsV to properties in SubVM and the values can transfer between them.
That allows you to control when you instantiate SettingsVM in MainWindowViewModel.
You therefore "know" whether you have an instance of it or not.
You can cache instances of viewmodels in a dictionary.
Use a type as your key and you can instantiate one if you don't have it in your dictionary, then re-use it if you wanted to retain state.
You can make MainWindow instantiate MainWindowViewModel by defining it's datacontext in xaml.
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
With viewmodel first there's an oddity with the way templating works. If you wanted to reset (unbound) view state then you need to force the re-templating by setting your SubVM property null first.
This command takes a Type as a parameter
private RelayCommand<Type> navigateCommand;
public RelayCommand<Type> NavigateCommand
{
get
{
return navigateCommand
?? (navigateCommand = new RelayCommand<Type>(
vmType =>
{
CurrentViewModel = null;
CurrentViewModel = Activator.CreateInstance(vmType);
}));
}
}
Obviously, this doesn't stash any instance of a vm away in a dictionary, it's just instantiating every time.

Set different datacontext in one view

I have 2 Windows. From the first window I'm calling second :
var window = new WindowButtonClick("Graphic") {DataContext = new GraphicViewModel()};
window.ShowDialog();
Here is XAML of second window:
<Window x:Class="WindowButtonClick"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:graphic="clr-namespace:Windows.Graphic"
WindowStartupLocation="CenterScreen" >
<Window.Resources>
<DataTemplate DataType="{x:Type graphic:GraphicViewModel}">
<graphic:Graphic />
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content="{Binding}"/>
</Grid>
and constructor:
public WindowButtonClicks(string title)
{
InitializeComponent();
Title = Application.Current.Resources[title].ToString();
}
So how can I set DataContext, that it will show title that I pass in constructor as window Title and ContentControl will show one of the viewModels(GraphicViewModel in this case) ?
This is a common problem in WPF. Luckily, it has a simple solution. You'll need to use a RelativeSource Binding. So you'll need to set the DataContext to one object, whose properties you can data bind simply to like this:
<TextBox Text="{Binding PropertyOfDataContext}" />
And for any properties that are declared in the Window or UserControl, you can use the RelativeSource Binding like this:
<TextBox Text="{Binding PropertyOfWindow, RelativeSource={RelativeSource AncestorType={
x:Type YourXamlPrefix:YourWindow}}}" />
UPDATE >>>
You said:
I will have 20 ViewModels, that I want to load to ContentControl
If you had only provided all of the relevant information when asking your question, then you would have had a better answer by now. This is a different problem, but can be fixed just as easily. In this case, you can just set your view models to their views using DataTemplates... just define one for each view model/view pair like this:
<DataTemplate DataType="{x:Type ViewModels:ViewModel1}">
<Views:View1 />
</DataTemplate>
...
<DataTemplate DataType="{x:Type ViewModels:ViewModelN}">
<Views:ViewN />
</DataTemplate>
Note that I did not set the x:Key values... this means that the specified views will be implicitly rendered by the Framework whenever it comes across objects of the relevant type. Then to display the View1 from this example, you'd just need to do this:
<ContentControl Content="{PropertyOfTypeViewModel1}" />
UPDATE 2 >>>
Wow... I really hope that you've explained your problem properly this time because this is my last update. So I can't really see a problem with what you are asking for... you want to set a property in the constructor to be displayed as the Window.Title. This would definitely work:
public WindowButtonClicks(string title)
{
InitializeComponent();
Title = "Some Title";
}
So if your code doesn't work, then you must have a problem with your call to Application.Current.Resources[title]... have you actually checked whether that returns a value or not? If it does, then you have a real problem, because it is perfectly acceptable to set the Window.Title like this.
If Application.Current is returning null, then just make sure that you set it to an instance of MainWindow.xaml.cs in the code behind:
// In MainWindow.xaml.cs constructor
Application.Current = this;
Other than that, your problem is impossible to determine from the information that you have provided.

WPF MVVM navigate views

I have a WPF application with multiple views. I want to switch from view 1 to view 2 and from there I can switch to multiple views. So I want a button on view 1 that loads view2 in the same window.
I tried those things, but can't get it to work.
How to navigate through windows with MVVM Light for WPF?
https://galasoft.ch/posts/2011/01/navigation-in-a-wp7-application-with-mvvm-light
From the first link, the problem is that I don't understand the ViewModelLocator code. They call the CreateMain(); function but where is this defined, and how can I switch to another view from inside a view.
Firstly, you don't need any of those toolkits/frameworks to implement MVVM. It can be as simple as this... let's assume that we have a MainViewModel, and PersonViewModel and a CompanyViewModel, each with their own related view and each extending an abstract base class BaseViewModel.
In BaseViewModel, we can add common properties and/or ICommand instances and implement the INotifyPropertyChanged interface. As they all extend the BaseViewModel class, we can have this property in the MainViewModel class that can be set to any of our view models:
public BaseViewModel ViewModel { get; set; }
Of course, you'd be implementing the INotifyPropertyChanged interface correctly on your properties unlike this quick example. Now in App.xaml, we declare some simple DataTemplates to connect the views with the view models:
<DataTemplate DataType="{x:Type ViewModels:MainViewModel}">
<Views:MainView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:PersonViewModel}">
<Views:PersonView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:CompanyViewModel}">
<Views:CompanyView />
</DataTemplate>
Now, wherever we use one of our BaseViewModel instances in our application, these DataTemplates will tell the framework to display the related view instead. We can display them like this:
<ContentControl Content="{Binding ViewModel}" />
So all we need to do now to switch to a new view is to set the ViewModel property from the MainViewModel class:
ViewModel = new PersonViewModel();
Finally, how do we change the views from other views? Well there are several possible ways to do this, but the easiest way is to add a Binding from the child view directly to an ICommand in the MainViewModel. I use a custom version of the RelayComand, but you can use any type you like and I'm guessing that you'll get the picture:
public ICommand DisplayPersonView
{
get { return new ActionCommand(action => ViewModel = new PersonViewModel(),
canExecute => !IsViewModelOfType<Person>()); }
}
In the child view XAML:
<Button Command="{Binding DataContext.DisplayPersonView, RelativeSource=
{RelativeSource AncestorType={x:Type MainView}}, Mode=OneWay}" />
That's it! Enjoy.
When i first started wiht MVVM I also struggled with the different MVVM-frameworks and especially the navigation part. Therefore I use this little tutorial i found, that Rachel Lim has created. It's very nice and well explained.
Have a look at it on the following link:
http://rachel53461.wordpress.com/2011/12/18/navigation-with-mvvm-2/
Hope it helped you :)
Maybe this link will help you. Just set the NavigateTo property to the view which you need to display on the window.
As an example you can do something like
<Window x:Class="MainWindowView" 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:meffed="http:\\www.codeplex.com\MEFedMVVM"
meffed:ViewModelLocator.NonSharedViewModel="YourViewModel"
WindowStartupLocation="CenterScreen">
<Button meffed:NavigationExtensions.NavigateTo="firstview"
meffed:NavigationExtensions.NavigationHost="{Binding ElementName=_viewContainer}"
meffed:NavigationExtensions.NavigateOnceLoaded="False"
Visibility="Visible" />
<ContentControl x:Name="_viewContainer" Margin="0,0,0,10" />
<Window>
Then the class file would be
public partial class MainWindowView : Window
{
public MainWindowView()
{
InitializeComponent();
}
public ContentControl ViewContainer { get { return _viewContainer; } }
}
Then you can define each view as UserControl and then using the link I gave above bind the button's meffed:NavigationExtensions.NavigateTo="secondView". To target the ContentControl of the Window just use a RelativeSource binding. For e.g
meffed:NavigationExtensions.NavigationHost="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}},Path=ViewContainer}"
In each of the view just see that you annotate the code behind class definition with the [NavigationView("firstview")] and so on.
It is complicated for first time but it will be very easy once you understand the idea.
<ContentControl x:Name="K.I.S.S" Content="{Binding ViewModel, Converter={StaticResource ViewLocator}}"/>

Categories