There are multiple questions above. To keep it simple, I will try and answer the part about navigation in the MainWindow using the
MVVM Design Pattern.
I'm sure that there are a lot of people have different ideas on what is the best solution and there are many libraries that implement a variety of solutions based on the
MVVM Design Pattern using Dependency Injection (DI) and Inversion of Control (IOC).
For solutions that use navigation, WPF has the
Frame[
^] control and the
Page[
^] view.
If you are looking for something more lightweight, you can use
ContentPresenter[
^] control to host UserControl views. This is the solution that I'll use below.
Because we are using the
MVVM Design Pattern, we cannot directly reference controls from the
ViewModel. So we're going to use
ViewModel navigation and rely on WPF's
Data Binding and
Implicit Templates do load the
UserControl views.
First, we need to add some core mechanisms for DataBinding for properties and Commanding for the Button events. These are core to the
MVVM Design Pattern.
1.
INotiftyPropertyChanged
public abstract class ObservableBase : INotifyPropertyChanged
{
public void Set<TValue>(ref TValue field,
TValue newValue,
[CallerMemberName] string propertyName = "")
{
if (EqualityComparer<TValue>.Default.Equals(field, default(TValue))
|| !field.Equals(newValue))
{
field = newValue;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
2.
ViewModelBase
public abstract class ViewModelBase : ObservableBase
{
public bool IsInDesignMode()
{
return Application.Current.MainWindow == null
? true
: DesignerProperties.GetIsInDesignMode(Application.Current.MainWindow);
}
}
3.
ICommand
public class RelayCommand<T> : ICommand
{
private readonly Action<T> execute;
private readonly Predicate<T> canExecute;
public RelayCommand(Action<T> execute, Predicate<T> canExecute = null)
{
this.execute = execute ?? throw new ArgumentNullException("execute");
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
=> canExecute == null || canExecute((T)parameter);
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public void Execute(object parameter)
=> execute(parameter == null
? default(T)
: (T)Convert.ChangeType(parameter, typeof(T)));
}
Next, we need to set up our
pages. There are two parts:
ViewModel and the
View (UserControl). IT is always good form to keep the
ViewModel and the
View with the same prefix.
4.
[Page x]ViewModels
public interface IPage
{
string Title { get; }
}
public abstract class PqgeViewModelBase : ViewModelBase, IPage
{
public PqgeViewModelBase(string title)
{
Title = title;
if (!IsInDesignMode())
{
Title += " is alive!";
}
}
public string Title { get; }
}
public class Page1ViewModel : PqgeViewModelBase
{
public Page1ViewModel() : base(nameof(Page1ViewModel))
{
}
}
public class Page2ViewModel : PqgeViewModelBase
{
public Page2ViewModel() : base(nameof(Page2ViewModel))
{
}
}
5.
[Page x]Views
<UserControl x:Class="WpfCPNav.Page1View"
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"
mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800">
<Viewbox>
<TextBlock Text="{Binding Title}"/>
</Viewbox>
</UserControl>
<UserControl x:Class="WpfCPNav.Page2View"
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"
mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800">
<Viewbox>
<TextBlock Text="{Binding Title}"/>
</Viewbox>
</UserControl>
Now that we have the pages, we are ready to host them in the
MainWindow
of the app. The
MainViewModel
will handle the switching of the views.
6.
MainViewModel
public class MainViewModel : ViewModelBase
{
private IPage content;
public IPage Content { get => content; private set => Set(ref content, value); }
public RelayCommand<int> NavigateCommand => new RelayCommand<int>(Navigate);
private readonly Dictionary<int, Lazy<IPage>> pages
= new Dictionary<int, Lazy<IPage>>
{
[1] = new Lazy<IPage>(() => new Page1ViewModel()),
[2] = new Lazy<IPage>(() => new Page2ViewModel())
};
public MainViewModel() => Navigate(1);
private void Navigate(int value) => Content = pages[value].Value;
}
Lastly, we need to do the User interface. As we are using Implicit templates, we define them in the
MainWindow
.
7.
MainWindow
<Window x:Class="WpfCPNav.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:v="clr-namespace:WpfCPNav"
xmlns:vm="clr-namespace:WpfCPNav"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" WindowStartupLocation="CenterScreen"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type vm:Page1ViewModel}">
<v:Page1View />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:Page2ViewModel}">
<v:Page2View />
</DataTemplate>
<Style TargetType="Button">
<Setter Property="Margin" Value="10"/>
<Setter Property="Padding" Value="10 5"/>
</Style>
<Style TargetType="StackPanel">
<Setter Property="Orientation" Value="Horizontal"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel>
<Button Content="PAGE 1" Command="{Binding NavigateCommand}" CommandParameter="1" />
<Button Content="PAGE 2" Command="{Binding NavigateCommand}" CommandParameter="2" />
</StackPanel>
<ContentPresenter Content="{Binding Content}" Grid.Row="1"/>
</Grid>
</Window>
How it works:
When a
Button
is clicked, the
MainViewModel
will pass the selected
MainViewModel
to the
ContentPresenter
control, the
ContentPresenter
control will them look for a template and loads it diplaying the associated UserControl and the
ViewModel is is automatically is automatically set to the UserControl
DataContext
property and
DataBinding is applied.