MVVM WPF - Changing Views creates new Instances of View - c#

I've decided to move away from WinForms as I wanted to be more flexible in creating my UIs. That said I'm just starting with WPF and the MVVM structure. I'm having a blast learning all the new stuff and trying to learn the "best practices" right away.
One thing I'm wondering is that with my current implementation of MVVM and navigation through my views it would always create a new instance of the View when changing my CurrentView of my MainViewModel of my MainWindow.
Is that the intended way? As I notice quite a bit of lagging when changing them which feels ... wrong? As a sidenote I'm currently not on my normal workstation but on a older surface which is getting stressed easily with VS running etc
A pretty 'standard' MainViewModel
public class MainViewModel : ObservableObject
{
public MainViewModel()
{
this.PageViewModels.Add(new EventsViewModel());
this.PageViewModels.Add(new SettingsViewModel());
this.PageViewModels.Add(new AnotherViewModel());
this.CurrentView = this.PageViewModels[0];
}
public ViewModelBase CurrentView
{
get
{
return this._currentVIew;
}
set
{
this._currentVIew = value;
this.OnPropertyChanged("CurrentView");
}
}
public ICommand ChangePageCommand
{
get
{
if (this.changePageCommand == null)
{
this.changePageCommand = new RelayCommand(
p => this.ChangeViewModel(p),
p => true);
}
return this.changePageCommand;
}
}
private void ChangeViewModel(object viewModel)
{
var t = (Type)viewModel;
foreach (var v in this.PageViewModels)
{
if (v.GetType() == t)
{
this.CurrentView = v;
return;
}
}
}
}
'Normal' View Data Templating
<DataTemplate DataType="{x:Type viewModel:EventsViewModel}">
<view:EventView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:SettingsViewModel}">
<view:SettingsView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:AnotherViewModel}">
<view:AnotherView/>
</DataTemplate>
The way the Command is beeing called, just with different x:Type parameter
Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
CommandParameter="{x:Type viewModel:SettingsViewModel}"

Related

WPF TabControl with MVVM using Dependency Injection

I'm very new to WPF but quite experienced with .NET and C#. I am trying to create (what I though would be) a fairly simple CRUD admin desktop application for a website I plan on building.
WPF seems to be way more complicated than I expected it to be and after lots of Googling I've basically realised that everyone uses the MVVM pattern - fine. Now, with my existing .NET experience, I know I definitely want to to be using dependency injection. I've discovered that everything seems to be done within the ViewModel in WPF, including all the services and everything - fine again.
Now, onto my problem. I have set up a basic tab control and I'm binding the tab values to an enum using Enum.GetValues(). I want the view to change when I select a tab and the view will depend on which tab is selected. The problem is, I can't seem to get the view to show - it just shows a blank screen. The view is a custom UserControl I've created and defined as a resource and contains a grid and a bunch of buttons and stuff. I've omitted this from below as it doesn't seem relevant.
My MainWindow.xaml is pretty simple and looks like this:
<Window x:Class="Stc.Admin.MainWindow"
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:viewmodels="clr-namespace:Stc.Admin.ViewModels"
xmlns:views="clr-namespace:Stc.Admin.Views"
xmlns:local="clr-namespace:Stc.Admin"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TabControl ItemsSource="{Binding Tabs}" SelectedItem="{Binding CurrentTab}">
<TabControl.Resources>
<DataTemplate DataType="{x:Type viewmodels:GamesViewModel}">
<views:Games />
</DataTemplate>
</TabControl.Resources>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding DataContext.CurrentViewModel, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TabControl}}}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
Here's my MainViewModel.cs:
public class MainViewModel
{
private readonly IViewModelFactory<GamesViewModel> _gamesViewModelFactory;
private ViewType _currentTab;
public ViewType CurrentTab
{
get
{
return _currentTab;
}
set
{
_currentTab = value;
ChangeView(_currentTab);
}
}
public ObservableCollection<ViewType> Tabs { get; }
public ViewModelBase CurrentViewModel { get; set; }
public MainViewModel(IViewModelFactory<GamesViewModel> gamesViewModelFactory)
{
_gamesViewModelFactory = gamesViewModelFactory;
Tabs = new ObservableCollection<ViewType>(Enum.GetValues(typeof(ViewType)).Cast<ViewType>().ToArray());
}
private void ChangeView(ViewType viewType)
{
switch (viewType)
{
case ViewType.Games:
CurrentViewModel = _gamesViewModelFactory.CreateViewModel();
break;
case ViewType.Listings:
break;
case ViewType.Users:
break;
case ViewType.Languages:
break;
case ViewType.Currencies:
break;
default:
break;
}
}
}
public enum ViewType
{
Games,
Listings,
Users,
Languages,
Currencies
}
GamesViewModel has service dependencies so it needs to be created using the factory.
And my DI setup in App.xaml.cs:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
IServiceProvider serviceProvider = this.createServiceProvider();
Window window = new MainWindow();
window.DataContext = serviceProvider.GetRequiredService<MainViewModel>();
window.Show();
base.OnStartup(e);
}
private IServiceProvider createServiceProvider()
{
IServiceCollection services = new ServiceCollection();
services.AddDbContext<StcContext>(options =>
options.UseSqlServer(#"Server=(localdb)\mssqllocaldb;Database=Stc;Integrated Security=True"));
services.AddSingleton<ICrudService<Game>, CrudService<Game>>();
services.AddSingleton<IViewModelFactory<GamesViewModel>, GamesViewModelFactory>();
services.AddScoped<MainViewModel>();
return services.BuildServiceProvider();
}
}
I have sorted this issue now. Being new to WPF, I didn't realise that I have to use INotifyPropertyChanged to get the UI to update after changing a property value on my ViewModel. I'd seen this used in a lot of the articles and tutorials I was seeing but didn't really understand what it was or how to apply it to my application.
The change I made was to implement this interface on my base ViewModel like so:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I then change my MainViewModel to inherit from the base class and changed the setter of the CurrentTab property to call OnPropertyChanged (with the name of the property) after I've changed the view/viewmodel property:
private ViewType _currentTab;
public ViewType CurrentTab
{
get
{
return _currentTab;
}
set
{
_currentTab = value;
ChangeView(_currentTab);
OnPropertyChanged(nameof(CurrentViewModel));
}
}
I believe this is telling the UI that something has changed and it needs to redraw itself. Correct me if I'm wrong or if that's an oversimplification.

WPF with multiple views plus Prism and Unity

I have to write an application in WPF C#. My problem is I don't know how to work with multiple views. Till know I know how use Prism to wire a ViewModel to View through bindings, at basic level. I have learnt a bit of Unity to register ViewModel to View in App.xml.cs, through overriding the OnStartup method and using of UnityContainer.
I want to know how to navigate from View 1 to View 2 and vice versa.
I want to navigate through a button and the views are different.
Can you help me,please? Some advices?
Like this,decoherence!
I have an example that i wrote long ago and it does not require any framework nonsense, based on my experience the WPF MVVM frameworks are useless for the most part and tend to complicate simple things all you need is an ICommand implementation and a ViewModelBase which implements INotiftyPropertyChanged, here is a simple illustration:
XML:
<Window.Resources>
<DataTemplate DataType="{x:Type local:ViewModel1}">
<local:View1/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModel2}">
<local:View2/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModel3}">
<local:View3/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<ContentControl Content="{Binding CurrentViewModel}">
</ContentControl>
<StackPanel Orientation="Horizontal" Grid.Row="1" VerticalAlignment="Bottom">
<Button Command="{Binding PrevViewModel}">Previouws View</Button>
<Button Command="{Binding NextViewModel}">Next View</Button>
<Button Command="{Binding SwitchToViewModel}" CommandParameter="{x:Type local:ViewModel1}">Switch To View1</Button>
<Button Command="{Binding SwitchToViewModel}" CommandParameter="{x:Type local:ViewModel2}">Switch To View2</Button>
<Button Command="{Binding SwitchToViewModel}" CommandParameter="{x:Type local:ViewModel3}">Switch To View3</Button>
</StackPanel>
</Grid>
Given the above when ever the CurrentViewModel property changes, the View will get selected based on the DataTemplate resources, and the DataContext of the Window is set to the MainViewModel.
The main ViewModel looks like the following:
public class MainViewModel : ViewModelBase
{
//add instances to all the ViewModels you want to switch between here, and add templates for them in your resources specifying the x:type and the view or data template to be used
public ObservableCollection<ViewModelBase> ViewModels { get; set; } = new ObservableCollection<ViewModelBase>();
private ViewModelBase _currentViewModel;
public ViewModelBase CurrentViewModel {
get { return _currentViewModel; }
set { SetField(ref _currentViewModel, value); }
}
private ICommand _nextViewModel;
public ICommand NextViewModel
{
get
{
return _nextViewModel = _nextViewModel ?? new RelayCommand(p =>
{
CurrentViewModel = ViewModels[ViewModels.IndexOf(CurrentViewModel) +1];
}, p =>
{
return ViewModels.IndexOf(CurrentViewModel) + 1 != ViewModels.Count && CurrentViewModel != null;
});
}
}
public ICommand _prevViewModel;
public ICommand PrevViewModel
{
get
{
return _prevViewModel = _prevViewModel ?? new RelayCommand(p =>
{
CurrentViewModel = ViewModels[ViewModels.IndexOf(CurrentViewModel) - 1];
}, p =>
{
return ViewModels.IndexOf(CurrentViewModel) != 0 && CurrentViewModel!=null;
});
}
}
private ICommand _switchToViewModel;
public ICommand SwitchToViewModel
{
get
{
return _switchToViewModel = _switchToViewModel ?? new RelayCommand(p =>
{
CurrentViewModel = this.ViewModels.FirstOrDefault(vm=>vm.GetType()==p as Type);
}, p =>
{
return this.ViewModels.FirstOrDefault(vm => vm.GetType() != p as Type) != null;
});
}
}
}
The result looks like
It is extremely easy to do this with Prism Navigation, and it doesn't require you to create dependencies on ViewModels, or introduce performance lags with the use of dynamic DataTemplates. I suggest reading the documentation on Prism navigation.
https://github.com/PrismLibrary/Prism/blob/master/Documentation/WPF/60-Navigation.md#view-based-navigation
Essentially you use a combination of ReqestNavigate, GoBack, and GoForward.
There is also a sample available for you to study here:
https://github.com/PrismLibrary/Prism-Samples-Wpf/tree/master/View-Switching%20Navigation_Desktop
You should also watch this course that walks you through exactly what you are asking:
https://app.pluralsight.com/library/courses/prism-introduction/table-of-contents

Navigate through UserControl with MVVM LIGHT (WPF)

First of all I apologize for my poor english which is not my first language.
I'm new in MVVM so my question is probably a very newbie one ;)
I'm encountering some issue with switching View in a C# Application using WPF and MVVM LIGHT. I've read a lot of articles but i still can't figured out how to do it in a clean way.
So here is my question: What is the best way to achieve the navigation between UserControl contained in a MainWindows, assuming that:
I've a ViewModel for each UserControl and one for the Main Windows.
The buttons for switching between usercontrols are contained into UserControl itself
I've a ViewModelLocator
I need to sometimes Destroy/re-create a userControl's ViewModel
I want to respect the MVVM Pattern.
I want to keep it simple
Since nobody answers to my question, this is what I finally did.
It might not be the best way but at least it works well.
I hope it'll helps some newbies like me who are struggling learning this pattern:
Put a CurrentViewModel Object in the MainViewModel:
public class MainViewModel : ViewModelBase,IMainViewModel
{
/* Other piece of code */
private ViewModelBase _currentViewModel;
public ViewModelBase CurrentViewModel
{
get
{
return _currentViewModel;
}
set
{
_currentViewModel = value;
RaisePropertyChanged(() => CurrentViewModel);
}
}
}
Obviously bind this to the Mainview (Just the relevant code):
<UserControl Content="{Binding Path=CurrentViewModel}"/>
Put the DataTemplate in the App.xaml:
<Application.Resources>
<ResourceDictionary>
<vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />
<DataTemplate DataType="{x:Type localViewModel:HomeViewModel }">
<localView:AccueilView/>
</DataTemplate>
<DataTemplate DataType="{x:Type localViewModel:ErrorViewModel }">
<localView:ErrorView/>
</DataTemplate>
</ResourceDictionary>
</Application.Resources>
Register the ViewModel with Simple IOC in the ViewModelLocator:
if (ViewModelBase.IsInDesignModeStatic)
{
SimpleIoc.Default.Register<IHomeViewModel, DesignHomeViewModel>();
}
else
{
SimpleIoc.Default.Register<IHomeViewModel, HomeViewModel>();
}
Set the getter of all the ViewModel in the ViewModelLocator to Static (just one for the exemple)
public static IHomeViewModel Home
{
get{return ServiceLocator.Current.GetInstance<IHomeViewModel>();}
}
Since it's static you can access the ViewModel you want from the MainViewModel:
public class MainViewModel : ViewModelBase,IMainViewModel
{
public ViewModelBase HomeVM
{
get
{
return (ViewModelBase)ViewModelLocator.Home;
}
}
}
Provide the ability to unregister the ViewModel and recreates it:
public static void CleanUpHome()
{
SimpleIoc.Default.Unregister<HomeViewModel>();
SimpleIoc.Default.Register<IHomeViewModel, HomeViewModel>();
}
The "child" View Model communicates with the MainViewModel through messages:
public class ErrorViewModel : ViewModelBase, IErrorViewModel
{
/*Other piece of code */
public void HomeReturn()
{
var msg = new ChangeView(ChangeView.EnumView.Home);
Messenger.Default.Send<ChangeView>(msg);
ViewModelLocator.CleanUpErrors();
}
}
The MainViewModel Register to the message and processes it:
public class MainViewModel : ViewModelBase,IMainViewModel
{
public MainViewModel()
{
Messenger.Default.Register<ChangeView>(this, (action) => ReceiveMessage(action));
CurrentViewModel = HomeVM;
}
private void ReceiveMessage(ChangeView viewName)
{
switch (viewName.switchView)
{
case ChangeView.EnumView.Home:
CurrentViewModel = HomeVM;
break;
case ChangeView.EnumView.Error:
CurrentViewModel = ErrorVM;
break;
}
Messenger.Default.Unregister<ChangeView>(this, (action) => ReceiveMessage(action));
}
That's all.

ContentControl not updating

I'm trying to have a MainWindow that is bound to the a view. I change that view in code and expect it to update in the Main Window, however that is not happening.
I have this code in my XAML
<Grid>
<ContentControl Content="{Binding Source={StaticResource ViewModelLocator}, Path=MainWindowViewModel.CurrentControl}" />
</Grid>
I then change my Control via this code
public class MainWindowViewModel : ReactiveObject
{
private UserControl _CurrentControl = null;
public UserControl CurrentControl
{
get
{
if (_CurrentControl == null)
{
_CurrentControl = new HomePage();
}
return _CurrentControl;
}
set
{
this.RaiseAndSetIfChanged(x => x.CurrentControl, value);
}
}
}
As you can see I'm using the ReactiveUI library.
Is ContentControl the wrong thing to use in that view or am I just not binding and updating correctly?
There is actually a far better way to do this, using ViewModelViewHost:
<Grid DataContext="{Binding ViewModel, ElementName=TheUserControl}">
<ViewModelViewHost ViewModel="{Binding CurrentControlViewModel}" />
</Grid>
Now, your class will look something like:
public class MainWindowViewModel : ReactiveObject
{
private ReactiveObject _CurrentControlViewModel = new HomePageViewModel();
public ReactiveObject CurrentControlViewModel {
get { return _CurrentControl; }
set { this.RaiseAndSetIfChanged(x => x.CurrentControlViewModel, value); }
}
}
And somewhere in your app's startup, you should write:
RxApp.Register(typeof(IViewFor<HomePageViewModel>), typeof(HomePage));
What's ViewModelViewHost?
ViewModelViewHost will take a ViewModel object that you provide via Bindings, and look up a View that fits it, using Service Location. The Register call is how you can associate Views with ViewModels.
why you call your class MainWindowViewModel? when you wanna do mvvm you shouldn't have properties with type UserControl in your VM.
the usual mvvm way looks like this:
viewmodel with INotifyPropertyChanged
public class MyViewmodel
{
public IWorkspace MyContent {get;set;}
}
xaml content control with binding to your VM
<ContentControl Content="{Binding MyContent}"/>
datatemplate --> so that wpf knows how to render your IWorkspace
<DataTemplate DataType="{x:Type local:MyIWorkSpaceImplementationType}" >
<view:MyWorkspaceView />
</DataTemplate>
I think you have several muddled concepts here and they are getting in each others way.
Firstly you aren't actually using ANY of the reactiveUI code, it never gets called. Since your get accessor implements a lazy instantiation pattern then it means the set accessor is ignored. This means that the view never notifies the property change, so you never get updates.
I'd recommend using something more like
private UserControl _currentControl;
public MainWindowVirwModel()
{
CurrentControl = new HomePage();
}
public UserControl CurrentControl
{
get { return _curentControl;}
set { this.RaiseAndSetIfChanged(...); }
}
In addition, this still mixes up View components i.e. HomePage, inside your ViewModel tier which will making unit testing far more difficult.

Changing the View for a ViewModel

I am trying to implement the MVVM design patern for mt WPF application. In order to connect the view to the viewmodels, I use a ResourceDictionary (used in Application.Resources), that looks like
<DataTemplate DataType={x:Type viewmodel:SampleViewModel}>
<view:SampleView1 />
</DataTemplate>
The view models are then simply put into content presenters to display them.
Now, when the user presses a button, I'd like to display SampleViewModel using a different view. How do I change the data template used for SampleViewModel?
Less words more code.
As far as you said, you have the class SampleViewModel. I added the property Title for demonstration and ViewType for identifying the correct view:
public enum ItemViewType { View1, View2 };
public class SampleViewModel
{
public string Title { get; set; }
public ItemViewType ViewType { get; set; }
}
The DataTemplateSelector for two views depending on the ViewType property:
class ItemViewTemplateSelector : DataTemplateSelector
{
public DataTemplate View1Template { get; set; }
public DataTemplate View2Template { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var vm = item as SampleViewModel;
if (vm == null)
return null;
switch (vm.ViewType)
{
case ItemViewType.View1:
return View1Template;
case ItemViewType.View2:
return View2Template;
}
return null;
}
}
Xaml code:
<Window.Resources>
<DataTemplate x:Key="view1Template">
<TextBlock Text="{Binding Title}" Foreground="Red"/>
</DataTemplate>
<DataTemplate x:Key="view2Template">
<TextBox Text="{Binding Title}" />
</DataTemplate>
<local:ItemViewTemplateSelector x:Key="viewTemplateSelector"
View1Template="{StaticResource view1Template}"
View2Template="{StaticResource view2Template}"/>
</Window.Resources>
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<StackPanel>
<Button Content="ChangeView" HorizontalAlignment="Center" Command="{Binding SwitchViewCommand}"/>
<ContentControl Content="{Binding ItemViewModel}" ContentTemplateSelector="{StaticResource viewTemplateSelector}"/>
</StackPanel>
The main part is in the class MainViewModel where I've put the logic for switching views:
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
this.ItemViewModel = new SampleViewModel { Title = "Some title", ViewType = ItemViewType.View1 };
this.SwitchViewCommand = new RelayCommand(() =>
{
this.ItemViewModel.ViewType = this.ItemViewModel.ViewType == ItemViewType.View1
? ItemViewType.View2
: ItemViewType.View1;
//The magic senquence of actions which forces a contentcontrol to change the content template
var copy = this.ItemViewModel;
this.ItemViewModel = null;
this.ItemViewModel = copy;
});
}
public RelayCommand SwitchViewCommand { get; set; }
private SampleViewModel itemViewModel;
public SampleViewModel ItemViewModel
{
get { return itemViewModel; }
set
{
itemViewModel = value;
RaisePropertyChanged("ItemViewModel");
}
}
}
The SwitchViewCommand can be any type of command, I use the command from the mvvmlight library.
Inside the handler of the command I change the type of viewmodel and update the property ItemViewModel in a tricky way because a ContentControl refreshes a view only if to change the Content property, and this property will not be changed unless you set a reference to different object.
I mean, even the code this.ItemViewModel = this.itemViewModel will not change the view.
It's strange, but the workaround doesn't require much work.
You can achieve this in many different ways depends upon the architecture you want.
You can write a custom DataTemplateSelector and use it on ContentControl.ContentTemplateSelector and choose those two templates appropriately
If this pattern of changing the view occures in many different places and more frequent UX, I would also recommend those two views toggled using a DataTemplate.DataTrigger based on a property in SampleViewModel [I am guessing you might have a distinguishing property in the ViewModel to know that state]
You can override the mapping by placing a similar resource lower down in the tree. Since WPF will resolve the resource by searching upwards, such an override will replace your existing mapping.

Categories

Resources