MVVM - Button command binding not working - c#

I am working on a WPF app using the MVVM pattern.
However , I am facing troubles with it, since it's the first time I try to use the MVVM pattern.
I have a main window view, and 2 UserControls, that I want to switch using Buttons. Easy as it is, I am stuck with the the following code, which should work but nothing happen when I click on the button:
MainView.xaml
<Window x:Class="WindowsClient.View.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:WindowsClient.ViewModel"
xmlns:local="clr-namespace:WindowsClient.View"
Height="768" Width="1366" MinHeight="768" MinWidth="1366">
<Window.Resources>
<DataTemplate DataType="{x:Type vm:HomeViewModel}">
<local:HomeView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:ProfilViewModel}">
<local:ProfilView/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="240"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- LeftMenu -->
<Border Grid.Column="0" Background="Black">
<StackPanel>
<Button Content="Home" Command="{Binding HomeViewCommand}"></Button>
<Button Content="Profil" Command="{Binding ProfilViewCommand}"></Button>
</StackPanel>
</Border>
<!-- Body -->
<ContentControl x:Name="Pages" Content="{Binding ContentControlViewModel}" Grid.Column="1"/>
</Grid>
MainViewModel.cs
namespace WindowsClient.ViewModel
{
public class MainWindowViewModel : INotifyPropertyChanged
{
private object contentControlViewModel;
public object ContentControlViewModel
{
get => contentControlViewModel;
set
{
contentControlViewModel = value;
OnPropertyChanged("ContentControlViewModel");
}
}
public MainWindowViewModel()
{
HomeViewCommand = new BaseCommand(OpenHome);
ProfilViewCommand = new BaseCommand(OpenProfil);
}
public ICommand HomeViewCommand { get; set; }
public ICommand ProfilViewCommand { get; set; }
public void OpenHome(object obj)
{
ContentControlViewModel = new HomeViewModel();
}
public void OpenProfil(object obj)
{
ContentControlViewModel = new ProfilViewModel();
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
public class BaseCommand : ICommand
{
private Predicate<object> _canExecute;
private Action<object> _method;
public event EventHandler CanExecuteChanged;
public BaseCommand(Action<object> method)
: this(method, null)
{
}
public BaseCommand(Action<object> method, Predicate<object> canExecute)
{
_method = method;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
if (_canExecute == null)
{
return true;
}
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_method.Invoke(parameter);
}
}
}
HomeView.xaml
<UserControl x:Class="WindowsClient.View.HomeView"
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="300" d:DesignWidth="300"
Height="768" Width="1126"
MinHeight="768" MinWidth="1126">
<Grid>
<ToggleButton x:Name="CommunityButton" Content="Community" Height="30" Width="200" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="-300,100,0,0" FontSize="18" />
<ToggleButton x:Name="NewsButton" Content="News" Height="30" Width="200" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="300,100,0,0" FontSize="18"/>
<ScrollViewer x:Name="HomeContentViewer" Margin="10,150,10,10"/>
</Grid>
</UserControl>
HomeViewModel.cs
namespace WindowsClient.ViewModel
{
internal class HomeViewModel
{
}
}
ProfilView and ProfilViewModel are pretty much the same as those two.
Anyway, when I click on a button, the view does not change and I can't understand why...

This bit is missing from your MainView.xaml:
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
You can add that just above your <Window.Resources> line.
That would be why nothing is being bound to your view from your viewmodel.

Related

Switch between views without losing data in WPF

I am working on a project that has one Main Window and two User Controls (Views).
The Main Window has a Menu on the left hand side with some buttons that activate one View or the other. On the right hand side of Main Window there's a Content Control that displays the CurrentView.
My idea was to have a Form for adding people on one View and a DataGrid displaying those people on the other. For that I made an ObservableColection that has People and bound that collection to the DataGrid on the corresponding View.
Everything is working as intended (with the Form I can add people to the collection and then those people get added to the DataGrid) except that when I add a person and then change the View to see if it got added to the DataGrid, nothing is added to the DataGrid but the headers.
If I put the same DataGrid with the same Binding in the first View (where the Form is) I can see that the people are getting added so I think the problem is that when I change Views I am losing the information about the collection.
``
This is the code I'm using to make the navigation between views available.
MainWindow
<Window x:Class="SwitchViewsDemo2.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:vm="clr-namespace:SwitchViewsDemo2.ViewModels"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<vm:NavigationViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel VerticalAlignment="Center">
<Button Command="{Binding GoToAddPeopleCommand}" Content="Go To Add People" />
<Button Command="{Binding GoToDisplayPeopleCommand}" Content="Go To Display People" />
</StackPanel>
<ContentControl Grid.Column="1" Content="{Binding CurrentView}" />
</Grid>
</Window>
AddPeopleView
<UserControl x:Class="SwitchViewsDemo2.Views.AddPeopleView"
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:vm="clr-namespace:SwitchViewsDemo2.ViewModels"
mc:Ignorable="d" FontSize="28"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<vm:AddPeopleViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="2*"/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="Add People" />
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock Text="First Name" />
<TextBox Text="{Binding FirstName}"/>
</StackPanel>
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Column="1">
<TextBlock Text="Last Name" />
<TextBox Text="{Binding LastName}"/>
</StackPanel>
</Grid>
<StackPanel Grid.Row="1">
<Button Content="Add Person" Command="{Binding AddPersonCommand}"
Height="40" Width="200" VerticalAlignment="Top"/>
<DataGrid ItemsSource="{Binding People}"/>
</StackPanel>
</Grid>
</UserControl>
DisplayPeopleView
<UserControl x:Class="SwitchViewsDemo2.Views.DisplayPeopleView"
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:vm="clr-namespace:SwitchViewsDemo2.ViewModels"
mc:Ignorable="d" FontSize="28"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<vm:DisplayPeopleViewModel/>
</UserControl.DataContext>
<Grid>
<StackPanel>
<TextBlock Text="Display People"/>
<DataGrid ItemsSource="{Binding People}"/>
</StackPanel>
</Grid>
</UserControl>
NavigationViewModel
using SwitchViewsDemo2.Utilities;
using System.Windows.Input;
namespace SwitchViewsDemo2.ViewModels
{
public class NavigationViewModel : Utilities.ViewModelBase
{
private object _currentView;
public object CurrentView
{
get { return _currentView; }
set { _currentView = value; OnPropertyChanged(); }
}
public ICommand GoToAddPeopleCommand { get; set; }
public ICommand GoToDisplayPeopleCommand { get; set; }
private void AddPeople(object obj) => CurrentView = new AddPeopleViewModel();
private void DisplayPeople(object obj) => CurrentView = new DisplayPeopleViewModel();
public NavigationViewModel()
{
GoToAddPeopleCommand = new RelayCommand(AddPeople);
GoToDisplayPeopleCommand = new RelayCommand(DisplayPeople);
// Startup View
CurrentView = new AddPeopleViewModel();
}
}
}
ViewModelBase
using SwitchViewsDemo2.Models;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace SwitchViewsDemo2.Utilities
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
private ObservableCollection<PersonModel> _people = new();
public ObservableCollection<PersonModel> People
{
get { return _people; }
set { _people = value; }
}
}
}
RelayCommand
using System;
using System.Windows.Input;
namespace SwitchViewsDemo2.Utilities
{
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
public event EventHandler? CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object? parameter) => _canExecute == null || _canExecute(parameter);
public void Execute(object? parameter) => _execute(parameter);
}
}
DataTemplate(Resource Dictionary)
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:SwitchViewsDemo2.ViewModels"
xmlns:view="clr-namespace:SwitchViewsDemo2.Views">
<DataTemplate DataType="{x:Type vm:AddPeopleViewModel}">
<view:AddPeopleView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:DisplayPeopleViewModel}">
<view:DisplayPeopleView/>
</DataTemplate>
</ResourceDictionary>
App.xaml
<Application x:Class="SwitchViewsDemo2.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SwitchViewsDemo2"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Utilities/DataTemplate.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
Edit:
This is how I add the data to the People object
using SwitchViewsDemo2.Models;
using SwitchViewsDemo2.Utilities;
namespace SwitchViewsDemo2.ViewModels
{
public class AddPeopleViewModel : ViewModelBase
{
private string _firstName;
public string FirstName
{
get { return _firstName; }
set { _firstName = value; OnPropertyChanged(); }
}
private string _lastName;
public string LastName
{
get { return _lastName; }
set { _lastName = value; OnPropertyChanged(); }
}
public ButtonCommand AddPersonCommand { get; set; }
public void AddPerson()
{
PersonModel p = new PersonModel()
{
FirstName = FirstName,
LastName = LastName
};
FirstName = string.Empty;
LastName = string.Empty;
People.Add(p);
}
public AddPeopleViewModel()
{
AddPersonCommand = new ButtonCommand(AddPerson);
}
}
}
ButtonCommand:
using System;
using System.Windows.Input;
namespace SwitchViewsDemo2.Utilities
{
public class ButtonCommand : ICommand
{
public event EventHandler? CanExecuteChanged;
private Action _execute;
public ButtonCommand(Action execute)
{
_execute = execute;
}
public bool CanExecute(object? parameter)
{
return true;
}
public void Execute(object? parameter)
{
_execute.Invoke();
}
}
}
Remove this:
<UserControl.DataContext>
<vm:AddPeopleViewModel/>
</UserControl.DataContext>
...and this:
<UserControl.DataContext>
<vm:DisplayPeopleViewModel/>
</UserControl.DataContext>
from the XAML markup of the views.
You are creating the AddPeopleViewModel and DisplayPeopleViewModel instances in the NavigationViewModel. You should not create additional instances of the view models in the views.
It's also unclear why're expecting adding an object to a collection in one view model should affect another one.
There should be only one collection of People. Now you have two in two different view models.

WPF ContentControl binding of dynamic content does not works properly

Again, I apologize in advance for any mistakes, English is not my native.
I'm making an MVVM app, and I want to dynamically change views using ContentControl in the MainWindow, here is a necessary part of the code to understand:
Firstly, views:
MainWindow.xaml
<Window x:Class="Vernam.MainWindow"
x:Name="MainWindowID"
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:Vernam"
xmlns:viewModel="clr-namespace:Vernam.ViewModels"
mc:Ignorable="d"
Height="600"
Width="900"
...>
<Window.Resources>
<viewModel:MainViewModel x:Key="vm"></viewModel:MainViewModel>
</Window.Resources>
...
<Grid>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"></ColumnDefinition>
<ColumnDefinition Width="40"></ColumnDefinition>
<ColumnDefinition Width="454"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
...
<StackPanel Grid.Column="2" Orientation="Horizontal"
DataContext="{Binding Source={StaticResource vm}}">
<RadioButton x:Name="CorrRadioButton"
Content="Correspondences"
Width="176"
Foreground="White"
FontSize="18"
Style="{StaticResource HeadButtonTheme}"
GroupName="Head"
IsChecked="True"
Command="{Binding Path=CorrCommand}"/>
<RadioButton x:Name="ProfileRadioButton"
Content="Profile"
Width="89"
Foreground="White"
FontSize="18"
Style="{StaticResource HeadButtonTheme}"
GroupName="Head"
Command="{Binding Path=ProfileCommand}"/>
</StackPanel>
...
</Grid>
<ContentControl Grid.Row="1"
Content="{Binding CurrentView}"/>
</Grid>
</Border>
</Window>
and MainWindow.xaml.cs:
public partial class MainWindow : Window
{
bool isMinimized = false;
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
}
Two views, that I want to be shown in MainWindow:
CorrespondensesView.xaml
<UserControl x:Class="Vernam.Views.CorrespondensesView"
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:Vernam.Views"
mc:Ignorable="d"
Height="540"
Width="900">
<Grid Background="#035251">
...
</Grid>
</UserControl>
CorrespondensesView.xaml.cs
public partial class CorrespondensesView : UserControl
{
public CorrespondensesView()
{
InitializeComponent();
}
}
ProfileView.xaml:
<UserControl x:Class="Vernam.Views.ProfileView"
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:Vernam.Views"
mc:Ignorable="d"
Height="540"
Width="900">
<Grid Background="#035251">
...
</Grid>
</UserControl>
ProfileView.xaml.cs:
public partial class ProfileView : UserControl
{
public ProfileView()
{
InitializeComponent();
}
}
Here are MainWindow view model
namespace Vernam.ViewModels
{
public class MainViewModel : ObservableObject
{
private RelayCommand corrCommand;
private RelayCommand profileCommand;
private object currentView;
public CorrespondensesViewModel CorrVM { get; set; }
public ProfileViewModel ProfileVM { get; set; }
public object CurrentView
{
get { return currentView; }
set
{
currentView = value;
this.OnPropertyChanged("CurrentView");
}
}
public RelayCommand CorrCommand
{
get
{
return corrCommand ??
(corrCommand = new RelayCommand(obj =>
{
CurrentView = CorrVM;
}));
}
}
public RelayCommand ProfileCommand
{
get
{
return profileCommand ??
(profileCommand = new RelayCommand(obj =>
{
CurrentView = ProfileVM;
}));
}
}
public MainViewModel()
{
CorrVM = new CorrespondensesViewModel();
ProfileVM = new ProfileViewModel();
CurrentView = CorrVM;
}
}
}
CorrespondencesViewModel and ProfileViewModel are empty.
And, finally, App.xaml
<Application x:Class="Vernam.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Vernam"
xmlns:viewModel="clr-namespace:Vernam.ViewModels"
xmlns:view="clr-namespace:Vernam.Views"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
...
<DataTemplate DataType="{x:Type viewModel:CorrespondensesViewModel}">
<view:CorrespondensesView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:ProfileViewModel}">
<view:ProfileView/>
</DataTemplate>
</ResourceDictionary>
</Application.Resources>
</Application>
You may need to look at the ObservableObject class:
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, e);
}
}
}
Then I run the app, I can actually see view, that I assign to CurrentView in MainViewModel constructor:
public MainViewModel()
{
CorrVM = new CorrespondensesViewModel();
ProfileVM = new ProfileViewModel();
CurrentView = CorrVM;
}
If I assign CorrVM or ProfileVM to CurrentView, I actually see CorrespondensesView or ProfileView, but I can't change view dynamically:
RadioButton Command binding works properly, CurrentView is reassigned every time I click on the corresponding button, but I can't see any changes in MainWindow.
So I think the problem is in the binding, do you have any ideas how to fix it?
UPD:
Get section of this property is called only during the initialization, so the problem is definitely with binding.
public ObservableObject CurrentView
{
get { return currentView; }
set
{
currentView = value;
this.OnPropertyChanged("CurrentView");
}
}
Tried to use different binding modes and update triggers, but to no avail.
<ContentControl Grid.Row="1"
Content="{Binding Path=CurrentView, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"/>
I believe that because you are using two different view model instances for the radio buttons and the content view in MainWindow.
Here you use an instance from the window resource.
<StackPanel Grid.Column="2" Orientation="Horizontal"
DataContext="{Binding Source={StaticResource vm}}">
Here you use an instance from the data context of MainWindow.
<ContentControl Grid.Row="1" Grid.Column="0"
Content="{Binding CurrentView}" />
So the fixing is quite simple, use only one of them.
Try the following:
Create the following folders:
Model
Theme
View
ViewModel
Add the files below to the appropriate folder (see image above).
HeadButtonTheme.xaml (ResourceDictionary)
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style BasedOn="{StaticResource {x:Type ToggleButton}}"
TargetType="{x:Type RadioButton}"
x:Key="HeadButtonTheme">
<Style.Setters>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RadioButton">
<Grid VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Background="{TemplateBinding Background}">
<TextBlock Text="{TemplateBinding Property=Content}"
VerticalAlignment="Center"
Margin="5, 0, 0, 0"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
</Style.Setters>
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Background" Value="#22202f" />
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
ObservableObject.cs (Class)
using System;
using System.ComponentModel;
using System.Windows.Input;
using System.Runtime.CompilerServices;
namespace Vernam
{
class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, e);
}
}
}
}
RelayCommand.cs (Class)
using System;
using System.Windows.Input;
namespace Vernam
{
class RelayCommand : ICommand
{
private Action<object> _execute;
private Func<object, bool> _canExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested += value; }
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
}
CorrespondencesViewModel.cs (Class)
namespace Vernam.ViewModel
{
class CorrespondencesViewModel
{
}
}
CorrespondencesView.xaml (UserControl)
<UserControl x:Class="Vernam.View.CorrespondencesView"
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:Vernam.View"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<TextBlock Text="Correspondences" Foreground="Red"/>
</Grid>
</UserControl>
ProfileViewModel.cs (Class)
namespace Vernam.ViewModel
{
class ProfileViewModel
{
}
}
ProfileView.xaml (UserControl)
<UserControl x:Class="Vernam.View.ProfileView"
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:Vernam.View"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<TextBlock Text="Profile" Foreground="Red"/>
</Grid>
</UserControl>
MainViewModel.cs (Class)
namespace Vernam.ViewModel
{
class MainViewModel : ObservableObject
{
public RelayCommand CorrCommand { get; set; }
public RelayCommand ProfileCommand { get; set; }
public CorrespondencesViewModel CorrVM { get; set; }
public ProfileViewModel ProfileVM { get; set; }
private object currentView;
public object CurrentView
{
get { return currentView; }
set
{
currentView = value;
OnPropertyChanged("CurrentView");
}
}
public MainViewModel()
{
//create new instance
CorrVM = new CorrespondencesViewModel();
ProfileVM = new ProfileViewModel();
CorrCommand = new RelayCommand(obj =>
{
CurrentView = CorrVM;
});
ProfileCommand = new RelayCommand(obj =>
{
CurrentView = ProfileVM;
});
//set default view
CurrentView = CorrVM;
}
}
}
App.xaml
<Application x:Class="Vernam.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Vernam"
xmlns:viewModel="clr-namespace:Vernam.ViewModel"
xmlns:view="clr-namespace:Vernam.View"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Theme\HeadButtonTheme.xaml" />
</ResourceDictionary.MergedDictionaries>
<DataTemplate DataType="{x:Type viewModel:CorrespondencesViewModel}">
<view:CorrespondencesView />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:ProfileViewModel}">
<view:ProfileView />
</DataTemplate>
</ResourceDictionary>
</Application.Resources>
</Application>
MainWindow.xaml
<Window x:Class="Vernam.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:local="clr-namespace:Vernam"
xmlns:viewModel="clr-namespace:Vernam.ViewModel"
xmlns:view="clr-namespace:Vernam.View"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<viewModel:MainViewModel x:Key="vm"></viewModel:MainViewModel>
</Window.Resources>
<Border Background="LightGray">
<Grid DataContext="{Binding Source={StaticResource vm}}">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="350" />
<ColumnDefinition Width="350" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal">
<RadioButton x:Name="CorrRadioButton"
Content="Correspondences"
Width="176"
Foreground="White"
FontSize="18"
Style="{StaticResource HeadButtonTheme}"
GroupName="Head"
IsChecked="True"
Command="{Binding CorrCommand}"/>
<RadioButton x:Name="ProfileRadioButton"
Content="Profile"
Width="89"
Foreground="White"
FontSize="18"
Style="{StaticResource HeadButtonTheme}"
GroupName="Head"
Command="{Binding ProfileCommand}"/>
</StackPanel>
</Grid>
<ContentControl Grid.Row="1" Grid.Column="0"
Content="{Binding CurrentView}" />
</Grid>
</Border>
</Window>
Resources:
WPF C# Professional Modern Flat UI Tutorial

ContentControl view not updating when changed outside of ViewModel constructor

My ContentControl in my MainMenuView will update when I set it in the constructor, but not after that. I'm trying to get it to change on button click. The command appears to be working and changes the values of my CurrentPageViewModel. I'm following this code example for changing the view https://rachel53461.wordpress.com/2011/12/18/navigation-with-mvvm-2/.
MainMenuViewModel:
public MainMenuViewModel()
{
SelectViewCommand = new RelayCommand<IPageViewModel>(s => ChangeViewModel(s));
PageViewModels.Add(new ClockViewModel());
CurrentPageViewModel = PageViewModels[1];
}
private List<IPageViewModel> pageViewModels;
public List<IPageViewModel> PageViewModels
{
get
{
if (pageViewModels == null)
pageViewModels = new List<IPageViewModel>();
return pageViewModels;
}
}
private IPageViewModel currentPageViewModel;
public IPageViewModel CurrentPageViewModel
{
get { return currentPageViewModel; }
set { currentPageViewModel = value; OnPropertyChanged("currentPageViewModel"); }
}
public RelayCommand<IPageViewModel> SelectViewCommand { get; private set; }
private void ChangeViewModel(IPageViewModel viewModel)
{
if (!PageViewModels.Contains(viewModel))
PageViewModels.Add(viewModel);
CurrentPageViewModel = PageViewModels
.FirstOrDefault(vm => vm == viewModel);
}
private void OnPropertyChanged(string propertyName)
{
if (string.IsNullOrEmpty(propertyName))
throw new ArgumentNullException("propertyName");
var changed = PropertyChanged;
if (changed != null)
{
changed(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
MainMenuView:
<Window x:Class="TextApp.View.MainMenuView"
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:TextApp.View"
xmlns:local1="clr-namespace:TextApp.ViewModel"
mc:Ignorable="d"
Title="MainMenuView" Height="450" Width="800">
<Window.Resources>
<DataTemplate DataType="{x:Type local1:ClockViewModel}">
<local:ClockView/>
</DataTemplate>
<DataTemplate DataType="{x:Type local1:IPageViewModel}">
<local:IPageView/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.3*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<ItemsControl ItemsSource="{Binding PageViewModels}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}"
Command="{Binding DataContext.SelectViewCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding}"
Margin="2,5"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
<ContentControl Grid.Column="1" Content="{Binding CurrentPageViewModel}"/>
</Grid>

Edits in wpf window don't update Observable object

Why do edits in my child window not affect the collection which I'm directly modifying from my main view models data context? When i click properties...it opens a seconds window where users can change the names of the people in the collection. However when i reopen the properties window Doug's name will be set back to default. I plan on making it possible for users to add/remove people from within this window as well. I'm a bit perplexed on why it wouldn't directly change and affect the bound data.
Main Window
Child Window
MainWindowViewModel.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace WpfApplication1
{
public class MainWindowViewModel : NotifyBase
{
private ObservableCollection<Person> crowd;
public ObservableCollection<Person> Crowd
{
get { return crowd ?? (crowd = new ObservableCollection<Person>()); }
set { Set(ref crowd, value); }
}
private ICommand mainWindowCommand;
public ICommand MainWindowCommand
{
get
{
return mainWindowCommand ?? (mainWindowCommand = new RelayCommand<CommandTargets>(ExecuteCommand));
}
}
private void ExecuteCommand(CommandTargets parameter)
{
switch (parameter)
{
case CommandTargets.EditProperties:
PropertiesView PropertiesWindow = new PropertiesView();
PropertiesWindow.Show();
break;
}
}
public MainWindowViewModel()
{
Crowd.Add(new Person("Doug"));
}
}
public enum CommandTargets
{
EditProperties,
}
public class Person
{
public string Name { get; set; }
public Person( string name)
{
this.Name = name;
}
}
class RelayCommand<T> : ICommand
{
private readonly Func<T, bool> _canExecuteHandler;
private readonly Action<T> _executeHandler;
public RelayCommand(Action<T> executeHandler)
: this(executeHandler, null)
{ }
public RelayCommand(Action<T> executeHandler, Func<T, bool> canExecuteHandler)
{
_canExecuteHandler = canExecuteHandler;
_executeHandler = executeHandler;
}
public bool CanExecute(object parameter)
{
return _canExecuteHandler != null ? _canExecuteHandler((T)parameter) : true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_executeHandler((T)parameter);
}
public void RaiseCanExecuteChanged()
{
EventHandler handler = CanExecuteChanged;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
}
public abstract class NotifyBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
protected bool Set<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
RaisePropertyChanged(propertyName);
return true;
}
}
}
MainWindow.xaml
<Window x:Class="WpfApplication1.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:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="250" Width="250"
WindowStartupLocation="CenterScreen">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Menu DockPanel.Dock="Top">
<Menu.Resources>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding MainWindowCommand}"/>
</Style>
</Menu.Resources>
<MenuItem Header="_File">
<MenuItem Header="_Properties..." CommandParameter="{x:Static local:CommandTargets.EditProperties}"/>
</MenuItem>
</Menu>
</Grid>
</Window>
PropertiesView.xaml
<Window x:Class="WpfApplication1.PropertiesView"
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:WpfApplication1"
mc:Ignorable="d"
Title="PropertiesView" Height="300" Width="300"
WindowStartupLocation="CenterScreen">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListView Grid.Row="0"
x:Name="ItemList"
ItemsSource="{Binding Crowd, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
HorizontalContentAlignment="Stretch"
SelectionMode="Single"
SelectedItem="{Binding SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="100" DisplayMemberBinding="{Binding Name}" />
</GridView>
</ListView.View>
</ListView>
<StackPanel Grid.Row="1" DataContext="{Binding ElementName=ItemList, Path=SelectedItem}">
<Label Grid.Column="0" Grid.Row="0" Content="Name: " VerticalAlignment="Center" Foreground="Black"/>
<TextBox Grid.Column="1" Grid.Row="0" Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"
Margin="5"/>
</StackPanel>
</Grid>
</Window>
You are using different instances of the view model. You'll need to share the same instance for the editing to work.
private void ExecuteCommand(CommandTargets parameter)
{
switch (parameter)
{
case CommandTargets.EditProperties:
PropertiesView PropertiesWindow = new PropertiesView();
PropertiesWindow.DataContext=this; //<----------- add this line
PropertiesWindow.Show();
break;
}
}
This is happening because each PropertiesView is using its own instance of the MainWindowViewModel so the two Views are referring to different ObservableCollections
I think the affect you are going for can be seen by making the private crowd member variable static.
private static ObservableCollection<Person> crowd;

Navigate to other page IocContainers and MVVM light

I'm making a windows universal 10 application with MVVM light.
But now I will, if I click on an item on the ShowWeatherPage be navigate to ShowWeatherDetailPage for more details about the clicked item. But I don't know how I can do this. Can you help me to do this?
Below you can find my code. I use IocContainers and for each page a viewmodel and only command bindings.
IocContainerpublic class IocContainer
{
static IocContainer()
{
SimpleIoc.Default.Register<ApplicationViewModel>(false);
SimpleIoc.Default.Register<ShowWeatherViewModel>(false);
SimpleIoc.Default.Register<ShowWeatherPage>(false);
SimpleIoc.Default.Register<ShowWeatherDetailPage>(false);
SimpleIoc.Default.Register<ShowWeatherDetailViewModel>(false);
}
public static ShowWeatherPage ShowWeatherPage
{
get { return SimpleIoc.Default.GetInstance<ShowWeatherPage>(); }
}
public static ShowWeatherViewModel ShowWeatherViewModel
{
get { return SimpleIoc.Default.GetInstance<ShowWeatherViewModel>(); }
}
public static ApplicationViewModel ApplicationViewModel
{
get { return SimpleIoc.Default.GetInstance<ApplicationViewModel>(); }
}
public static ShowWeatherDetailPage ShowWeatherDetailPage
{
get { return SimpleIoc.Default.GetInstance<ShowWeatherDetailPage>(); }
}
public static ShowWeatherDetailViewModel ShowWeatherDetailViewModel
{
get { return SimpleIoc.Default.GetInstance<ShowWeatherDetailViewModel>(); }
}
}
View modelsApplicationViewModelpublic class ApplicationViewModel: ViewModelBaseClass
{
private Page _currentPage = IocContainer.ShowWeatherPage;
public Page CurrentPage
{
get
{
return _currentPage;
}
set
{
if (_currentPage != value)
{
_currentPage = value;
OnPropertyChanged();
}
}
}
public void Navigate(Page page, object attribs)
{
CurrentPage = page;
}
}
ShowWeatherViewModelpublic class ShowWeatherViewModel: ViewModelBaseClass
{
#region variables
private Item _selectedVillage = null;
#endregion variables
#region properties
public Item SelectedVillage
{
get
{
return _selectedVillage;
}
set
{
if (_selectedVillage != value)
{
_selectedVillage = value;
ShowDetailPage();
}
}
}
#endregion properties
#region constructor
public ShowWeatherViewModel()
{ }
#endregion constructor
#region methodes
private void ShowDetailPage()
{
ApplicationViewModel appVm = new ApplicationViewModel();
appVm.Navigate(IocContainer.ShowWeatherPage, SelectedVillage);
}
#endregion methodes
}
ShowWeatherDetailViewModelpublic class ShowWeatherDetailViewModel: ViewModelBaseClass
{ }
ViewModelBaseClasspublic class ViewModelBaseClass: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
PagesMainPage<Page
x:Class="BALaboVoorbeeld.UWP.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:BALaboVoorbeeld.UWP"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
DataContext="{Binding Source={StaticResource ioc}, Path=ApplicationViewModel}"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Page Content="{Binding CurrentPage, Mode=TwoWay}" />
</Grid>
</Page>
ShowWeatherPage<Page
x:Class="BALaboVoorbeeld.UWP.Pages.ShowWeatherPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:BALaboVoorbeeld.UWP.Pages"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
DataContext="{Binding Source={StaticResource ioc}, Path=ShowWeatherViewModel}"
mc:Ignorable="d" Width="450">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="240" />
<ColumnDefinition Width="60" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1" />
<RowDefinition Height="40" />
<RowDefinition Height="1*" />
<RowDefinition Height="40" />
</Grid.RowDefinitions>
<TextBlock Text="Village:"
HorizontalAlignment="Right" Margin="4" VerticalAlignment="Center"
Grid.Row="1" Grid.Column="0" />
<Button HorizontalAlignment="Stretch" Margin="4" VerticalAlignment="Center"
Grid.Row="1" Grid.Column="2" Command="{Binding ShowWeahter}" >
<SymbolIcon Symbol="Find" />
</Button>
<ListBox Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3"
ItemContainerStyle="{StaticResource lstidflt}"
SelectedItem="{Binding SelectedVillage, Mode=TwoWay}"
ItemTemplate="{StaticResource weatheritemdt}"
ItemsSource="{Binding VillageList}" />
</Grid>
</Page>
ShowWeatherDetailPage<Page
x:Class="BALaboVoorbeeld.UWP.Pages.ShowWeatherDetailPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:BALaboVoorbeeld.UWP.Pages"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock Text="Yes we did it ☻"/>
</Grid>
</Page>
You can use MVVM Light's navigation service to navigate to another view.
http://www.mvvmlight.net/doc/nav1.cshtml
https://marcominerva.wordpress.com/2014/10/10/navigationservice-in-mvvm-light-v5/

Categories

Resources