I've spent the last days reading and trying to apply the Navigation pattern from this page: https://rachel53461.wordpress.com/2011/12/18/navigation-with-mvvm-2/
Now, after I got my project to work I'm really confused about how the binding works here. At first I have to clarify that I don't want a Navigation pane which is always visible like in the given example. I just want to use my MainView for navigation and each "SubView" should be able to go back to it's "parent" only.
Here's what I've got:
Project: APP
Class: App.xaml.cs
protected override void OnStartup(StartupEventArgs e) {
base.OnStartup(e);
UI.View.Main.MainView app = new UI.View.Main.MainView();
UI.View.Main.MainViewModel viewModel = new UI.View.Main.MainViewModel(some dependencies);
app.DataContext = viewModel;
app.Show();
}
ViewModel Base Class
public abstract class BaseViewModel : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
private string _name;
public string Name {
get {
return _name;
}
set {
if (Name != value) {
_name = value;
OnPropertyChanged("Name");
}
}
}
private BaseViewModel _homePage;
public BaseViewModel HomePage {
get {
return _homePage;
}
set {
if (HomePage != value) {
_homePage = value;
OnPropertyChanged("HomePage");
}
}
}
public void OnPropertyChanged(string propertyName) {
PropertyChangedEventHandler temp = PropertyChanged;
if (temp != null) {
temp(this, new PropertyChangedEventArgs(propertyName));
}
}
}
MainViewModel
namespace SGDB.UI.View.Main {
public class MainViewModel : BaseViewModel {
private BaseViewModel _currentPageViewModel;
public BaseViewModel CurrentPageViewModel {
get {
return _currentPageViewModel;
}
set {
if (CurrentPageViewModel != value) {
_currentPageViewModel = value;
OnPropertyChanged("CurrentPageViewModel");
}
}
}
public List<BaseViewModel> PageViewModels { get; private set; }
public RelayCommand ChangePageCommand {
get {
return new RelayCommand(p => ChangeViewModel((BaseViewModel)p), p => p is BaseViewModel);
}
}
//Some Dependencies
public List<BaseViewModel> ViewPages { get; private set; }
public MainViewModel(some dependencies) {
HomePage = new HomeViewModel() { Name = "TEST" };
//assign dependencies
var uavm = new UserAdministration.UserAdministrationViewModel(_userUnitOfWork, _personUnitOfWork) {
Name = Resources.Language.Sys.UserAdministartionTitle
};
PageViewModels = new List<BaseViewModel>();
PageViewModels.Add(uavm);
ChangeViewModel(HomePage);
}
public void ChangeViewModel(BaseViewModel viewModel) {
CurrentPageViewModel = viewModel;
}
}
}
MainView
<Window x:Class="SGDB.UI.View.Main.MainView"
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:local="clr-namespace:SGDB.UI.View.Main"
xmlns:ua="clr-namespace:SGDB.UI.View.UserAdministration"
xmlns:home="clr-namespace:SGDB.UI.View.Home"
mc:Ignorable="d"
Title="MainView" Height="400" Width="800">
<Window.Resources>
<DataTemplate DataType="{x:Type home:HomeViewModel}">
<home:Home/>
</DataTemplate>
<DataTemplate DataType="{x:Type ua:UserAdministrationViewModel}">
<ua:UserAdministration/>
</DataTemplate>
</Window.Resources>
<ContentControl Content="{Binding CurrentPageViewModel}"/>
HomeViewModel
public class HomeViewModel : BaseViewModel {
public RelayCommand TestCommand {
get {
return new RelayCommand((x) => MessageBox.Show(x.ToString()), (x) => true);
}
}
}
HomeView
<UserControl x:Class="SGDB.UI.View.Home.Home"
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:local="clr-namespace:SGDB.UI.View.Home"
xmlns:controls="clr-namespace:SGDB.UI.Controls"
xmlns:resx="clr-namespace:SGDB.UI.Resources.Language"
mc:Ignorable="d"
d:DesignHeight="400" d:DesignWidth="800">
<Grid>
<Grid.Resources>
<Style TargetType="controls:ModernButton">
<Setter Property="Margin" Value="1"/>
<Setter Property="FontFamily" Value="Bosch Office Sans"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Size" Value="155"/>
</Style>
</Grid.Resources>
<Grid.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStop Color="#26688B" Offset="1"/>
<GradientStop Color="#11354C" Offset="0"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Grid.Background>
<Grid.RowDefinitions>
<RowDefinition Height="60"/>
<RowDefinition Height="3*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="25"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="150"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" Grid.Column="0" HorizontalAlignment="Center">
<StackPanel.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontFamily" Value="Bosch Office Sans"/>
</Style>
</StackPanel.Resources>
<TextBlock Text="{x:Static resx:Sys.ApplicationTitle}" FontSize="20" FontWeight="Bold" Margin="5"/>
<TextBlock Text="{x:Static resx:Sys.ApplicationSubTitle}" FontSize="12" FontWeight="Light"/>
</StackPanel>
<WrapPanel Grid.Row="1"
Grid.Column="0"
FlowDirection="LeftToRight"
HorizontalAlignment="Left"
Width="367">
<ItemsControl ItemsSource="{Binding DataContext.PageViewModels, RelativeSource={RelativeSource AncestorType={x:Type Window}}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:ModernButton Background="Dark"
Text="{Binding Name}"
Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Content="{Binding Name}" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding HomePage}"/>
// This Button is always disabled although HomePage is of Type HomeViewModel which is based on BaseViewModel.
</WrapPanel>
</Grid>
My questions are:
- Why does the HomeView knot that the HomeViewModel is it's ViewModel? I do not define it anywhere in my code.
- Why does the Binding on the Name Property work but binding to the HomePage Property doesn't? Both of them are defined in the BaseViewModel class.
Update 1:
RelayCommand class:
public class RelayCommand : ICommand {
public event EventHandler CanExecuteChanged;
readonly Action<object> _action;
readonly Predicate<object> _predicate;
public RelayCommand(Action<object> action, Predicate<object> predicate) {
_action = action;
_predicate = predicate;
}
public RelayCommand(Action<object> action) {
_action = action;
_predicate = ((x) => true);
}
public bool CanExecute(object parameter) {
return _predicate(parameter);
}
public void Execute(object parameter) {
_action(parameter);
}
}
Update 2:
What's the actual problem?
<Button Content="{Binding Name}" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding HomePage}"/>
The Content gets bound properly but the CommandParameter (HomePage) which should be of Type BaseViewModel won't get validated through the Command's CanExecute. Both the Properties, Name and HomePage are defined inside the BaseViewModel.
Update 3:
<Button Content="{Binding Name}" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding DataContext.HomePage, ElementName=Test}"/>