更新对嵌套 ViewModel 命令的引用?

发布于 2024-10-07 16:01:50 字数 24255 浏览 0 评论 0原文

我知道我可能错过了一些简单而明显的东西,但目前我却无法理解。

我正在尝试使用 MVVM 模式。

如何更新对链接到子视图模型的视图模型中的命令的引用?

我有一个绑定到视图模型(MainViewModel)的视图(MainView)。 在 MainView 上,我有一个绑定到视图模型 (SummaryViewModel) 的另一个视图 (SummaryView) 的实例。 SummaryViewModel 包含第三个视图模型(SummaryFilterViewModel)的集合。

在 SummaryView 上,有一个 TabControl,其上的每个选项卡都绑定到 SummaryViewodel 集合中的 SummaryFilterViewModel 实例之一。

在 MainView 上有一个按钮绑定到 MainViewModel 中的命令。

我想要发生的是将命令逻辑置于 SummaryFilterViewModel 类中。因此,当前显示的任何选项卡都需要连接到 MainView 上的按钮触发的命令。

我尝试做的是:

  1. SummaryViewModel 集合中存储的各个 SummaryFilterViewModel 对象保存 ShoutCommand 的实际实现。
  2. MainView 的 XAML 中的 CommandReference 对象绑定到 MainViewModel 的 ShoutCommand 属性
  3. MainViewModel 的 ShoutCommand 属性返回对存储在 MainViewModel 中的 SummaryViewModel 对象的 ShoutCommand 属性的引用。
  4. 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:

  1. The individual SummaryFilterViewModel objects stored in the collection in SummaryViewModel hold the actual implementations of the ShoutCommand.
  2. A CommandReference object in the XAML of MainView binds to a ShoutCommand property of the MainViewModel
  3. The ShoutCommand property of the MainViewModel returns a reference to the ShoutCommand property of the SummaryViewModel object stored in MainViewModel.
  4. 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

知足的幸福 2024-10-14 16:01:50

我认为你所走的道路很快就会变得复杂且紧密耦合。您可能应该考虑使用 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:

睫毛上残留的泪 2024-10-14 16:01:50

为了响应 Kent Boogaart 提出的一些建议,我对示例解决方案进行了一些更改。肯特,再次感谢您的回复,它给了我新的前进方向。

我会尽量保持简短。

  • MainView 基本上是一个框架集,包含应用程序的主命令界面。在示例中,SummaryView 直接嵌入到 MainView 的 XAML 中。在真正的解决方案中,它是一个可能包含不同类型的子视图的内容控件。每种类型的子视图可能会也可能不会执行该命令。
  • 我能够将 SelectedIndex 连接到一个属性,这样我就不需要依赖于 System.Windows.Control 库。当该属性更改时,我还会为 ShoutCommand 属性调用 OnPropertyChanged。
  • 然而,这并没有将该更改中继到 MainView 对象。因此,在 MainViewModel 中,我监听 _SummaryViewModel.PropertyChanged 事件。
  • 当 MainView 听到 _SummaryViewModel.PropertyChanged 事件触发时,我调用 OnPropertyChanged(this, "ShoutCommand") 将更改传播到 MainView。

所以,我想我想知道 MainViewModel 是否有必要像我一样监听 _SummaryViewModel 的 PropertyChanged 事件,或者是否有更干净的方法来做到这一点。

我的代码如下:(我尽力取出)

谢谢!

MainView

<v:SummaryView Grid.Row="0"
               DataContext="{Binding SummaryViewModel}" />
<Button Content="Shout Command"
        Grid.Row="1"
        Command="{Binding ShoutCommand}" />

MainViewModel

public MainViewModel()
{
    _SummaryViewModel = new SummaryViewModel();
    _SummaryViewModel.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(_SummaryViewModel_PropertyChanged);
}

void _SummaryViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
    switch (e.PropertyName)
    {
        case "ShoutCommand":
            OnPropertyChanged(this, "ShoutCommand");
            break;
    }
}

private SummaryViewModel _SummaryViewModel;
public SummaryViewModel SummaryViewModel {...}

public ICommand ShoutCommand
{
    get { return _SummaryViewModel.ShoutCommand; }
}

SummaryView

<TabControl SelectedIndex="{Binding SelectedTabIndex}">
    <TabItem DataContext="{Binding Filters[0]}" Header="{Binding FilterName}">
        <ListBox ItemsSource="{Binding ListData}" />
    </TabItem>
    <!-- TabItem repeated two more times -->
</TabControl>

SummaryViewModel

private List<SummaryFilterViewModel> _Filters;
public List<SummaryFilterViewModel> Filters {...}

private int _SelectedTabIndex;
public int SelectedTabIndex 
{
    get { return _SelectedTabIndex; }
    set
    {
        _SelectedTabIndex = value;
        OnPropertyChanged(this, "SelectedTabIndex");
        OnPropertyChanged(this, "ShoutCommand");
    }
}

public ICommand ShoutCommand
{
    get {
        int selectedTabIndex = SelectedTabIndex;

        return (selectedTabIndex == -1) ? null : Filters[SelectedTabIndex].ShoutCommand; 
    }
}

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.

  • The MainView is basically a frameset that houses the application's main command interface. In the sample the SummaryView is embedded directly in MainView's XAML. In the real solution it's a content control that may contain different types of child views. Each type of child view may or may not implement the command.
  • I was able to wire the SelectedIndex to a property so that I wouldn't need a dependency on the System.Windows.Control library. When that property changes, I also call OnPropertyChanged for the ShoutCommand property.
  • This, however, did not relay that change to the MainView object. So, in MainViewModel, I listen for the _SummaryViewModel.PropertyChanged event.
  • When MainView hears that the _SummaryViewModel.PropertyChanged event fired, I call OnPropertyChanged(this, "ShoutCommand") which propagates the change to the MainView.

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

<v:SummaryView Grid.Row="0"
               DataContext="{Binding SummaryViewModel}" />
<Button Content="Shout Command"
        Grid.Row="1"
        Command="{Binding ShoutCommand}" />

MainViewModel

public MainViewModel()
{
    _SummaryViewModel = new SummaryViewModel();
    _SummaryViewModel.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(_SummaryViewModel_PropertyChanged);
}

void _SummaryViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
    switch (e.PropertyName)
    {
        case "ShoutCommand":
            OnPropertyChanged(this, "ShoutCommand");
            break;
    }
}

private SummaryViewModel _SummaryViewModel;
public SummaryViewModel SummaryViewModel {...}

public ICommand ShoutCommand
{
    get { return _SummaryViewModel.ShoutCommand; }
}

SummaryView

<TabControl SelectedIndex="{Binding SelectedTabIndex}">
    <TabItem DataContext="{Binding Filters[0]}" Header="{Binding FilterName}">
        <ListBox ItemsSource="{Binding ListData}" />
    </TabItem>
    <!-- TabItem repeated two more times -->
</TabControl>

SummaryViewModel

private List<SummaryFilterViewModel> _Filters;
public List<SummaryFilterViewModel> Filters {...}

private int _SelectedTabIndex;
public int SelectedTabIndex 
{
    get { return _SelectedTabIndex; }
    set
    {
        _SelectedTabIndex = value;
        OnPropertyChanged(this, "SelectedTabIndex");
        OnPropertyChanged(this, "ShoutCommand");
    }
}

public ICommand ShoutCommand
{
    get {
        int selectedTabIndex = SelectedTabIndex;

        return (selectedTabIndex == -1) ? null : Filters[SelectedTabIndex].ShoutCommand; 
    }
}
半山落雨半山空 2024-10-14 16:01:50

你的帖子很长,我承认我没有完全阅读它。但是,我不明白 CommandReference 的用途。为什么不直接绑定到MainViewModel.ShoutCommand?考虑:

  • TabControlItemsSource 绑定到子视图模型的集合
  • TabControlSelectedItem 绑定到跟踪所选子视图模型的另一个属性
  • 当上述属性发生更改时,也会引发 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 to MainViewModel.ShoutCommand? Consider:

  • Bind the ItemsSource of the TabControl to the collection of child view models
  • Bind the SelectedItem of the TabControl to another property that tracks the selected child view model
  • When the aforementioned property changes, raise the PropertyChanged event for the ShoutCommand property, too
  • In the getter for ShoutCommand property, simply return the ShoutCommand of the selected child view model
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文