更新对嵌套 ViewModel 命令的引用?
我知道我可能错过了一些简单而明显的东西,但目前我却无法理解。
我正在尝试使用 MVVM 模式。
如何更新对链接到子视图模型的视图模型中的命令的引用?
我有一个绑定到视图模型(MainViewModel)的视图(MainView)。 在 MainView 上,我有一个绑定到视图模型 (SummaryViewModel) 的另一个视图 (SummaryView) 的实例。 SummaryViewModel 包含第三个视图模型(SummaryFilterViewModel)的集合。
在 SummaryView 上,有一个 TabControl,其上的每个选项卡都绑定到 SummaryViewodel 集合中的 SummaryFilterViewModel 实例之一。
在 MainView 上有一个按钮绑定到 MainViewModel 中的命令。
我想要发生的是将命令逻辑置于 SummaryFilterViewModel 类中。因此,当前显示的任何选项卡都需要连接到 MainView 上的按钮触发的命令。
我尝试做的是:
- SummaryViewModel 集合中存储的各个 SummaryFilterViewModel 对象保存 ShoutCommand 的实际实现。
- MainView 的 XAML 中的 CommandReference 对象绑定到 MainViewModel 的 ShoutCommand 属性
- MainViewModel 的 ShoutCommand 属性返回对存储在 MainViewModel 中的 SummaryViewModel 对象的 ShoutCommand 属性的引用。
- SummaryViewModel 的 ShoutCommand 属性返回对当前选定的 SummaryFilterViewModel 的 ShoutCommand 属性的引用。
发生的情况是,当用户更改选项卡时该命令不会更新。
我在如何实现这一点上是否偏离了基础? 我是否需要将命令的实现移至 SummaryViewModel 类中?
预先感谢您的任何帮助!
下面列出了我的解决方案的来源:
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>
命令类
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);
}
}
}
}
}
}
视图模型
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
}
}
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
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我认为你所走的道路很快就会变得复杂且紧密耦合。您可能应该考虑使用 Mediator 模式 来促进 SummaryFilterViewModel 中更改的通信到您的 MainViewModel。
使用中介者模式,您可以实现一种订阅和发布消息的方法,该方法允许一个视图模型与另一个视图模型进行通信,而不会导致视图模型紧密耦合。
基本上,当您的选项卡选择发生更改时,摘要视图模型将使用包含引用对象或其他数据的消息负载来发布更改。主视图模型将订阅此消息的发布并相应地修改其状态。
您可以查看有关中介模式的一些资源:
I think the path you are going down is quickly going to end up complex and tightly coupled. You should probably take a look at using the Mediator Pattern to facilitate communication of changes in your SummaryFilterViewModel to your MainViewModel.
Using the mediator pattern, you can implement a means of subscribing to and publishing messages that allows one view model to communicate with another view model without ending up with tightly coupled view models.
Basically, when your tab selection changes, the summary view model would publish the change with the message payload containing the reference object or other data. The main view model would be subscribed to publication of this message and modify its state accordingly.
Some resources on the Mediator Pattern you can take a look at:
为了响应 Kent Boogaart 提出的一些建议,我对示例解决方案进行了一些更改。肯特,再次感谢您的回复,它给了我新的前进方向。
我会尽量保持简短。
所以,我想我想知道 MainViewModel 是否有必要像我一样监听 _SummaryViewModel 的 PropertyChanged 事件,或者是否有更干净的方法来做到这一点。
我的代码如下:(我尽力取出)
谢谢!
MainView
MainViewModel
SummaryView
SummaryViewModel
I've made some changes to my sample solution in response to some suggestions that were made by Kent Boogaart. Kent, thank you again for your reply it gave me a new direction to move.
I'll try to keep this as short as possible.
So, I guess I want to know if it's necessary for the MainViewModel to listen to the _SummaryViewModel's PropertyChanged event like I'm doing, or if there is a cleaner way to do it.
My code is listed below: (I tried to take out as much as I could)
Thanks!
MainView
MainViewModel
SummaryView
SummaryViewModel
你的帖子很长,我承认我没有完全阅读它。但是,我不明白
CommandReference
的用途。为什么不直接绑定到MainViewModel.ShoutCommand
?考虑:TabControl
的ItemsSource
绑定到子视图模型的集合TabControl
的SelectedItem
绑定到跟踪所选子视图模型的另一个属性ShoutCommand
属性的PropertyChanged
事件ShoutCommand
的 getter 中> 属性,只需返回所选子视图模型的ShoutCommand
Your post was long and I confess I didn't fully read it. However, I don't understand the purpose of
CommandReference
. Why not just bind directly toMainViewModel.ShoutCommand
? Consider:ItemsSource
of theTabControl
to the collection of child view modelsSelectedItem
of theTabControl
to another property that tracks the selected child view modelPropertyChanged
event for theShoutCommand
property, tooShoutCommand
property, simply return theShoutCommand
of the selected child view model