WPF After ICommand INotifyPropertyChanged is called, but not updating UI [MVVM] - c#

When the View is initialized, their standard value "VM", definied in the ViewModel, is updated trough the Model and updated in the View. However, when the ICommand NavigationCommand is triggered, the code in the OnNavigationCommand method executes, and even the OnPropertyChanged (INotifyPropertyChanged) method is called in the Model. However, the textboxes in the UI still remains the same value: "VM". I have tried a lot, but can't seem to find the problem. Hope you can help.
View
<vw:View xmlns:cc="clr-namespace:HMI.CustomControl" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
x:Class="HMI.ZoneFView"
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:vw="http://inosoft.com/visiwin7"
xmlns:main="clr-namespace:HMI.Views.MainRegion"
xmlns:local="clr-namespace:HMI" xmlns:dialogregion="clr-namespace:HMI.Views.DialogRegion"
mc:Ignorable="d"
DataContext="{vw:AdapterBinding ViewModel}">
<Viewbox>
<Grid x:Name="LayoutRoot" Width="140" Height="220.5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3"/>
<ColumnDefinition Width="5*"/>
<ColumnDefinition Width="3"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="5" />
<RowDefinition Height="5*" />
<RowDefinition Height="5" />
</Grid.RowDefinitions>
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding theModel.VisibilityFFUView}" Margin="57,16,7,62" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding theModel.VisibilityFFUView}" Margin="57,48,7,30" />
</Grid>
</Viewbox>
</vw:View>
View.cs
using HMI.Views.MainRegion;
using VisiWin.ApplicationFramework;
namespace HMI
{
[ExportView("ZoneFView")]
public partial class ZoneFView : VisiWin.Controls.View
{
public ZoneFView()
{
this.InitializeComponent();
}
}
}
ViewModel
using System;
using System.ComponentModel.Composition;
using System.Windows.Input;
using VisiWin.ApplicationFramework;
using VisiWin.Commands;
namespace HMI.Views.MainRegion
{
[ExportAdapter("ViewModel")]
[PartCreationPolicy(CreationPolicy.NonShared)]
class ViewModel : AdapterBase
{
#region Constructor
public Model Model { get; set; }
public ViewModel()
{
// If the VisiWin system is not in the runtime, the VW7 functionalities cannot be accessed
if (ApplicationService.IsInDesignMode)
{
return;
}
// Create the Action Commands
this.NavigationCommand = new ActionCommand(OnNavigationCommand);
Model = new Model()
{
VisibilityFFUView = "VM"
};
}
#endregion
#region CordisAdapterBase implementation
// Called when the view on which this adapter is located as DataContext is loaded
public override void OnViewAttached(IView view)
{
base.OnViewAttached(view);
}
public override void OnViewDetached(IView view)
{
base.OnViewDetached(view);
}
#endregion
#region NavigationCommand - Command from the view into the ViewModel
public ICommand NavigationCommand { get; set; }
// NavigationCommand event call
// Will be called if one of the buttons in the "AppbarView" view is clicked.
// The command of the model is linked to the button via the Command property.
private void OnNavigationCommand(object commandParameter)
{
if (commandParameter != null)
{
if (ApplicationService.IsInDesignMode) return;
string strParameter = commandParameter.ToString();
switch (strParameter)
{
case "FFU":
Model.VisibilityFFUView = "Turn on";
break;
case "FFUswitch":
Model.VisibilityFFUView = "Turn off";
break;
default:
break;
}
}
else
{
throw new ArgumentNullException(nameof(commandParameter));
}
}
#endregion
}
}
Model
using System.ComponentModel.Composition;
using VisiWin.ApplicationFramework;
namespace HMI.Views.MainRegion
{
[ExportAdapter("Model")]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class Model : ObserverableObject
{
private string _visibilityFFUView;
public string VisibilityFFUView
{
get { return _visibilityFFUView; }
set
{
_visibilityFFUView = value;
OnPropertyChanged();
}
}
}
}
ObserverableObject
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace HMI.Views.MainRegion
{
public class ObserverableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
UPDATE
I have added the NavigationView, and shifted both the DataContext of NavigationView and ZoneFView to XAML to reduce some code..
NavigationView
<vw:View xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
x:Class="HMI.Views.Common.AppbarView"
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:vw="http://inosoft.com/visiwin7" xmlns:local="clr-namespace:HMI"
mc:Ignorable="d" d:DesignWidth="200" d:DesignHeight="638"
DataContext="{vw:AdapterBinding ViewModel}">
<Grid x:Name="LayoutRoot" Background="{DynamicResource AppbarBackgroundBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="8" />
</Grid.ColumnDefinitions>
<Rectangle Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="{DynamicResource AccentBrush}" StrokeThickness="0" />
<StackPanel Grid.Column="0" Margin="10,10,0,0" VerticalAlignment="Top">
<vw:NavigationRadioButton HorizontalAlignment="Stretch" RegionName="MainRegion" ViewName="HomeView" IsChecked="True" Style="{DynamicResource AppbarNavigationRadioButtonStyle}" VerticalAlignment="Top" Height="52" Symbol="{DynamicResource appbar.tiles.nine}" LocalizableText="#Appbar.Dashboard" Margin="0,0,0,0" SymbolHorizontalAlignment="Left" />
<vw:NavigationRadioButton HorizontalAlignment="Stretch" RegionName="MainRegion" ViewName="FFUnitsView" IsChecked="false" Style="{DynamicResource AppbarNavigationRadioButtonStyle}" VerticalAlignment="Top" Height="52" Symbol="{DynamicResource appbar.interface.button}" LocalizableText="#Appbar.FFUnits" Margin="0,10,0,0" BorderThickness="1,1,0,1" CommandParameter="FFU" Command="{Binding NavigationCommand}">
</vw:NavigationRadioButton>
</StackPanel>
</Grid>
</vw:View>
Solution:
Like Clemens said I did use two instances of my DataContext. Therefore, when I did updated my property
I found this post on how to solve this problem: How can I create only one instance of a DataContext for multiple windows? Thanks for the input! Case closed.

Related

How to make a Save/Reset options page that has two sub-pages using WPF and C#

I would like to make a page that contains two sub-pages of options.
The problem I've encountered is that I can't access the objects from sub-pages of the main page. I should note that I use only DataContext to script behind.
And here is some of the code that will help you understand better what I mean:
StartPage.xaml
<Page x:Class="WpfApp.StartPage"
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:WpfApp"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Title="startPage">
<Grid Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<StackPanel>
<Button Content="Page 1" Command="{Binding FirstPageCommand}"/>
<Button Content="Page 2" Command="{Binding SecondPageCommand}"/>
</StackPanel>
<Frame x:Name="frame" Grid.Column="1" NavigationUIVisibility="Hidden"
Source="Page1.xaml"/>
<StackPanel Grid.Column="1" Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<Button Content="Reset" Margin="10" Command="{Binding ResetCommand}"/>
<Button Content="Save" Margin="10" Command="{Binding SaveCommand}"/>
</StackPanel>
</Grid>
StartPage.xaml.cs
using System.Windows.Controls;
namespace WpfApp
{
/// <summary>
/// Interaction logic for StartPage.xaml
/// </summary>
public partial class StartPage : Page
{
public StartPage()
{
InitializeComponent();
DataContext = new StartPage_DataContext(this);
}
}
}
StartPage_DataContext.cs
using System.Windows.Controls;
using System.Windows.Input;
namespace WpfApp
{
public class StartPage_DataContext
{
private Page _Page;
public StartPage_DataContext(Page page)
{
_Page = page;
FirstPageCommand = new RelayCommand(() => FirstPage());
SecondPageCommand = new RelayCommand(() => SecondPage());
SaveCommand = new RelayCommand(() => Save());
ResetCommand = new RelayCommand(() => Reset());
}
private void FirstPage()
{
(_Page as StartPage).frame.NavigationService.Navigate(new Page1());
}
private void SecondPage()
{
(_Page as StartPage).frame.NavigationService.Navigate(new Page2());
}
private void Save()
{
//Here is where I need code for saving both "Page1" and "Page2" elements to Settings class.
//Exeple : Settings._firstCB = Page1.firstCB.IsCheked.Value;
// Settings._secondCB = Page2.firstCB.IsCheked.Value;
}
private void Reset()
{
//Here is where I need code for setting both "Page1" and "Page2" elements to some default values.
//Exemple : Page1.firstCB.IsCheked.Value = false;
// Page2.firstCB.IsCheked.Value = true;
}
public ICommand FirstPageCommand { get; private set; }
public ICommand SecondPageCommand { get; private set; }
public ICommand SaveCommand { get; private set; }
public ICommand ResetCommand { get; private set; }
}
}
Page1.xaml (page2 is similar the only difference is the naming of elements convention)
<Page x:Class="WpfApp.Page1"
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:WpfApp"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Title="Page1">
<Grid Background="White">
<StackPanel>
<CheckBox x:Name="firstCB" Content="First Combo Box"/>
<CheckBox x:Name="secondCB" Content="Second Combo Box"/>
<ComboBox x:Name="firstCombo">
<ComboBoxItem Content="First Item"/>
<ComboBoxItem Content="Second Item"/>
</ComboBox>
</StackPanel>
</Grid>
Not sure if you have view models for your sub pages,
If you do have, one way of accessing properties of those viewmodel for your check box would be as shown below.
var tt = (((_Page as StartPage).frame.NavigationService.Content as Page1).DataContext as Page1ViewModel).IsCBChecked;

WPF Updating UI using Multiple Classes

I am teaching myself... I cannot understand why the UI won't update when a second class is involved. I am missing something basic and I don't get it.
In the first Class:
I have two ObservableCollections bound to two WPF ListViews, which is bound correctly and works.
I have a Command bound to a Button to move items from one Collection to the other, which works as expected.
In the second Class (backcode) I have implemented "Drag and Drop". On Drop I try to call the same Method (which is in the first Class and is used by the Button/Command. The Command is also in the first class).
On "Drag and Drop" the items are moved from one collection to the other (confirmed with Console.Writeline), however the UI doesn't update like it does with the Button/Command.
I believe the problem is that with "Drag and Drop" I am calling the Method from another class. I thought I could do that, but I must not be doing it right?
I have included everything from 4 files (xaml, backcode, class, relayCommand) so hopefully it is easy to reproduce. Can anyone tell me why & how to get this to work???
<Window x:Class="MultipleClassDragAndDrop.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:MultipleClassDragAndDrop"
xmlns:ViewModel="clr-namespace:MultipleClassDragAndDrop.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="716" Width="500">
<Window.Resources>
<ViewModel:MultiColumnViewModel x:Key="MultiColumnViewModel"/>
</Window.Resources>
<Grid DataContext="{Binding Mode=OneWay, Source={StaticResource MultiColumnViewModel}}" >
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid Grid.Row="0" Grid.Column="0" >
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="700"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" Orientation="Vertical">
<Button Content="Test Command" Command="{Binding Test_Command}"/>
</StackPanel>
<ListView Grid.Row="1" Grid.Column="0" x:Name="ListView1" Background="Black" MinWidth="165" Width="Auto" HorizontalContentAlignment="Center"
ItemsSource="{Binding ActiveJobListView1, UpdateSourceTrigger=PropertyChanged}" MouseMove="ListView1_MouseMove" >
<ListView.ItemTemplate>
<DataTemplate>
<GroupBox BorderThickness="0" Foreground="Black" FontWeight="Bold" Width="150" Background="LightPink" BorderBrush="Transparent">
<StackPanel Orientation="Vertical" VerticalAlignment="Center" >
<TextBlock Text="{Binding JobID}" FontWeight="Bold" />
<TextBlock Text="{Binding CustomerName}" FontWeight="Bold" />
</StackPanel>
</GroupBox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
<Grid Grid.Row="0" Grid.Column="2" >
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="700"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ListView Grid.Row="1" Grid.Column="0" x:Name="ListView2" Background="Black" MinHeight="300" MinWidth="165" Width="Auto" HorizontalContentAlignment="Center"
ItemsSource="{Binding ActiveJobListView2, UpdateSourceTrigger=PropertyChanged}"
MouseMove="ListView1_MouseMove"
AllowDrop="True" Drop="ListView2_Drop" >
<ListView.ItemTemplate>
<DataTemplate>
<GroupBox BorderThickness="0" Foreground="Black" FontWeight="Bold" Width="150" Background="LightBlue" BorderBrush="Transparent">
<StackPanel Orientation="Vertical" VerticalAlignment="Center" >
<TextBlock Text="{Binding JobID}" FontWeight="Bold" />
<TextBlock Text="{Binding CustomerName}" FontWeight="Bold" />
</StackPanel>
</GroupBox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Grid>
BackCode
using MultipleClassDragAndDrop.ViewModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Input;
namespace MultipleClassDragAndDrop
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
MultiColumnViewModel objMultiColumnViewModel = new MultiColumnViewModel();
private void ListView1_MouseMove(object sender, MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.LeftButton == MouseButtonState.Pressed)
{
int lb_itemIndex = ListView1.SelectedIndex;
// Package the data.
DataObject data = new DataObject();
data.SetData("Int", lb_itemIndex);
data.SetData("Object", this);
// Inititate the drag-and-drop operation.
DragDrop.DoDragDrop(this, data, DragDropEffects.Move);
}
}
private void ListView2_Drop(object sender, DragEventArgs e)
{
Debug.WriteLine($"\n\n{System.Reflection.MethodBase.GetCurrentMethod()}");
base.OnDrop(e);
int index = (int)e.Data.GetData("Int");
// Call A Method In A Different Class
objMultiColumnViewModel.AddAndRemove(index);
e.Handled = true;
}
}
}
My ViewModel Class
using MultipleClassDragAndDrop.ViewModel.Commands;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Windows.Input;
namespace MultipleClassDragAndDrop.ViewModel
{
public class ActiveJob : INotifyPropertyChanged
{
#region INotifyPropertyChanged
//INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
Debug.WriteLine($"NOTIFY PROPERTY CHANGED! {info}");
}
}
#endregion
public string _JobID;
public string JobID
{
get { return _JobID; }
set
{ _JobID = value; NotifyPropertyChanged("JobID"); }
}
public string _CustomerName;
public string CustomerName
{
get { return _CustomerName; }
set
{ _CustomerName = value; NotifyPropertyChanged("CustomerName"); }
}
}
public partial class MultiColumnViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged
//INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
Debug.WriteLine($"NOTIFY PROPERTY CHANGED! {info}");
}
}
#endregion
//Test Command
private ICommand _Test_Command;
public ICommand Test_Command
{
get
{
if (_Test_Command == null)
{
_Test_Command = new RelayCommand<object>(ExecuteTest_Command, CanExecuteTest_Command);
}
return _Test_Command;
}
}
public bool CanExecuteTest_Command(object parameter)
{
return true;
}
public void ExecuteTest_Command(object parameter)
{
Mouse.OverrideCursor = Cursors.Wait;
AddAndRemove(0);
Mouse.OverrideCursor = Cursors.Arrow;
}
public void AddAndRemove(int selectedIndex)
{
Debug.WriteLine($"\n\n{System.Reflection.MethodBase.GetCurrentMethod()} Index = {selectedIndex}\n");
ActiveJobListView2.Add(ActiveJobListView1[selectedIndex]);
ActiveJobListView1.RemoveAt(selectedIndex);
foreach (var item in ActiveJobListView1)
{
System.Console.WriteLine($"ActiveJobListView1: {item.JobID}, {item.CustomerName}");
}
System.Console.WriteLine($" ");
foreach (var item in ActiveJobListView2)
{
System.Console.WriteLine($"ActiveJobListView2: {item.JobID}, {item.CustomerName}");
}
}
public MultiColumnViewModel()
{
ActiveJobListView1 = new ObservableCollection<ActiveJob>();
ActiveJobListView2 = new ObservableCollection<ActiveJob>();
ActiveJobListView1.Add(new ActiveJob { JobID = "JOB100", CustomerName = "Smith" });
ActiveJobListView1.Add(new ActiveJob { JobID = "JOB101", CustomerName = "Jones" });
ActiveJobListView1.Add(new ActiveJob { JobID = "JOB102", CustomerName = "Black" });
}
#region Properties
private ObservableCollection<ActiveJob> _ActiveJobListView1;
public ObservableCollection<ActiveJob> ActiveJobListView1
{
get { return _ActiveJobListView1; }
set
{
_ActiveJobListView1 = value;
NotifyPropertyChanged("ActiveJobListView1");
}
}
private ObservableCollection<ActiveJob> _ActiveJobListView2;
public ObservableCollection<ActiveJob> ActiveJobListView2
{
get { return _ActiveJobListView2; }
set
{
_ActiveJobListView2 = value;
NotifyPropertyChanged("ActiveJobListView2");
}
}
#endregion
}
}
When binding to a Collection, there are 3 kinds of ChangeNotification you need:
The Notification that informs the UI if something was added or removed from the Collection. That is the only kind of Notification ObservableCollection provides.
The Notification on the property exposing the ObservableCollection. Due to case 1 binding and the lack of a "add range", it is a bad idea to do bulk-modifications of a exposed List. Usually you create a new list and only then Expose it to the UI. In your case those would be the properties "ActiveJobListView1" and it's kind.
The Notification on every property of every type exposed in the collection. That would be "ActiveJob" in your case.
Some of those are often forgotten, with Case 2 being the most common case. I wrote a small introduction into WPF and the MVVM pattern a few years back. maybe it can help you here: https://social.msdn.microsoft.com/Forums/vstudio/en-US/b1a8bf14-4acd-4d77-9df8-bdb95b02dbe2/lets-talk-about-mvvm?forum=wpf
You have issues with different instances of the same class.
Change:
MultiColumnViewModel objMultiColumnViewModel = new MultiColumnViewModel();
To:
var objMultiColumnViewModel = this.DataContext as MultiColumnViewModel;
and it should work
EDIT:
What you are doing is strongly against MVVM principals.
EDIT-2
I had to do some modification to you code to make it work:
In your XAML:
<Window.DataContext>
<local:MultiColumnViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
In your MainWindow.cs:
public MainWindow()
{
InitializeComponent();
objMultiColumnViewModel = this.DataContext as MultiColumnViewModel;
}
private MultiColumnViewModel objMultiColumnViewModel;

How to switch Usercontrol(s) in MainWindow.xaml using ICommand Mvvm pattern?

Please -- I do not want to employ/use outside frameworks like MVVMlite (etc) at this time. I need to do this manually so that I can see the full process.
I have seen various articulations of the question which I am asking, but I have not seen any versions of my question which bind a command to a usercontrol to change out a usercontrol in MainWindow.xaml. In the following code I demonstrate the effort/attempt I have tried to make a Wpf/Mvvm application for switching out usercontrols in MainWindow.xaml. The question/request is what steps I need to take to follow through with this project?
In my project I have the standard Models/ViewModels/Views folders, 3 usercontrol views that I want to switch around in MainWindow.xaml (MainWindow.xaml resides in the root folder of the project) -- BlueView, OrangeView, RedView. The only content these views/usercontrols have is that Blueview has a blue background grid, OrangeView has an orange background grid, RedView has a red background grid. I have 3 buttons in a stackpanel to the left in MainWindow.xaml and a content control where I want to load/switch the usercontrols in the right of MainWindow.xaml. I have 3 corresponding ViewModels, BlueViewModel, OrangeViewModel, RedViewModel. I also have a MainViewModel for tying up these 3 viewModels, and RelayCommand.cs in the Models folder. But I don't know where to go from there.
Here is my code -- note: I'm only going to add MainWindow.xaml, RelayCommand.cs, MainViewModel and BlueViewModle/BlueView since the other views/ViewModels are the same except for the background grid color . What do I need to do/add so that I can load/switch the usercontrols in the content control in MainWindow.xaml? I can't show a usercontrol - so I don't have a show/display method in MainViewModel.cs How do I load the usercontrols? Do I need methods in the ViewModels?
--MainWindow.xaml -- resides in the project root folder
<Window x:Class="ViewChangerFromICommand.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:ViewChangerFromICommand"
xmlns:viewmodels="clr-namespace:ViewChangerFromICommand.ViewModels"
xmlns:views="clr-namespace:ViewChangerFromICommand.Views"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Name="redViewTemplate" DataType="{x:Type viewmodels:RedViewModel}">
<views:RedView DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate x:Name="BlueViewTemplate" DataType="{x:Type viewmodels:BlueViewModel}">
<views:BlueView DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate x:Name="OrangeViewTemplate" DataType="{x:Type viewmodels:OrangeViewModel}">
<views:OrangeView DataContext="{Binding}"/>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<viewmodels:MainViewModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<DockPanel Background="Gray" Grid.Row="0" Grid.Column="0" Grid.RowSpan="5">
<StackPanel>
<Button Content="Red View"/>
<Button Content="Blue View"/>
<Button Content="Orange View"/>
</StackPanel>
</DockPanel>
<ContentControl Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="4" Grid.RowSpan="5" Content="{Binding}"/>
</Grid>
</Window>
--RelayCommand.cs -- resides in Models folder
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace ViewChangerFromICommand.Models
{
public class RelayCommand : ICommand
{
readonly Action _execute;
readonly Func<bool> _canExecute;
public RelayCommand(Action execute, Func<bool> canExecute)
{
if (execute == null)
throw new NullReferenceException("execute");
_execute = execute;
_canExecute = canExecute;
}
public RelayCommand(Action execute) : this(execute, null)
{
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute();
}
public void Execute(object parameter)
{
_execute.Invoke();
}
}
}
--MainViewModel.cs -- resides in ViewModels folder
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ViewChangerFromICommand.Models;
using ViewChangerFromICommand.Views;
namespace ViewChangerFromICommand.ViewModels
{
public class MainViewModel
{
public BlueViewModel blueVM { get; set; }
public OrangeViewModel orangeVM { get; set; }
public RedViewModel redVM { get; set; }
public MainViewModel()
{
blueVM = new BlueViewModel();
orangeVM = new OrangeViewModel();
redVM = new RedViewModel();
}
}
}
--BlueViewModel.cs -- resides in ViewModels folder
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using ViewChangerFromICommand.Models;
using ViewChangerFromICommand.Views;
namespace ViewChangerFromICommand.ViewModels
{
public class BlueViewModel
{
public BlueViewModel()
{
}
}
}
--BlueView.xaml -- resides in Views folder
<UserControlx:Class="ViewChangerFromICommand.Views.BlueView"
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:ViewChangerFromICommand.Views"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid Background="Blue">
</Grid>
</UserControl>
Consider the following approach.
In MainViewModel create 'Selected' property, which will reflect which ViewModel/View you want to see. Setup RelayCommands to assign desired view model to the 'Selected' property.
In view (Window), bind Content of the ContentControl to 'Selected' and setup command bindings.
MainViewModel also needs to implement INotifyPropertyChanged for change to 'Selected' property to be recognized by the view.
View model:
public class MainViewModel:INotifyPropertyChanged //Look into using Prism and BindableBase instead of INotifyPropertyChanged
{
private BlueViewModel blueVM;
private OrangeViewModel orangeVM;
private RedViewModel redVM;
public event PropertyChangedEventHandler PropertyChanged=delegate { };
object selectedView;
public object SelectedView
{
get { return selectedView; }
private set
{
selectedView = value;
RaisePropertyChanged("SelectedView");
}
}
public ICommand SelectBlueViewCommand { get; private set; }
public ICommand SelectOrangeViewCommand { get; private set; }
public ICommand SelectRedViewCommand { get; private set; }
public MainViewModel()
{
blueVM = new BlueViewModel();
orangeVM = new OrangeViewModel();
redVM = new RedViewModel();
SelectBlueViewCommand = new RelayCommand(() => SelectedView = blueVM);
SelectOrangeViewCommand = new RelayCommand(() => SelectedView = orangeVM);
SelectRedViewCommand = new RelayCommand(() => SelectedView = redVM);
}
void RaisePropertyChanged(string property)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
View/Window
<Window x:Class="WpfApp1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:av="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:local="clr-namespace:ViewChangerFromICommand"
xmlns:viewmodels="clr-namespace:ViewChangerFromICommand.ViewModels"
xmlns:views="clr-namespace:ViewChangerFromICommand.Views"
Title="Window1" Height="650" Width="750">
<Window.Resources>
<DataTemplate x:Name="redViewTemplate" DataType="{x:Type viewmodels:RedViewModel}">
<views:RedView DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate x:Name="BlueViewTemplate" DataType="{x:Type viewmodels:BlueViewModel}">
<views:BlueView DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate x:Name="OrangeViewTemplate" DataType="{x:Type viewmodels:OrangeViewModel}">
<views:OrangeView DataContext="{Binding}"/>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<viewmodels:MainViewModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<DockPanel Background="Gray" Grid.Row="0" Grid.Column="0" Grid.RowSpan="5">
<StackPanel>
<Button Content="Red View" Command="{Binding SelectBlueViewCommand}"/>
<Button Content="Blue View" Command="{Binding SelectOrangeViewCommand}"/>
<Button Content="Orange View" Command="{Binding SelectRedViewCommand}"/>
</StackPanel>
</DockPanel>
<ContentControl Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="4" Grid.RowSpan="5"
Content="{Binding SelectedView}"/>
</Grid>

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/

Binding ContentControl to a View for a ViewModel

I'm using MVVM in a windows phone 8 application. I would like to move from 1 view model to another inside my shell view model. I can't seem to get the ContentControl to bind to a template that is a usercontrol/phoneApplicationPage over the view model.
What am I missing?
I'm trying to avoid things like MVVM light. (I want my app to be as small a download as possible) And this should be possible to do.
P.S. I'm still pretty new to WPF/WP8
Here is a sample of what I have so far, Excuse the dumb functionality :)
/** The Shell view **/
<phone:PhoneApplicationPage
x:Class="PhoneAppWithDataContext.Navigation.ViewModelNavigation"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
mc:Ignorable="d"
shell:SystemTray.IsVisible="True"
xmlns:vm="clr-namespace:PhoneAppWithDataContext.Navigation">
<phone:PhoneApplicationPage.DataContext>
<vm:AppViewModel/>
</phone:PhoneApplicationPage.DataContext>
<phone:PhoneApplicationPage.Resources>
<DataTemplate x:Key="MonthViewModel">
<vm:MonthViewControl/>
</DataTemplate>
</phone:PhoneApplicationPage.Resources>
<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--TitlePanel contains the name of the application and page title-->
<StackPanel Grid.Row="0" Margin="12,17,0,28">
<TextBlock Text="MY APPLICATION" Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock Text="page name" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<ContentControl Content="{Binding CurrentViewModel}"
ContentTemplate="{Binding ContentTemplate}"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"/>
</Grid>
<Button Content="Change VM" Command="{Binding ChangeViewModelCommand}"/>
</Grid>
</phone:PhoneApplicationPage>
/** Shell/Application View Model **/
public class AppViewModel : ViewModelBase
{
private ViewModelBase _currentViewModel;
private List<ViewModelBase> _viewModels = new List<ViewModelBase>();
private byte _month = 1;
public ViewModelBase CurrentViewModel
{
get
{
return _currentViewModel;
}
set
{
if (_currentViewModel == value)
return;
_currentViewModel = value;
NotifyPropertyChanged("CurrentViewModel");
}
}
public DataTemplate SelectedTemplate
{
get
{
if (_currentViewModel == null)
return null;
return DataTemplateSelector.GetTemplate(_currentViewModel);
}
}
public List<ViewModelBase> ViewModels
{
get
{
return _viewModels;
}
}
public AppViewModel()
{
ViewModels.Add(new MonthViewModel(_month));
CurrentViewModel = ViewModels.FirstOrDefault();
}
private ICommand _changeViewModelCommand;
public ICommand ChangeViewModelCommand
{
get
{
return _changeViewModelCommand ?? (_changeViewModelCommand = new GenericCommand(() =>
{
_month++;
var newVM = new MonthViewModel(_month);
ViewModels.Add(newVM);
CurrentViewModel = newVM;
}, true));
}
}
private void ChangeViewModel(ViewModelBase viewModel)
{
if (!ViewModels.Contains(viewModel))
ViewModels.Add(viewModel);
CurrentViewModel = ViewModels.FirstOrDefault(vm => vm == viewModel);
}
}
/** DataTemplateSelector **/
public static class DataTemplateSelector
{
public static DataTemplate GetTemplate(ViewModelBase param)
{
Type t = param.GetType();
return App.Current.Resources[t.Name] as DataTemplate;
}
}
/** User Control **/
<UserControl x:Class="PhoneAppWithDataContext.Navigation.MonthViewControl"
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"
mc:Ignorable="d"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
d:DesignHeight="480" d:DesignWidth="480"
xmlns:vm="clr-namespace:PhoneAppWithDataContext.Navigation">
<UserControl.DataContext>
<vm:MonthViewModel/>
</UserControl.DataContext>
<Grid x:Name="LayoutRoot" Background="{StaticResource PhoneChromeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Id" Width="100" VerticalAlignment="Center" Grid.Row="0" Grid.Column="0" />
<TextBlock Text="{Binding Id}" Width="100" VerticalAlignment="Center" Grid.Row="0" Grid.Column="1" />
<TextBlock Text="Name" Width="100" VerticalAlignment="Center" Grid.Row="1" Grid.Column="0" />
<TextBlock Text="{Binding Name}" Width="100" VerticalAlignment="Center" Grid.Row="1" Grid.Column="1" />
</Grid>
</UserControl>
/** ViewModelBase **/
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
/** View model that the user control should bind to **/
public sealed class MonthViewModel : ViewModelBase
{
private byte _id;
private string _name;
public MonthViewModel()
{
}
public MonthViewModel(byte id)
{
_id = id;
_name = "Month " + id.ToString() + " of the year";
}
public override string ToString()
{
return _name;
}
public byte Id
{
get
{
return _id;
}
}
public string Name
{
get
{
return _name;
}
}
}
I believe the problem here is:
<UserControl.DataContext>
<vm:MonthViewModel/>
</UserControl.DataContext>
When your Content is changed from one MonthViewModel to the next, the DataContext of the returned DataTemplate is set to the object bound to Content. Well, once that DataContext is set, you should be good to go, but once the UserControl is loaded, it is resetting the DataContext to a new instace of an empty MonthViewModel (vm:MonthViewModel). Get rid of that explicit DataContext declaration--in other words delete the code that I posted above.
That way, when you first call CurrentViewModel and INPC is raised, it won't reset the DataContext. When you switch between CurrentViewModel's that are of MonthViewModel type, your UserControl won't call InitializeComponent again, instead the DataContext will change.
EDIT
In addition, if you still aren't seeing changes, then I would point to SelectedTemplate property. Instead of the null check in the property, just pass null to the GetTemplate. Inside of GetTemplate, check for null and return null there if it is null.

Categories

Resources