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

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>

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 After ICommand INotifyPropertyChanged is called, but not updating UI [MVVM]

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.

View(User Control)'s binded properties stop working after being nested in a MainWindow

I'm currently new to WPF and I created a sample project to work on using MVVMCross framework following the MVVM setup. So far I'm using the MainWindow.xaml to be the parent of my child view (StudentDetailsView.xaml). At first I had only my child view and all the bindings worked out fine, but after I added the child view to my parent view as a nested view, all the bindings stopped working. For example, the FirstName and LastName property stopped working with their two way mode Binding to update the FullName property.
MainWindow.xaml
<views:MvxWindow
xmlns:views="clr-namespace:MvvmCross.Platforms.Wpf.Views;assembly=MvvmCross.Platforms.Wpf"
x:Class="MvxStarter.Wpf.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:MvxStarter.Wpf.Views"
xmlns:vm="clr-namespace:MvxStarter.Core.ViewModels;assembly=MvxStarter.Core"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid x:Name="OuterGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="25"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="5"/>
<RowDefinition Height="*"/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<Grid Grid.Row="1" Grid.Column="1">
<StackPanel>
<Menu>
<MenuItem Header="_File">
<MenuItem Header="FileOption1"/>
</MenuItem>
<MenuItem Header="_Options">
<MenuItem Header="Option1"/>
</MenuItem>
</Menu>
<Grid>
<!--Dynamic view switch out here-->
<local:StudentDetailsView/>
</Grid>
</StackPanel>
</Grid>
</Grid>
</views:MvxWindow>
MainWindow.xaml.cs
using MvvmCross.Platforms.Wpf.Views;
using System.Windows.Controls;
namespace MvxStarter.Wpf
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : MvxWindow
{
public MainWindow()
{
InitializeComponent();
}
}
}
StudentDetailsView.xaml
<views:MvxWpfView
xmlns:views="clr-namespace:MvvmCross.Platforms.Wpf.Views;assembly=MvvmCross.Platforms.Wpf"
xmlns:mvx="clr-namespace:MvvmCross.Platforms.Wpf.Binding;assembly=MvvmCross.Platforms.Wpf"
x:Class="MvxStarter.Wpf.Views.StudentDetailsView"
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:MvxStarter.Wpf.Views"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" FontSize="20">
<Grid>
<StackPanel Margin="5">
<TextBlock Text="{Binding FullName}" FontSize="28" Margin="0,0,0,15"/>
<TextBlock Text="First Name"/>
<TextBox Text="{Binding FirstName, Mode=TwoWay}" Margin="0,0,0,15"/>
<TextBlock Text="Last Name"/>
<TextBox Text="{Binding LastName, Mode=TwoWay}" Margin="0,0,0,15"/>
</StackPanel>
</Grid>
</views:MvxWpfView>
StudentDetailsViewModel.cs
using MvvmCross.Logging;
using MvvmCross.Navigation;
using MvvmCross.ViewModels;
namespace MvxStarter.Core.ViewModels
{
public class StudentDetailsViewModel : MvxViewModel
{
public StudentDetailsViewModel()
{
}
private string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
SetProperty(ref _firstName, value);
RaisePropertyChanged(() => FullName);
}
}
private string _lastName;
public string LastName
{
get { return _lastName; }
set
{
SetProperty(ref _lastName, value);
RaisePropertyChanged(() => FullName);
}
}
public string FullName => $"{FirstName} {LastName}";
}
}
Am I not correctly binding my properties once I set my child view within my parent view?
Eventually what I want to do is switch out my child views within the grid panel whenever the user selects a different view to see within the menu bar. The area drawn in red is where I want so switch out different views dependent on the user's choice.
Okay, after a little searching of the web and retracing my steps. I found out that the datacontext of my child view wasn't properly finding the viewmodel so I had to explicitly call it out in the .xaml like so
<views:MvxWpfView.DataContext>
<vm:StudentDetailsViewModel/>
</views:MvxWpfView.DataContext>
Not sure if that was the cleanest solution...

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;

How to dynamically change DataTemplate according to bound object's type?

I'm trying to create a DataTemplate for a View, to show a specific UserControl type (like a texbox, combobox, custom control or another View) based on the type of object it is bound to.
I have the following MVVM framework:
FieldView is tied to an instance of FieldPresenter, and should display a <Textblock /> for the "Label" property, and a UserControl or another View for the Value (based on the Type of the value), with it's DataSource set to the Value property of the Presenter. Currently, I do not have the second part working. I can't figure out how to write a WPF template for what I need.
ViewModel:
public class FieldPresenter : Observable<object>, IFieldPresenter, INotifyPropertyChanged
{
public FieldPresenter() { }
public FieldPresenter(object value)
{
Value = value;
}
object IFieldPresenter.Value
{
get
{
return base.Value;
}
set
{
base.Value = value;
OnPropertyChanged("Value");
}
}
private string _label;
public virtual string Label
{
get
{
return _label;
}
private set
{
_label = value;
OnPropertyChanged("Label");
}
}
}
View:
<UserControl x:Class="My.Views.FieldView"
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:ViewModels="clr-namespace:My.ViewModels"
mc:Ignorable="d"
d:DesignHeight="24" d:DesignWidth="100">
<UserControl.DataContext>
<ViewModels:FieldPresenter/>
</UserControl.DataContext>
<UserControl.Template>
<ControlTemplate>
<Grid Margin="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Key" />
</Grid.ColumnDefinitions>
<StackPanel Margin="0,0,0,0" HorizontalAlignment="Stretch" Width="{Binding RelativeSource={RelativeSource AncestorType=Grid}, Path=ActualWidth}">
<TextBlock Text="{Binding Label}" FontWeight="Bold" Height="32" HorizontalAlignment="Stretch"/>
<TextBox Text="{Binding Value}" Height="Auto" HorizontalAlignment="Stretch"/>
</StackPanel>
</Grid>
</ControlTemplate>
</UserControl.Template>
</UserControl>
I'm curious if what I'm trying to do is even possible, or if I can workaround it by making my Presenter viewmodel return a UserControl rather than an object value, and have the Presenter parse the UserControl Type from the object type, but I don't feel like my Presenter should be instantiating Controls (or what is technically an unbound view). Should I make an interface, something like IViewAs<controlType> { controlType View { get; } }?
How else would I replace <TextBox Text="{Binding Value}" /> in the above script with some kind of template of a UserControl based on the databound object's type?
You almost certainly want a ContentTemplateSelector :
Code:
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Primitive primitive;
primitive = new Sphere();
// primitive = new Cube();
DataContext = primitive;
}
}
internal abstract class Primitive
{
public abstract string Description { get; }
}
internal class Cube : Primitive
{
public override string Description
{
get { return "Cube"; }
}
}
internal class Sphere : Primitive
{
public override string Description
{
get { return "Sphere"; }
}
}
public class MyTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var frameworkElement = container as FrameworkElement;
if (frameworkElement != null && item != null)
{
if (item is Cube)
{
return frameworkElement.FindResource("CubeTemplate") as DataTemplate;
}
if (item is Sphere)
{
return frameworkElement.FindResource("SphereTemplate") as DataTemplate;
}
}
return base.SelectTemplate(item, container);
}
}
}
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:local="clr-namespace:WpfApplication1"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="Window"
Title="MainWindow"
Width="525"
Height="350"
mc:Ignorable="d">
<Grid>
<Grid.Resources>
<local:MyTemplateSelector x:Key="myTemplateSelector" />
<DataTemplate x:Key="CubeTemplate" DataType="local:Cube">
<Border BorderBrush="Blue"
BorderThickness="1"
CornerRadius="5" />
</DataTemplate>
<DataTemplate x:Key="SphereTemplate" DataType="local:Sphere">
<Border BorderBrush="Red"
BorderThickness="1"
CornerRadius="50" />
</DataTemplate>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="1*" />
<RowDefinition />
</Grid.RowDefinitions>
<Label Grid.Row="0"
Content="{Binding Description}"
d:DataContext="{d:DesignInstance local:Primitive}" />
<ContentControl Grid.Row="1"
Content="{Binding}"
ContentTemplateSelector="{StaticResource myTemplateSelector}" />
</Grid>
</Window>
Result:
See the documentation for more:
https://msdn.microsoft.com/en-us/library/system.windows.controls.datatemplateselector(v=vs.110).aspx

Categories

Resources