在MVVM中为View提供一些命令

我们假设我有一些用户控制权。 用户控件有一些子窗口。 并且用户控制用户想要关闭某种类型的子窗口。 用户控制代码中有一种方法:

public void CloseChildWindows(ChildWindowType type) { ... } 

但我无法调用此方法,因为我无法直接访问该视图。

我想到的另一个解决方案是以某种方式将用户控件ViewModel公开为其属性之一(因此我可以绑定它并直接向ViewModel发出命令)。 但我不希望用户控制用户知道有关用户控件ViewModel的任何信息。

那么解决这个问题的正确方法是什么?

我觉得我刚刚找到了一个相当不错的MVVM解决方案来解决这个问题。 我写了一个行为,它暴露了一个类型属性WindowType和一个布尔属性Open 。 DataBinding后者允许ViewModel轻松打开和关闭窗口,而不了解View的任何信息。

得爱的行为…… 🙂

在此处输入图像描述

XAML:

            5                        

YellowWindow(黑色/紫色):

    

ViewModel,ActionCommand:

 using System; using System.ComponentModel; using System.Windows.Input; namespace WpfApplication1 { public class ViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged(string propertyName) { if (this.PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } private bool _blackOpen; public bool BlackOpen { get { return _blackOpen; } set { _blackOpen = value; OnPropertyChanged("BlackOpen"); } } private bool _yellowOpen; public bool YellowOpen { get { return _yellowOpen; } set { _yellowOpen = value; OnPropertyChanged("YellowOpen"); } } private bool _purpleOpen; public bool PurpleOpen { get { return _purpleOpen; } set { _purpleOpen = value; OnPropertyChanged("PurpleOpen"); } } public ICommand OpenBlackCommand { get; private set; } public ICommand OpenYellowCommand { get; private set; } public ICommand OpenPurpleCommand { get; private set; } public ViewModel() { this.OpenBlackCommand = new ActionCommand(OpenBlack); this.OpenYellowCommand = new ActionCommand(OpenYellow); this.OpenPurpleCommand = new ActionCommand(OpenPurple); } private void OpenBlack(bool open) { this.BlackOpen = open; } private void OpenYellow(bool open) { this.YellowOpen = open; } private void OpenPurple(bool open) { this.PurpleOpen = open; } } public class ActionCommand : ICommand { public event EventHandler CanExecuteChanged; private Action _action; public ActionCommand(Action action) { _action = action; } public bool CanExecute(object parameter) { return true; } public void Execute(object parameter) { if (_action != null) { var castParameter = (T)Convert.ChangeType(parameter, typeof(T)); _action(castParameter); } } } } 

OpenCloseWindowBehavior:

 using System; using System.Windows; using System.Windows.Controls; using System.Windows.Interactivity; namespace WpfApplication1 { public class OpenCloseWindowBehavior : Behavior { private Window _windowInstance; public Type WindowType { get { return (Type)GetValue(WindowTypeProperty); } set { SetValue(WindowTypeProperty, value); } } public static readonly DependencyProperty WindowTypeProperty = DependencyProperty.Register("WindowType", typeof(Type), typeof(OpenCloseWindowBehavior), new PropertyMetadata(null)); public bool Open { get { return (bool)GetValue(OpenProperty); } set { SetValue(OpenProperty, value); } } public static readonly DependencyProperty OpenProperty = DependencyProperty.Register("Open", typeof(bool), typeof(OpenCloseWindowBehavior), new PropertyMetadata(false, OnOpenChanged)); ///  /// Opens or closes a window of type 'WindowType'. ///  private static void OnOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var me = (OpenCloseWindowBehavior)d; if ((bool)e.NewValue) { object instance = Activator.CreateInstance(me.WindowType); if (instance is Window) { Window window = (Window)instance; window.Closing += (s, ev) => { if (me.Open) // window closed directly by user { me._windowInstance = null; // prevents repeated Close call me.Open = false; // set to false, so next time Open is set to true, OnOpenChanged is triggered again } }; window.Show(); me._windowInstance = window; } else { // could check this already in PropertyChangedCallback of WindowType - but doesn't matter until someone actually tries to open it. throw new ArgumentException(string.Format("Type '{0}' does not derive from System.Windows.Window.", me.WindowType)); } } else { if (me._windowInstance != null) me._windowInstance.Close(); // closed by viewmodel } } } } 

我过去通过引入WindowManager的概念来处理这种情况,这是一个可怕的名称,所以让我们将它与WindowViewModel配对,这只是稍微不那么可怕 – 但基本的想法是:

 public class WindowManager { public WindowManager() { VisibleWindows = new ObservableCollection(); VisibleWindows.CollectionChanged += OnVisibleWindowsChanged; } public ObservableCollection VisibleWindows {get; private set;} private void OnVisibleWindowsChanged(object sender, NotifyCollectionChangedEventArgs args) { // process changes, close any removed windows, open any added windows, etc. } } public class WindowViewModel : INotifyPropertyChanged { private bool _isOpen; private WindowManager _manager; public WindowViewModel(WindowManager manager) { _manager = manager; } public bool IsOpen { get { return _isOpen; } set { if(_isOpen && !value) { _manager.VisibleWindows.Remove(this); } if(value && !_isOpen) { _manager.VisibleWindows.Add(this); } _isOpen = value; OnPropertyChanged("IsOpen"); } } public event PropertyChangedEventHandler PropertyChanged = delegate {}; private void OnPropertyChanged(string name) { PropertyChanged(this, new PropertyChangedEventArgs(name)); } } 

注意:我只是非常偶然地把它扔在一起; 你当然希望根据你的具体需求调整这个想法。

但是,任何基本前提是你的命令可以在WindowViewModel对象上工作,适当地切换IsOpen标志,并且管理器类处理打开/关闭任何新窗口。 有很多种可能的方法可以做到这一点,但过去它对我来说很有用(实际实施时并没有在我的手机上一起扔,也就是说)

纯粹主义者的合理方式是创建一个处理导航的服务。 简短摘要:创建NavigationService,在NavigationService注册您的视图,并使用视图模型中的NavigationService进行导航。

例:

 class NavigationService { private Window _a; public void RegisterViewA(Window a) { _a = a; } public void CloseWindowA() { a.Close(); } } 

要获得对NavigationService的引用,您可以在它上面进行抽象(即INavigationService)并通过IoC注册/获取它。 更准确地说,您甚至可以进行两个抽象,一个包含注册方法(由视图使用)和一个包含执行器(由视图模型使用)的抽象。

有关更详细的示例,您可以查看Gill Cleeren的实施情况,该实施在很大程度上取决于IoC:

http://www.silverlightshow.net/video/Applied-MVVM-in-Win8-Webinar.aspx从00:36:30开始

实现此目的的一种方法是视图模型请求关闭子窗口:

 public class ExampleUserControl_ViewModel { public Action ChildWindowsCloseRequested; ... } 

然后视图将订阅其视图模型的事件,并在触发时关闭窗口。

 public class ExampleUserControl : UserControl { public ExampleUserControl() { var viewModel = new ExampleUserControl_ViewModel(); viewModel.ChildWindowsCloseRequested += OnChildWindowsCloseRequested; DataContext = viewModel; } private void OnChildWindowsCloseRequested() { // ... close child windows } ... } 

因此,视图模型可以确保关闭子窗口而不了解视图。

此问题的大多数答案都涉及由ViewModel控制的状态变量,而View则对此变量的更改起作用。 这对于打开或关闭窗口等状态命令 ,或者仅显示或隐藏某些控件很有用。 但它对于无状态事件命令不起作用。 您可以在信号的上升沿触发某些操作,但需要再次将信号设置为低(假),否则它将不再触发。

我写了一篇关于ViewCommand模式的文章来解决这个问题。 它基本上是从View到当前ViewModel的常规命令的反方向。 它涉及一个接口,每个ViewModel都可以实现该接口,以便向所有当前连接的视图发送命令。 可以扩展View以在其DataContext属性更改时向每个已分配的ViewModel注册。 此注册将View添加到ViewModel中的Views列表。 每当ViewModel需要在View中运行命令时,它都会遍历所有已注册的视图,并在它们上面运行命令(如果存在)。 这使用reflection来查找View类中的ViewCommand方法,但是在相反方向上绑定也是如此。

View类中的ViewCommand方法:

 public partial class TextItemView : UserControl { [ViewCommand] public void FocusText() { MyTextBox.Focus(); } } 

这是从ViewModel调用的:

 private void OnAddText() { ViewCommandManager.Invoke("FocusText"); } 

该文章可在我的网站上以及CodeProject上的旧版本中找到 。

包含的代码(BSD许可证)提供了允许在代码混淆期间重命名方法的措施。