WPF ContentControl binding of dynamic content does not works properly - c#

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

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 XAML Control Binding Not Updating Even With INotifyProperty Changed

I have a ContentControl in DayView.xaml whose content binds to the CurrentSongViewModel property in DayViewModel.cs . The CurrentSongViewModel is DataTemplated in the ContentControl to display its respective view depending on the view model (either DefaultSongViewModel or EditSongViewModel), and both of the DataTemplates are confirmed to work. When I click the 'Edit' button on DefaultSongView.xaml, the EditSongCommand executes and sets the CurrentSongViewModel to a new EditSongViewModel. The CurrentSongViewModel setter calls OnPropertyChanged(), but the ContentControl content is not updating! I have set break points on the OnPropertyChanged() call and it is calling it. I have no idea why its not updating...
DayView.xaml
<UserControl x:Class="Calandar.Views.DayView"
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:Calandar.Views"
xmlns:viewmodels="clr-namespace:Calandar.ViewModels"
d:DataContext="{d:DesignInstance Type=viewmodels:DayViewModel}"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="LightSteelBlue">
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="Day" Grid.Row="0" FontSize="35" FontFamily="Yu Gothic UI Semibold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="400"/>
<ColumnDefinition Width="400"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" >
<Border Style="{StaticResource PurpleBorder}">
<!-- The binding that isnt working -->
<ContentControl Content="{Binding CurrentSongViewModel}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type viewmodels:DefaultSongViewModel}">
<local:DefaultSongView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:EditSongViewModel}">
<local:EditSongView/>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</Border>
</StackPanel>
</Grid>
</Grid>
</UserControl>
DefaultSongView.xaml
<UserControl x:Class="Calandar.Views.DefaultSongView"
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:Calandar.Views"
xmlns:viewmodels="clr-namespace:Calandar.ViewModels"
d:DataContext="{d:DesignInstance Type=viewmodels:DayViewModel}"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.DataContext>
<viewmodels:DayViewModel/>
</Grid.DataContext>
<StackPanel>
<DockPanel >
<Button Content="Edit" Command="{Binding EditSongCommand}"
Style="{StaticResource CollectionModifierButton}" DockPanel.Dock="Right"/>
<TextBlock Text="Songs" Style="{StaticResource BoxTitleText}" DockPanel.Dock="Top"/>
</DockPanel>
<ListBox ItemsSource="{Binding SongList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=.}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>
</UserControl>
DayViewModel.cs
public class DayViewModel : ViewModelBase
{
// content view models
private ViewModelBase _currentSongViewModel;
public ViewModelBase CurrentSongViewModel
{
get { return _currentSongViewModel; }
set
{
_currentSongViewModel = value;
OnPropertyChanged(nameof(CurrentSongViewModel));
}
}
// Song list
public ObservableCollection<string> SongList { get; set; }
// Commands
public ICommand EditSongCommand => new EditSongsCommand(this);
// Constructor
public DayViewModel()
{
_currentSongViewModel = new DefaultSongViewModel();
SongList = new ObservableCollection<string>();
foreach (string line in File.ReadLines(#"C:\Users\person\source\repos\Calandar\DataFiles\Songs.txt"))
{
SongList.Add(line);
}
}
}
EditSongCommand.cs
public class EditSongsCommand : CommandBase
{
DayViewModel _vm;
public override bool CanExecute(object parameter) => true;
public override void Execute(object parameter)
{
_vm.CurrentSongViewModel = new EditSongViewModel();
}
public EditSongsCommand(DayViewModel vm)
{
_vm = vm;
}
}
ViewModelBase.cs
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string PropertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
}
}
The following markup sets the DataContext to another instance of the DayViewModel:
<Grid.DataContext>
<viewmodels:DayViewModel/>
</Grid.DataContext>
You should remove it from DefaultSongView.xaml and bind to the command of the parent view model using a RelativeSource:
<Button
Content="Edit"
Command="{Binding DataContext.EditSongCommand, RelativeSource={RelativeSource AncestorType=ContentControl}}"

ContentControl not refreshing View when new ViewModel is created

I am trying to make an app using using MVVMLight that has a sidebar with buttons, each button shows a new usercontrol hosted in a ContentControl. When the user clicks the buttons in the sidebar (Show View1 / Show View2) it shows the views correctly.
My problems arise when the user has already shown a View (it could be View1 or View2 ), say View1 and click (Show View1) ** again ** View1 controls remain with the same values and I would like to show the same view with all controls as if it had restarted. (I know that I could reset all controls within the user control (View1) but my application requires having a new instance of the view every time the button is clicked).
Somehow, in the same scenario when the user is in View 1, switch to view 2 and return to view 1, a new instance is created and shown as I would like. I would like to get the same result without the need to switch to view 2 and return.
Here is a minimal reproducible example of the issue:
ViewModelLocator.cs
using CommonServiceLocator;
using GalaSoft.MvvmLight.Ioc;
namespace SidebarApp.ViewModel
{
public class ViewModelLocator
{
/// <summary>
/// Initializes a new instance of the ViewModelLocator class.
/// </summary>
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
// Register viewmodels
SimpleIoc.Default.Register<MainViewModel>();
SimpleIoc.Default.Register<View1ViewModel>();
SimpleIoc.Default.Register<View2ViewModel>();
}
public MainViewModel Main { get { return ServiceLocator.Current.GetInstance<MainViewModel>(); } }
public View1ViewModel View1Vm { get { return ServiceLocator.Current.GetInstance<View1ViewModel>(); } }
public View2ViewModel View2Vm { get { return ServiceLocator.Current.GetInstance<View2ViewModel>(); } }
public static void Cleanup()
{
// TODO Clear the ViewModels
}
}
}
MainViewModel.cs
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System.Windows.Input;
namespace SidebarApp.ViewModel
{
public class MainViewModel : ViewModelBase
{
// Commands
public ICommand ShowView1Command { get; private set; }
public ICommand ShowView2Command { get; private set; }
/// <summary>
/// Property that will cointain the current viewmodel to show
/// ViewModelBase inplements ObservableObject class which imlements INotifyPropertyChanged
/// </summary>
public ViewModelBase CurrentViewModel { get; set; }
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel()
{
this.ShowView1Command = new RelayCommand(ShowView1);
this.ShowView2Command = new RelayCommand(ShowView2);
}
private void ShowView1()
{
// I tried this but it doesn't work
//CurrentViewModel = null or View2ViewModel;
CurrentViewModel = new View1ViewModel();
}
private void ShowView2()
{
CurrentViewModel = new View2ViewModel();
}
}
}
MainWindow.xaml
<Window x:Class="SidebarApp.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:SidebarApp"
mc:Ignorable="d"
DataContext="{Binding Main, Source={StaticResource Locator}}"
Title="MainWindow" Height="348.965" Width="560.683">
<Window.Resources>
<DataTemplate DataType="{x:Type local:View1ViewModel}">
<local:View1View />
</DataTemplate>
<DataTemplate DataType="{x:Type local:View2ViewModel}">
<local:View2View />
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.3*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel>
<Button Command="{Binding ShowView1Command}">Show View1</Button>
<Button Command="{Binding ShowView2Command}">Show View2</Button>
</StackPanel>
<ContentControl Grid.Column="1" Content="{Binding Path=CurrentViewModel}"></ContentControl>
</Grid>
</Window>
View1View.xaml
<UserControl x:Class="SidebarApp.View1View"
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:SidebarApp"
mc:Ignorable="d"
DataContext="{Binding View1Vm, Source={StaticResource Locator}}"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="LightPink">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock HorizontalAlignment="Center" Text="Welcome to View 1" FontSize="20"/>
<TextBox HorizontalAlignment="Stretch" Text="Original text value"/>
<CheckBox Content="Some boolean value"/>
</StackPanel>
</Grid>
</UserControl>
View1ViewModel.cs
using GalaSoft.MvvmLight;
namespace SidebarApp
{
public class View1ViewModel : ViewModelBase
{
public View1ViewModel()
{
}
}
}
View2View.xaml
<UserControl x:Class="SidebarApp.View2View"
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:SidebarApp"
mc:Ignorable="d"
DataContext="{Binding View2Vm, Source={StaticResource Locator}}"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="LightCyan">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock HorizontalAlignment="Center" Text="Welcome to View 2" FontSize="20"/>
<TextBox HorizontalAlignment="Stretch" Text="Some text in view2"/>
<Button Content="Button"/>
</StackPanel>
</Grid>
</UserControl>
View2ViewModel.cs
using GalaSoft.MvvmLight;
namespace SidebarApp
{
public class View2ViewModel : ViewModelBase
{
public View2ViewModel()
{
}
}
}
App.xaml
<Application x:Class="SidebarApp.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:SidebarApp" StartupUri="MainWindow.xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" d1p1:Ignorable="d" xmlns:d1p1="http://schemas.openxmlformats.org/markup-compatibility/2006">
<Application.Resources>
<ResourceDictionary>
<vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" xmlns:vm="clr-namespace:SidebarApp.ViewModel" />
</ResourceDictionary>
</Application.Resources>
</Application>
For example I enter to the view1 and change the value of the textbox ("Original text value") to another value and I click again in Show View1 the text and everything in the usercontrol stays the same but when I switch to view2 and go back to view1 a new usercontrol is shown with his original values.
This might feel like something of a workaround, but it works. Hopefully it will prove useful. (Obviously make same changes to View2View view/view model).
ViewModelLocator.cs
public View1ViewModel View1Vm { get { return new View1ViewModel(); } }
MainViewViewModel.cs
private void ShowView1()
{
CurrentViewModel = new ViewModelLocator().View1Vm;
}
View1View.xaml
<UserControl x:Class="SidebarApp.View1View"
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:SidebarApp"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="LightPink">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock HorizontalAlignment="Center" Text="Welcome to View 1" FontSize="20"/>
<TextBox HorizontalAlignment="Stretch" Text="{Binding Path=View1Text, Mode=TwoWay}"/>
<CheckBox Content="Some boolean value" IsChecked="{Binding Path=CheckBoxChecked, Mode=TwoWay}"/>
</StackPanel>
</Grid>
</UserControl>
View1ViewModel.cs
public class View1ViewModel : ViewModelBase
{
private string _view1Text;
private bool _checkBoxedChecked;
public string View1Text
{
get
{
return _view1Text;
}
set
{
_view1Text = value;
RaisePropertyChanged("View1Text");
}
}
public bool CheckBoxChecked
{
get
{
return _checkBoxedChecked;
}
set
{
_checkBoxedChecked = value;
RaisePropertyChanged("CheckBoxChecked");
}
}
public View1ViewModel()
{
View1Text = "Original text value";
}
}

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>

MVVM - Button command binding not working

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.

Categories

Resources