How can i bind to the sub-ViewModel using ViewModel inheritance? - c#

In my application i have the following MasterViewModel1-class.
public class MasterViewModel1 : ViewModelBase
{
private ObservableCollection<ObservableObject> _MainGrid;
public ObservableCollection<ObservableObject> MainGrid
{
get => _MainGrid;
set
{
_MainGrid = value;
RaisePropertyChanged();
}
}
public ObservableCollection<FilterItem> FilterItems
{
get;
set;
}
public MasterViewModel1()
{
CreateDefaultMenu();
}
public void CreateDefaultMenu()
{
FilterItems = new ObservableCollection<FilterItem>
{
new FilterItem(OnFilterClicked)
{
Content = "Filter"
},
new FilterItem(OnFilterCancelClicked)
{
Content = "Filter aufheben"
}
};
}
public virtual void OnFilterClicked() { }
public virtual void OnFilterCancelClicked() { }
The MasterViewModel1-class is inherited by the TestViewModel-class.
public class TestViewModel : MasterViewModel1
{
private Kunde _NeuerKunde;
public Kunde NeuerKunde
{
get => _NeuerKunde;
set => _NeuerKunde = value;
}
private string _Kundenmatchcode;
public string Kundenmatchcode
{
get => _Kundenmatchcode;
set
{
_Kundenmatchcode = value;
RaisePropertyChanged();
}
}
public TestViewModel()
{
NeuerKunde = new Kunde();
}
}
I use the MasterViewModel1-class and its view for reusable reasons, because in the future there will be many more views which will inherit the MasterViewModel.
Inside the MasterView in need to bind to both, the MasterViewModel, so i have the "Base-Design".
And i need to bind to the "Sub"ViewModel, in this example the TestViewModel.
View of the MasterViewModel1
In the image u can see the MasterView. The red marked region is the place where the TestViewModel (TestView) should be placed. I can't use staticresource!!! It have to be dynamic, so if i instanciate another ViewModel, which also inherites from MasterViewModel1. The red marked region should change depending on the instantiated ViewModel.
I hope it's clear enought.
If u need further informations please ask.

Generally, all public properties of a superclass are visible and accessible via every subclass. You can bind to every public property.
If you want to change the layout or appearance of a view based on the actual implementation or type, you should use a DataTemplate which describes how the view is structured and bound to the model's data.
A simple ContentControl will serve as the dynamic view host.
ViewModelBase.cs
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MainViewModel.cs
public class MainViewModel : ViewModelBase
{
private ViewModelBase currentView;
public ViewModelBase CurrentView
{
get => this.currentView;
set
{
this.currentView= value;
OnPropertyChanged();
}
}
public ICommand ToggleViewCommand => new RelayCommand(param => this.CurrentView = this.Views.FirstOrDefault(view => view != this.CurrentView));
private List<ViewModelBase> Views { get; }
public MainViewModel()
{
this.Views = new ObservableCollection<ViewModelBase>()
{
new TestViewModel() { Value = "TestViewModel View" },
new AnotherTestViewModel() { Name = "AnotherTestViewModel View" }
}
this.CurrentView = this.Views.First();
}
}
TestViewModel.cs
public class TestViewModel : ViewModelBase
{
private string value;
public string Value
{
get => this.value;
set
{
this.value = value;
OnPropertyChanged();
}
}
}
AnotherTestViewModel.cs
public class TestViewModel : ViewModelBase
{
private string name;
public string Name
{
get => this.name;
set
{
this.name = value;
OnPropertyChanged();
}
}
}
TestView.xaml
<Window>
<Window.DataContext>
<TestViewModel />
</Window.DataContext>
<TextBlock Text="{Binding Value}" />
</Window>
MainWindow.xaml
<Window>
<Window.DataContext>
<MainViewModel />
</Window.DataContext>
<Window.Resources>
<!-- Define the views as an implicit (keyless) DataTemplate -->
<DataTemplate DataType="{x:Type TestViewModel}">
<!-- Show a view as a UserControl -->
<TestView />
</DataTemplate>
<DataTemplate DataType="{x:Type AnotherTestViewModel}">
<!-- Or add a elements -->
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<Rectangle Height="80" Width="80" Fill="Red" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel>
<Button Command="{Binding ToggleViewCommand}" Content="Toggle View" />
<!--
Host of the different views based on the actual model type (dynamic view).
The implicit DataTemplates will apply automatically
and show the view that maps to the current CurrentView view model type
-->
<ContentControl Content="{Binding CurrentView}" />
</StackPanel>
</Window>

Related

Prevent WPF ViewModel from creating new instance when navigating to other views

I am attempting to prevent my application from deleting a view and then creating a new one each time it's navigated around. I have a dashboard that will run a test program, if I select the settings view, then back to the dashboard, it has deleted the running test and initialized a new view. I need to keep the same view instance alive so that the test can continue to run while the user navigates to the settings view and back again but I cant exactly figure out how to successfully do that. I have attempted making the instance static but that doesn't seem to make a difference.
MainViewModel
class MainVM : ViewModelBase
{
private object _currentView;
public object CurrentView
{
get { return _currentView; }
set { _currentView = value; OnPropertyChanged(); }
}
public ICommand DashboardCommand { get; set; }
public ICommand SettingsCommand { get; set; }
public static DashboardVM DashboardInstance { get; } = new DashboardVM();
public static SettingsVM SettingsInstance { get; } = new SettingsVM();
private void Dashboard(object obj) => CurrentView = DashboardInstance;
private void Settings(object obj) => CurrentView = SettingsInstance;
public MainVM()
{
DashboardCommand = new RelayCommand(Dashboard);
SettingsCommand = new RelayCommand(Settings);
// Startup Page
CurrentView = DashboardInstance;
}
}
ViewModelBase
public partial class ViewModelBase : ObservableObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
public void NotifyPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
MainView - Navigation
<!-- Navigation Panel -->
<Grid HorizontalAlignment="Left" Width="76">
<Border Background="#3D5A8A" CornerRadius="10,0,0,10" />
<StackPanel Height="1200" Width="76">
<!-- Dashboard Button -->
<nav:Button Style="{StaticResource NavButton_Style}"
Command="{Binding DashboardCommand}"
IsChecked="True">
<Grid>
<Image Source="Images/dash_black_50.png"
Style="{StaticResource NavImage_Style}" />
<TextBlock Text="Dashboard"
Style="{StaticResource NavText_Style}" />
</Grid>
</nav:Button>
<!-- Settings Button -->
<nav:Button Style="{StaticResource NavButton_Style}"
Command="{Binding SettingsCommand}">
<Grid>
<Image Source="Images/gear_black_50.png"
Style="{StaticResource NavImage_Style}" />
<TextBlock Text="Settings"
Style="{StaticResource NavText_Style}" />
</Grid>
</nav:Button>
</StackPanel>
</Grid>
DashboardVM
class DashboardVM : ViewModelBase
{
enum TestItemStatus
{
Reset,
Queued,
InProgress,
Pass,
Fail
}
private readonly PageModel _pageModel;
private string _StartButtonText,
_WaveRelayEthernetText;
private bool isTestRunning;
public DashboardVM()
{
_pageModel = new PageModel();
_StartButtonText = "Start Test";
_WaveRelayEthernetText = string.Empty;
StartButtonCommand = new RelayCommand(o => StartButtonClick("StartButton"));
}
#region Text Handlers
public string StartButtonText
{
get { return _StartButtonText; }
set { _StartButtonText = value; NotifyPropertyChanged("StartButtonText"); }
}
public string WaveRelayEthernetText
{
get { return _WaveRelayEthernetText; }
set { _WaveRelayEthernetText = value; NotifyPropertyChanged("WaveRelayEthernetText"); }
}
#endregion
private bool TestRunning
{
get { return isTestRunning; }
set { isTestRunning = value;
if (isTestRunning) { StartButtonText = "Stop Test"; }
else { StartButtonText = "Start Test";
ResetTestItems();
}
NotifyPropertyChanged("TestRunning");
}
}
public ICommand StartButtonCommand { get; set; }
private void StartButtonClick(object sender)
{
if(TestRunning)
{
TestRunning = false;
}
else
{
SetTestItemsToQueued();
MessageBox.Show("Please plug in Tube 1");
// Start program.
TestRunning = true;
WaveRelayEthernetText = TestItemStatusEnumToString(TestItemStatus.InProgress);
}
}
private string TestItemStatusEnumToString(TestItemStatus temp)
{
if (temp == TestItemStatus.Reset) { return string.Empty; }
else if (temp == TestItemStatus.Queued) { return "Queued"; }
else if (temp == TestItemStatus.InProgress) { return "In Progress"; }
else if (temp == TestItemStatus.Pass) { return "Pass"; }
else if (temp == TestItemStatus.Fail) { return "Fail"; }
else { return string.Empty; }
}
private void SetTestItemsToQueued()
{
WaveRelayEthernetText = TestItemStatusEnumToString(TestItemStatus.Queued);
}
private void ResetTestItems()
{
WaveRelayEthernetText = TestItemStatusEnumToString(TestItemStatus.Reset);
}
}
Image for reference:
My Issue was in the App.xaml, I link a DataTemplate file like this:
<ResourceDictionary Source="Utilities/DataTemplate.xaml" />
Inside the data template, I had this code that linked the views to the view models.
<ResourceDictionary [...]">
<DataTemplate DataType="{x:Type vm:DashboardVM}">
<view:Dashboard />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SettingsVM}">
<view:Settings />
</DataTemplate>
</ResourceDictionary>
I changed that code to link the two to this:
<ResourceDictionary [...]>
<view:Dashboard x:Key="DashboardViewKey"/>
<view:Settings x:Key="SettingsViewKey"/>
<DataTemplate DataType="{x:Type vm:DashboardVM}">
<ContentControl Content="{StaticResource DashboardViewKey}" />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SettingsVM}">
<ContentControl Content="{StaticResource SettingsViewKey}" />
</DataTemplate>
</ResourceDictionary>
I am now receiveing the expected behavior of being able to navigate without the Dashboard constructor being called, thus the view does not destory and recreate.
I hope someone else finds this useful.

UserControl with Collection Binding always returning Null

Problem: When attempting to bind an Observable Collection to my user control, it is always showing null at runtime.
Description: I have a user control as described below. The goal is to create a button to cycles through an array of images with every click. However, when I run this, ImageCollection is always null, regardless of how I setup the binding on the implementation side. I'm at a loss for why this is. The code is as follows:
XAML:
<UserControl x:Class="kTrack.ToggleImage"
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:kTrack"
mc:Ignorable="d"
d:DesignHeight="{Binding ImageHeight}" d:DesignWidth="{Binding ImageWidth}">
<Grid x:Name="LayoutRoot">
<Button Click="ToggleImage_Click" Height="{Binding ImageHeight}" Width="{Binding ImageWidth}">
<Image Source="{Binding ActiveImage}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ToolTip="{Binding ToolTip}" />
</Button>
</Grid>
</UserControl>
Code-Behind (Important Bits)
public partial class ToggleImage : UserControl, INotifyPropertyChanged
{
public static readonly DependencyProperty ImageCollectionProperty = DependencyProperty.Register("ImageCollection", typeof(ObservableCollection<string>), typeof(ToggleImage), new PropertyMetadata(null));
...
public ObservableCollection<String> ImageCollection
{
get => (ObservableCollection<String>)GetValue(ImageCollectionProperty);
set => SetValue(ImageCollectionProperty, value);
}
private ImageSource _activeImage;
public ImageSource ActiveImage
{
get => _activeImage;
set
{
_activeImage = value;
OnPropertyChanged();
}
}
private int _currentImageIndex;
public int CurrentImageIndex
{
get => _currentImageIndex;
set
{
_currentImageIndex = value;
OnPropertyChanged();
}
}
// Constructor
public ToggleImage()
{
ImageCollection = new ObservableCollection<String>();
InitializeComponent();
LayoutRoot.DataContext = this;
CurrentImageIndex = 0;
if (ImageCollection.Count > 0)
{
ActiveImage = SetCurrentImage(CurrentImageIndex);
}
}
...
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML Implementation:
<local:ToggleImage ImageHeight="32" ImageWidth="32"
ImageCollection="{Binding Images, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
Code-Behind of Implementation:
public partial class MainWindow : kWindow
{
public ObservableCollection<String> Images = new ObservableCollection<String>()
{
"pack://application:,,,/Resources/Icons/Close.png",
"pack://application:,,,/Resources/Icons/Minimize.png",
"pack://application:,,,/Resources/Icons/WindowOne.png"
};
public MainWindow()
{
InitializeComponent();
}
}
for one, you should set DataContext in Window.
for two, Binding work with properties, not fields: change Images field to property.
public partial class MainWindow : kWindow
{
public ObservableCollection<String> Images { get; } = new ObservableCollection<String>()
{
"pack://application:,,,/Resources/Icons/Close.png",
"pack://application:,,,/Resources/Icons/Minimize.png",
"pack://application:,,,/Resources/Icons/WindowOne.png"
};
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
}

Set up a binding between two properties in code behind

In my view, I have a ListBox with some templated items that contain buttons.
<ListBox x:Name="MyListBox" ItemTemplate="{DynamicResource DataTemplate1}"
ItemsSource="{Binding MyItems}">
</ListBox>
And the template for generated items:
<DataTemplate x:Key="DataTemplate1">
<StackPanel Orientation="Horizontal">
<Button Width="50" Click="Button_Click" />
</StackPanel>
</DataTemplate>
When user clicks a button on one of those ListBox items, I want to send the index of that ListBox item to my ViewModel.
So figured to use Binding as it seems to be the way in MVVM. But I'm struggling to set up a binding in code between two properties.
My View code is as follows:
public partial class ItemView : UserControl
{
ViewModel.ItemViewModel VM;
public ItemView()
{
InitializeComponent();
VM = new ViewModel.ItemViewModel();
this.DataContext = VM;
}
private int clickedItemIndex;
public int ClickedItemIndex { get => clickedItemIndex; set => clickedItemIndex = value; }
private void Button_Click(object sender, RoutedEventArgs e)
{
var ClickedItem = (sender as FrameworkElement).DataContext;
ClickedItemIndex = MyListBox.Items.IndexOf(ClickedItem);
}
}
I get the index and set it to ClickedItemIndex property,
I also have property in my ViewModel:
public int SomeInt { get; set; }
Now how do I set up a binding between these two properties?
I'm quite new to MVVM and still learning it. So, maybe this not the correct approach. But I need to have a way for each individual listbox item to be able to call upon an effect in more global viewmodel. For example, if I wanted to have a "Remove" button on each of the listbox items, I would somehow need to send the index to the viewmodel and call the removeItem method with index as the parameter. Or is there a better way to do similar things?
I have a sample app created just for this scenario. I know it seems a lot of code at first glance. Copy this code in your project, that will help debug and get a hang of it(MVVM, databinding, commands and so on).
usercontrol.xaml
<UserControl x:Class="WpfApplication1.UserControl1"
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:WpfApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type local:Model}">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Path=Name}"/>
<Button Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=DataContext.UpdateCommand}"
CommandParameter="{Binding}"
Content="Update"/>
<Button Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=DataContext.RemoveCommand}"
CommandParameter="{Binding}"
Content="Remove"/>
</StackPanel>
</DataTemplate>
</Grid.Resources>
<ListBox ItemsSource="{Binding Models}">
</ListBox>
</Grid>
usercontrol.cs
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
View model
public class ViewModel : INotifyPropertyChanged
{
private Models _Models;
public Models Models
{
get { return _Models; }
set { _Models = value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(Models)));
}
}
public ViewModel()
{
Models = new Models();
UpdateCommand = new Command(o => true, UpdateItem);
RemoveCommand = new Command(o => true, RemoveItem);
}
void RemoveItem(object item)
{
Model m = (item as Model);
Models.Remove(m);
}
void UpdateItem(object item)
{
Model m = (item as Model);
m.Name = m.Name + " updated";
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public ICommand UpdateCommand { get; private set; }
public ICommand RemoveCommand { get; private set; }
}
Icommand implementation
public class Command : ICommand
{
private readonly Func<object, bool> _canExe;
private readonly Action<object> _exe;
public Command(Func<object,bool> canExecute,Action<object> execute)
{
_canExe = canExecute;
_exe = execute;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return _canExe(parameter);
}
public void Execute(object parameter)
{
_exe(parameter);
}
}
Model and a collection of models
public class Models : ObservableCollection<Model>
{
public Models()
{
Add(new Model ());
Add(new Model ());
Add(new Model ());
Add(new Model ());
}
}
public class Model : INotifyPropertyChanged
{
static int count = 0;
public Model()
{
Name = "Model "+ ++count;
}
private string _Name;
public string Name
{
get { return _Name; }
set { _Name = value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
You don't need to use a Button in order to select the item. When you click/tap on the item it will get automatically selected.
Then simply bind ListBox.SelectedIndex to your view model property SomeInt and it will update on every selection.
Data binding overview in WPF
You can also get the item itself by binding ListBox.SelectedItem to your view model.
You can handle new values by invoking a handler from the property's set method:
ViewModel.cs
class ViewModel : INotifyPropertyChanged
{
private int currentItemIndex;
public int CurrentItemIndex
{
get => this.currentItemIndex;
set
{
this.currentItemIndex = value;
OnPropertyChanged();
// Handle property changes
OnCurrentItemIndexChanged();
}
}
private MyItem currentItem;
public MyItem CurrentItem
{
get => this.currentItem;
set
{
this.currentItem = value;
OnPropertyChanged();
}
}
protected virtual void OnCurrentItemIndexChanged()
{
// Handle the new this.CurrentItemIndex value
}
// Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
ItemView .xaml
<UserControl>
<UserControl.DataContext>
<ViewModel />
</UserControl.DataContext>
<ListBox ItemsSource="{Binding MyItems}"
SelectedIndex="{Binding CurrentItemIndex}"
SelectedItem="{Binding CurrentItem}" />
</UserControl>

Create a ViewModel with sub ViewModel

Is there a proper way to create a C#/WPF ViewModel containing subViewModel ?
Objective is:
I have a MainWindow. That window is use to read/create images. There is a button on that windows who switch between 2 UserControl one with IHM used to read image, the other one used to create.
The MainWindow has a MainWindowViewModel with :
command switch
image length
application parameters
I want that both UserControls can acces to MainWindowViewModel field/properties and have they own commands.
Construction will be something like this:
public partial class ReadUserControl : UserControl
{
public ReadUserControl()
{
InitializeComponent();
DataContext = MainViewModel.ReadViewModel;
}
}
public partial class CreateUserControl : UserControl
{
public CreateUserControl()
{
InitializeComponent();
DataContext = MainViewModel.CreateViewModel;
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = MainViewModel;
}
}
For example, if a MainViewModel contain a field ImageWidth setting ImageWidth in CreateUserControl change the value for ReadUserControl.
I hope to have been clear, I don't know how design my MainViewModel to achieve this result
EDIT1:
I've created the MainWindowViewModel as a Singleton but i'm still unable to get MainViewModel.CreateViewModel and MainViewModel.ReadViewModel
public class MainWindowViewModel : ViewModelBase
{
private static MainWindowViewModel _instance = null;
public static MainWindowViewModel Instance
{
get
{
if (_instance == null)
_instance = new MainWindowViewModel();
return _instance;
}
}
private MainWindowViewModel()
: base()
{
}
#region CreateViewModel
/* How to create ? */
#endregion
#region ReadViewModel
/* How to create ? */
#endregion
}
Your example will work. At least if you have made your MainViewModel a Singleton.
A more professional approach might be an Constructor-Injection like this.
public partial class ReadUserControl : UserControl
{
public ReadUserControl(MainViewModel vm)
{
InitializeComponent();
DataContext = vm.ReadViewModel;
}
}
With such DependencyInjections you can achieve a higher level of abstraction, since your UserControls can be generalized. (They will all have the same Constructor)
On the other hand, you give every such UserControl the ability, to manipulate the MainViewModel, not aware of side-effects.
In your special case, it would be more safe, to pass only the needed parameters to the UserControl, instead of giving them a bunch of informations, they will never need.
public partial class ReadUserControl : UserControl
{
public ReadUserControl(Icommand command, int imageLength, AppParams appParams)
{
InitializeComponent();
...
// Do with your Constructorparameters what ever you have to
}
}
Edit:
Here a small, dumb implementation of how it could be done:
Code
public class MainViewModel : INotifyPropertyChanged {
private INotifyPropertyChanged _selectedViewModel;
public MainViewModel() {
var cmd = new RelayCommand(x => {
MessageBox.Show("HelloWorld");
}, x => true);
this.RVM = new ReadViewModel(cmd);
this.WVM = new WriteViewModel(cmd);
this.SelectedViewModel = WVM;
}
private ICommand _switchViewModelCommand;
public ICommand SwitchViewModelCommand => this._switchViewModelCommand ?? (this._switchViewModelCommand = new RelayCommand(x => {
if (this.SelectedViewModel == RVM) {
this.SelectedViewModel = WVM;
return;
}
this.SelectedViewModel = RVM;
}));
public INotifyPropertyChanged SelectedViewModel {
get {
return this._selectedViewModel;
}
set {
if (Equals(value, this._selectedViewModel))
return;
this._selectedViewModel = value;
this.OnPropertyChanged();
}
}
public ReadViewModel RVM {
get; set;
}
public WriteViewModel WVM {
get; set;
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class ReadViewModel : INotifyPropertyChanged {
public ReadViewModel(ICommand sayHelloCommand) {
this.HelloCommand = sayHelloCommand;
}
public ICommand HelloCommand {
get;
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class WriteViewModel : INotifyPropertyChanged {
public WriteViewModel(ICommand sayHelloCommand) {
this.HelloCommand = sayHelloCommand;
}
public ICommand HelloCommand {
get;
}
public ICommand HelloMoonCommand => new RelayCommand(x => { MessageBox.Show("Hello Moon"); });
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid Height="200">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<ContentControl Content="{Binding SelectedViewModel, UpdateSourceTrigger=PropertyChanged}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:ReadViewModel}">
<StackPanel>
<Button Content="Say Hello world" Command="{Binding HelloCommand}"></Button>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:WriteViewModel}">
<StackPanel>
<Button Content="Say Hello world" Command="{Binding HelloCommand}"></Button>
<Button Content="Say Hello Moon" Command="{Binding HelloMoonCommand}"></Button>
</StackPanel>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
<Button Content="Switch VM" Command="{Binding SwitchViewModelCommand}" Grid.Row="1"/>
</Grid>
You can pass in the MainViewModel as DataContext for your user control and set the data context of elements as Read/Create model
something like
<Grid> <!--using MainWindowViewModel as data context-->
<Grid DataContext="{Binding Path=CreateViewModel}"> <!--using CreateViewModel as data context-->
.....
</Grid>
<Grid>

How to excecute a command declared in child viewmodel in an MVVM app?

I've got a MainWindowVM and multiple child viewmodels inheriting from it.
MainWindowVM inherits from ViewModelBase which implements INotifyPropertychanged.
Each view has DataContext set to CurrentViewModel defined in MainWindowVM and every button
has got a binding to a command.
If I put the commands (and other command-handling code in the constructor) in the MainWindowVM,
button clicks in every view works as expected. I set MainControlVM as CurrentViewModel in the constructor of MainWindowVM.
Except for MainControlVM and MainWindowVM, setting commands in any other VM means they wont execute.
However, I want to have commands only in the VMs they are used.
I found many tutorials on MVVM with only one or two viewmodels so this situation isnt an issue for them.
Edit including code:
This is the relevant code:
Part of one of the child views in XAML with a binding:
<Grid DataContext="{Binding CurrentViewModel}" Margin="0,0,-186,0">
<Button Content="Add" HorizontalAlignment="Left" Margin="25,249,0,0" VerticalAlignment="Top" Width="62" Height="32"
Command="{Binding AddCategoryVMCommand}" />
MainWindowVM class contains:
public ICommand AddCategoryVMCommand { get; private set; }
and, in the constructor:
AddCategoryVMCommand = new RelayCommand(() => ExecuteAddCategoryVMCommand());
and:
protected void ExecuteAddCategoryVMCommand()
{
CurrentViewModel = new AddCategoryVM();
}
....and the same kind of code for each command. Aso, CurrentViewModel is set in the MainWindowVM class. This is the property that the MainWindow view uses to determine which view to display along with a datatemplate:
public ViewModelBase CurrentViewModel
{
get { return _currentViewModel; }
set
{
if (_currentViewModel == value)
return;
_currentViewModel = value;
this.RaiseNotifyPropertyChanged("CurrentViewModel");
}
}
How can I make commands execute when declared in child viewmodel?
There are a lot of comments going on, all out of sync and they appear to convolute the issue so I thought I would try to solve your problem with a basic example. The example deals solely with the command binding issue you appear to have.
I have created 3 ViewModel's, MyViewModel1 and MyViewModel2 are derived of MyViewModel. There is a command defined in the base ViewModel which is used to load the CurrentViewModel. The other 2 ViewModels contain their own commands.
public class MyViewModel : INotifyPropertyChanged
{
private MyViewModel currentViewModel;
public RelayCommand<object> MyCommand { get; set; }
public MyViewModel()
{
MyCommand = new RelayCommand<object>(MyCommandExecute);
}
public MyViewModel CurrentViewModel
{
get { return currentViewModel; }
set
{
if (value != currentViewModel)
{
currentViewModel = value;
OnPropertyChanged();
}
}
}
protected virtual void MyCommandExecute(object obj)
{
switch (int.Parse(obj.ToString()))
{
case 1:
CurrentViewModel = new MyViewModel1();
break;
case 2:
CurrentViewModel = new MyViewModel2();
break;
}
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class MyViewModel1 : MyViewModel
{
public RelayCommand<object> MyCommand1 { get; set; }
public MyViewModel1()
{
MyCommand1 = new RelayCommand<object>(MyCommand1Execute);
}
private void MyCommand1Execute(object obj)
{
Debug.WriteLine("MyCommand1");
}
}
public class MyViewModel2 : MyViewModel
{
public RelayCommand<object> MyCommand2 { get; set; }
public MyViewModel2()
{
MyCommand2 = new RelayCommand<object>(MyCommand2Execute);
}
private void MyCommand2Execute(object obj)
{
Debug.WriteLine("MyCommand2");
}
}
The code behind the UserControl1 is
public partial class UserControl1 : UserControl
{
public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register("ViewModel", typeof(MyViewModel1), typeof(UserControl1));
public UserControl1()
{
InitializeComponent();
}
public MyViewModel1 ViewModel
{
get { return GetValue(ViewModelProperty) as MyViewModel1; }
set { SetValue(ViewModelProperty, value); }
}
}
I have created the ViewModel Property as a DependencyProperty so I can bind to it from the MainWindow.
The Xaml of the user control is
<UserControl x:Class="StackOverflow._20937791.UserControl1" 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:this="clr-namespace:StackOverflow._20937791"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300">
<StackPanel DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type this:UserControl1}}, Path=ViewModel}">
<Button Content="View 1 Command" Command="{Binding Path=MyCommand1}" />
</StackPanel>
</UserControl>
Note I have set up the DataContext on the first content element of the control. The bindings on all child elements are against the ViewModel of the UserControl while any incoming bindings (from the parent control) will be evaluated from the DataContext of that parent control.
Another point to note is that by defining the DataContext in the Xaml, you will get autocomplete in the Binding expressions which will cut down on bad expression errors.
The second UserControl is the same but the ViewModel is of type MyViewModel2.
Finally, the code for the MainWindow is
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public MyViewModel ViewModel { get; set; }
}
The Xaml is
<Window x:Class="StackOverflow._20937791.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:this="clr-namespace:StackOverflow._20937791"
DataContext="{Binding RelativeSource={RelativeSource Self}, Path=ViewModel}"
Title="MainWindow" Height="200" Width="300">
<Window.Resources>
<DataTemplate DataType="{x:Type this:MyViewModel1}">
<this:UserControl1 ViewModel="{Binding}" />
</DataTemplate>
<DataTemplate DataType="{x:Type this:MyViewModel2}">
<this:UserControl2 ViewModel="{Binding}" />
</DataTemplate>
</Window.Resources>
<StackPanel>
<StackPanel Orientation="Horizontal">
<Button Content="Show View 1" Command="{Binding Path=MyCommand}" CommandParameter="1" Width="100" Margin="4" />
<Button Content="Show View 2" Command="{Binding Path=MyCommand}" CommandParameter="2" Width="100" Margin="0 4" />
</StackPanel>
<ContentControl Content="{Binding Path=CurrentViewModel}" Margin="20" />
</StackPanel>
</Window>
The UserControl is referenced in the main window and it has its ViewModel passed in.
The application shows a window that looks like
I hope this helps.
Firt, FYI - your approach is called the strategy pattern.
Now what you are doing sounds right but it's hard withou seeing your xaml.
Maybe you need to raise a propertychanged event after setting your vm properties?
It would be helpful if you would post your code .But if I havent misunderstood your question then you can try this
<Button Command="{Binding MainControlVM.ClickCommand}"
Set the binding MainControlVM.ClickCommand .Here ClickCommand is the name of your Command.
Update
I think the issue is in Setting the CurrentViewModel. You are setting the CurrentViewModel in the Action Of Command. I think you want to set the CurrentViewModel on the basis of Command. I think this could be better by CommandParameter . Like Bind all Buttons to same Base ViewModel Command and from each Command pass the different CommandParameter and then on Command compare that CommandParameter and set CurrentViewModel accordingly.
ViewModelBase ,Child1ViewModel ,Child2ViewModel
public class ViewModelBase:INotifyPropertyChanged
{
private ICommand _clickCommand;
public ICommand ClickCommand
{
get
{
return _clickCommand ?? (_clickCommand = new CommandHandler(MyAction,()=>true));
}
}
public void MyAction(object obj)
{
if(obj == null )
return;
//if CommandParameter is Cild1VM
if (obj.ToString() == "Child1VM")
CurrentViewModel = new Child1ViewModel();
//if CommandParameter is Cild1VM
else if (obj.ToString() == "Child2VM")
CurrentViewModel = new Child2ViewModel();
}
ViewModelBase _currentViewModel;
public ViewModelBase CurrentViewModel
{
get { return _currentViewModel; }
set
{
if (_currentViewModel == value)
return;
_currentViewModel = value;
this.RaiseNotifyPropertyChanged("CurrentViewModel");
}
}
public event PropertyChangedEventHandler PropertyChanged;
void RaiseNotifyPropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public class Child1ViewModel : ViewModelBase
{ }
public class Child2ViewModel : ViewModelBase
{ }
xaml
<StackPanel>
<Button Content="Foo" Command="{Binding ClickCommand}" CommandParameter="Child1VM"/>
<Button Content="Bar" Command="{Binding ClickCommand}" CommandParameter="Child2VM"/>
</StackPanel>
xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModelBase();
}
}
I hope this will give you an idea.

Categories