I know I'm probably missing something simple and obvious, but at the moment it eludes me.
I'm attempting to use the MVVM pattern.
How do you update a reference to a command in a viewmodel that is linked to a child viewmodel?
I've got a view (MainView) bound to a viewmodel (MainViewModel). On MainView, I've got an instance of another view (SummaryView) bound to a viewmodel (SummaryViewModel). SummaryViewModel contains a collection of a third viewmodel (SummaryFilterViewModel).
On SummaryView, there is a TabControl and each tab on it is bound to one of the SummaryFilterViewModel instances in the SummaryViewodel collection.
On MainView there is a button that is bound to a command in MainViewModel.
What I want to happen is for the command logic to live within the SummaryFilterViewModel class. So, whichever tab is currently displayed needs to be wired up to the command that the button on MainView fires.
What I tried to do was this:
- The individual SummaryFilterViewModel objects stored in the collection in SummaryViewModel hold the actual implementations of the ShoutCommand.
- A CommandReference object in the XAML of MainView binds to a ShoutCommand property of the MainViewModel
- The ShoutCommand property of the MainViewModel returns a reference to the ShoutCommand property of the SummaryViewModel object stored in MainViewModel.
- The ShoutCommand property of the SummaryViewModel returns a reference to the ShoutCommand property of whichever is the currently selected SummaryFilterViewModel.
What happens, is that the command does not get updated when the user changes tabs.
Am I way off base in how to implement this? Do I need to move the implementation of the command into the SummaryViewModel class?
Thanks in advance for any help!
The source to my solution is listed below:
ViewModels
SummaryView.xaml
<UserControl x:Class="NestedCommands.Views.SummaryView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="309" d:DesignWidth="476">
<Grid>
<TabControl SelectedIndex="{Binding SelectedTabIndex}">
<TabItem DataContext="{Binding Filters[0]}" Header="{Binding FilterName}">
<ListBox ItemsSource="{Binding ListData}" />
</TabItem>
<TabItem DataContext="{Binding Filters[1]}" Header="{Binding FilterName}">
<ListBox ItemsSource="{Binding ListData}" />
</TabItem>
<TabItem DataContext="{Binding Filters[2]}" Header="{Binding FilterName}">
<ListBox ItemsSource="{Binding ListData}" />
</TabItem>
</TabControl>
</Grid>
MainView.xaml
<Window x:Class="NestedCommands.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:v="clr-namespace:NestedCommands.Views"
xmlns:c="clr-namespace:NestedCommands.Commands"
Title="MainView" Height="336" Width="420">
<Window.Resources>
<c:CommandReference x:Key="ShoutCommandReference" Command="{Binding ShoutCommand}" />
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<v:SummaryView Grid.Row="0"
DataContext="{Binding SummaryViewModel}" />
<Button Content="Shout Command"
Grid.Row="1"
Command="{StaticResource ShoutCommandReference}" />
</Grid>
Command Classes
CommandReference.cs
using System;
using System.Windows;
using System.Windows.Input;
namespace NestedCommands.Commands
{
/// <summary>
/// This class facilitates associating a key binding in XAML markup to a command
/// defined in a View Model by exposing a Command dependency property.
/// The class derives from Freezable to work around a limitation in WPF when data-binding from XAML.
/// </summary>
public class CommandReference : Freezable, ICommand
{
public CommandReference()
{
// Blank
}
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(CommandReference), new PropertyMetadata(new PropertyChangedCallback(OnCommandChanged)));
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
#region ICommand Members
public bool CanExecute(object parameter)
{
if (Command != null)
return Command.CanExecute(parameter);
return false;
}
public void Execute(object parameter)
{
Command.Execute(parameter);
}
public event EventHandler CanExecuteChanged;
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CommandReference commandReference = d as CommandReference;
ICommand oldCommand = e.OldValue as ICommand;
ICommand newCommand = e.NewValue as ICommand;
if (oldCommand != null)
{
oldCommand.CanExecuteChanged -= commandReference.CanExecuteChanged;
}
if (newCommand != null)
{
newCommand.CanExecuteChanged += commandReference.CanExecuteChanged;
}
}
#endregion
#region Freezable
protected override Freezable CreateInstanceCore()
{
throw new NotImplementedException();
}
#endregion
}
}
DelegateCommand.cs
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
namespace NestedCommands.Commands
{
/// <summary>
/// This class allows delegating the commanding logic to methods passed as parameters,
/// and enables a View to bind commands to objects that are not part of the element tree.
/// </summary>
public class DelegateCommand : ICommand
{
#region Constructors
/// <summary>
/// Constructor
/// </summary>
public DelegateCommand(Action executeMethod)
: this(executeMethod, null, false)
{
}
/// <summary>
/// Constructor
/// </summary>
public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod)
: this(executeMethod, canExecuteMethod, false)
{
}
/// <summary>
/// Constructor
/// </summary>
public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod, bool isAutomaticRequeryDisabled)
{
if (executeMethod == null)
{
throw new ArgumentNullException("executeMethod");
}
_executeMethod = executeMethod;
_canExecuteMethod = canExecuteMethod;
_isAutomaticRequeryDisabled = isAutomaticRequeryDisabled;
}
#endregion
#region Public Methods
/// <summary>
/// Method to determine if the command can be executed
/// </summary>
public bool CanExecute()
{
if (_canExecuteMethod != null)
{
return _canExecuteMethod();
}
return true;
}
/// <summary>
/// Execution of the command
/// </summary>
public void Execute()
{
if (_executeMethod != null)
{
_executeMethod();
}
}
/// <summary>
/// Property to enable or disable CommandManager's automatic requery on this command
/// </summary>
public bool IsAutomaticRequeryDisabled
{
get
{
return _isAutomaticRequeryDisabled;
}
set
{
if (_isAutomaticRequeryDisabled != value)
{
if (value)
{
CommandManagerHelper.RemoveHandlersFromRequerySuggested(_canExecuteChangedHandlers);
}
else
{
CommandManagerHelper.AddHandlersToRequerySuggested(_canExecuteChangedHandlers);
}
_isAutomaticRequeryDisabled = value;
}
}
}
/// <summary>
/// Raises the CanExecuteChaged event
/// </summary>
public void RaiseCanExecuteChanged()
{
OnCanExecuteChanged();
}
/// <summary>
/// Protected virtual method to raise CanExecuteChanged event
/// </summary>
protected virtual void OnCanExecuteChanged()
{
CommandManagerHelper.CallWeakReferenceHandlers(_canExecuteChangedHandlers);
}
#endregion
#region ICommand Members
/// <summary>
/// ICommand.CanExecuteChanged implementation
/// </summary>
public event EventHandler CanExecuteChanged
{
add
{
if (!_isAutomaticRequeryDisabled)
{
CommandManager.RequerySuggested += value;
}
CommandManagerHelper.AddWeakReferenceHandler(ref _canExecuteChangedHandlers, value, 2);
}
remove
{
if (!_isAutomaticRequeryDisabled)
{
CommandManager.RequerySuggested -= value;
}
CommandManagerHelper.RemoveWeakReferenceHandler(_canExecuteChangedHandlers, value);
}
}
bool ICommand.CanExecute(object parameter)
{
return CanExecute();
}
void ICommand.Execute(object parameter)
{
Execute();
}
#endregion
#region Data
private readonly Action _executeMethod = null;
private readonly Func<bool> _canExecuteMethod = null;
private bool _isAutomaticRequeryDisabled = false;
private List<WeakReference> _canExecuteChangedHandlers;
#endregion
}
/// <summary>
/// This class allows delegating the commanding logic to methods passed as parameters,
/// and enables a View to bind commands to objects that are not part of the element tree.
/// </summary>
/// <typeparam name="T">Type of the parameter passed to the delegates</typeparam>
public class DelegateCommand<T> : ICommand
{
#region Constructors
/// <summary>
/// Constructor
/// </summary>
public DelegateCommand(Action<T> executeMethod)
: this(executeMethod, null, false)
{
}
/// <summary>
/// Constructor
/// </summary>
public DelegateCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod)
: this(executeMethod, canExecuteMethod, false)
{
}
/// <summary>
/// Constructor
/// </summary>
public DelegateCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod, bool isAutomaticRequeryDisabled)
{
if (executeMethod == null)
{
throw new ArgumentNullException("executeMethod");
}
_executeMethod = executeMethod;
_canExecuteMethod = canExecuteMethod;
_isAutomaticRequeryDisabled = isAutomaticRequeryDisabled;
}
#endregion
#region Public Methods
/// <summary>
/// Method to determine if the command can be executed
/// </summary>
public bool CanExecute(T parameter)
{
if (_canExecuteMethod != null)
{
return _canExecuteMethod(parameter);
}
return true;
}
/// <summary>
/// Execution of the command
/// </summary>
public void Execute(T parameter)
{
if (_executeMethod != null)
{
_executeMethod(parameter);
}
}
/// <summary>
/// Raises the CanExecuteChaged event
/// </summary>
public void RaiseCanExecuteChanged()
{
OnCanExecuteChanged();
}
/// <summary>
/// Protected virtual method to raise CanExecuteChanged event
/// </summary>
protected virtual void OnCanExecuteChanged()
{
CommandManagerHelper.CallWeakReferenceHandlers(_canExecuteChangedHandlers);
}
/// <summary>
/// Property to enable or disable CommandManager's automatic requery on this command
/// </summary>
public bool IsAutomaticRequeryDisabled
{
get
{
return _isAutomaticRequeryDisabled;
}
set
{
if (_isAutomaticRequeryDisabled != value)
{
if (value)
{
CommandManagerHelper.RemoveHandlersFromRequerySuggested(_canExecuteChangedHandlers);
}
else
{
CommandManagerHelper.AddHandlersToRequerySuggested(_canExecuteChangedHandlers);
}
_isAutomaticRequeryDisabled = value;
}
}
}
#endregion
#region ICommand Members
/// <summary>
/// ICommand.CanExecuteChanged implementation
/// </summary>
public event EventHandler CanExecuteChanged
{
add
{
if (!_isAutomaticRequeryDisabled)
{
CommandManager.RequerySuggested += value;
}
CommandManagerHelper.AddWeakReferenceHandler(ref _canExecuteChangedHandlers, value, 2);
}
remove
{
if (!_isAutomaticRequeryDisabled)
{
CommandManager.RequerySuggested -= value;
}
CommandManagerHelper.RemoveWeakReferenceHandler(_canExecuteChangedHandlers, value);
}
}
bool ICommand.CanExecute(object parameter)
{
// if T is of value type and the parameter is not
// set yet, then return false if CanExecute delegate
// exists, else return true
if (parameter == null &&
typeof(T).IsValueType)
{
return (_canExecuteMethod == null);
}
return CanExecute((T)parameter);
}
void ICommand.Execute(object parameter)
{
Execute((T)parameter);
}
#endregion
#region Data
private readonly Action<T> _executeMethod = null;
private readonly Func<T, bool> _canExecuteMethod = null;
private bool _isAutomaticRequeryDisabled = false;
private List<WeakReference> _canExecuteChangedHandlers;
#endregion
}
/// <summary>
/// This class contains methods for the CommandManager that help avoid memory leaks by
/// using weak references.
/// </summary>
internal class CommandManagerHelper
{
internal static void CallWeakReferenceHandlers(List<WeakReference> handlers)
{
if (handlers != null)
{
// Take a snapshot of the handlers before we call out to them since the handlers
// could cause the array to me modified while we are reading it.
EventHandler[] callees = new EventHandler[handlers.Count];
int count = 0;
for (int i = handlers.Count - 1; i >= 0; i--)
{
WeakReference reference = handlers[i];
EventHandler handler = reference.Target as EventHandler;
if (handler == null)
{
// Clean up old handlers that have been collected
handlers.RemoveAt(i);
}
else
{
callees[count] = handler;
count++;
}
}
// Call the handlers that we snapshotted
for (int i = 0; i < count; i++)
{
EventHandler handler = callees[i];
handler(null, EventArgs.Empty);
}
}
}
internal static void AddHandlersToRequerySuggested(List<WeakReference> handlers)
{
if (handlers != null)
{
foreach (WeakReference handlerRef in handlers)
{
EventHandler handler = handlerRef.Target as EventHandler;
if (handler != null)
{
CommandManager.RequerySuggested += handler;
}
}
}
}
internal static void RemoveHandlersFromRequerySuggested(List<WeakReference> handlers)
{
if (handlers != null)
{
foreach (WeakReference handlerRef in handlers)
{
EventHandler handler = handlerRef.Target as EventHandler;
if (handler != null)
{
CommandManager.RequerySuggested -= handler;
}
}
}
}
internal static void AddWeakReferenceHandler(ref List<WeakReference> handlers, EventHandler handler)
{
AddWeakReferenceHandler(ref handlers, handler, -1);
}
internal static void AddWeakReferenceHandler(ref List<WeakReference> handlers, EventHandler handler, int defaultListSize)
{
if (handlers == null)
{
handlers = (defaultListSize > 0 ? new List<WeakReference>(defaultListSize) : new List<WeakReference>());
}
handlers.Add(new WeakReference(handler));
}
internal static void RemoveWeakReferenceHandler(List<WeakReference> handlers, EventHandler handler)
{
if (handlers != null)
{
for (int i = handlers.Count - 1; i >= 0; i--)
{
WeakReference reference = handlers[i];
EventHandler existingHandler = reference.Target as EventHandler;
if ((existingHandler == null) || (existingHandler == handler))
{
// Clean up old handlers that have been collected
// in addition to the handler that is to be removed.
handlers.RemoveAt(i);
}
}
}
}
}
}
View Models
ViewModelBase.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace NestedCommands.ViewModels
{
class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(object sender, string propertyName)
{
if (this.PropertyChanged != null)
{
PropertyChanged(sender, new PropertyChangedEventArgs(propertyName));
}
}
}
}
MainViewModel.cs
using System;
using System.Windows.Input;
namespace NestedCommands.ViewModels
{
class MainViewModel : ViewModelBase
{
public MainViewModel()
{
_SummaryViewModel = new SummaryViewModel();
}
private SummaryViewModel _SummaryViewModel;
public SummaryViewModel SummaryViewModel
{
get { return _SummaryViewModel; }
set
{
_SummaryViewModel = value;
OnPropertyChanged(this, "SummaryViewModel");
}
}
public ICommand ShoutCommand
{
get { return _SummaryViewModel.ShoutCommand; }
}
}
}
SummaryViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NestedCommands.Commands;
using System.Windows.Input;
namespace NestedCommands.ViewModels
{
class SummaryViewModel : ViewModelBase
{
#region Constructor
public SummaryViewModel()
{
List<SummaryFilterViewModel> filters = new List<SummaryFilterViewModel>();
filters.Add(new SummaryFilterViewModel("Filter 1"));
filters.Add(new SummaryFilterViewModel("Filter 2"));
filters.Add(new SummaryFilterViewModel("Filter 3"));
Filters = filters;
}
#endregion
#region Properties
private List<SummaryFilterViewModel> _Filters;
public List<SummaryFilterViewModel> Filters
{
get { return _Filters; }
set
{
_Filters = value;
OnPropertyChanged(this, "Filters");
}
}
private int _SelectedTabIndex;
public int SelectedTabIndex
{
get { return _SelectedTabIndex; }
set
{
_SelectedTabIndex = value;
OnPropertyChanged(this, "SelectedTabIndex");
}
}
#endregion
#region Command References
public ICommand ShoutCommand
{
get { return Filters[SelectedTabIndex].ShoutCommand; }
}
#endregion
}
}
SummaryFilterViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NestedCommands.Commands;
using System.Windows.Input;
namespace NestedCommands.ViewModels
{
class SummaryFilterViewModel : ViewModelBase
{
#region Constructor
public SummaryFilterViewModel(string FilterName)
{
this.FilterName = FilterName;
List<string> listData = new List<string>();
for (int i = 1; i < 10; i++)
{
listData.Add(string.Format("{0}: {1}", FilterName, i));
}
ListData = listData;
}
#endregion
#region Properties
private string _FilterName;
public string FilterName
{
get { return _FilterName; }
set
{
_FilterName = value;
OnPropertyChanged(this, "FilterName");
}
}
private List<string> _ListData;
public List<string> ListData
{
get { return _ListData; }
set
{
_ListData = value;
OnPropertyChanged(this, "ListData");
}
}
#endregion
#region Shout Command
private DelegateCommand _ShoutCommand;
public ICommand ShoutCommand
{
get { return _ShoutCommand ?? (_ShoutCommand = new DelegateCommand(Shout, CanShout)); }
}
private void Shout()
{
System.Windows.MessageBox.Show(string.Format("Called from SummaryFilterViewModel: {0}", FilterName));
}
private bool CanShout()
{
return true;
}
#endregion
}
}