wpf 给一些命令在 MVVM 中查看
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/15465161/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Give some command to View in MVVM
提问by SiberianGuy
Let's imagine I have some user control. The user control has some child windows. And user control user wants to close child windows of some type. There is a method in user control code behind:
让我们假设我有一些用户控制。用户控件有一些子窗口。并且用户控制用户想要关闭某种类型的子窗口。在用户控制代码后面有一个方法:
public void CloseChildWindows(ChildWindowType type)
{
...
}
But I can't call this method as I don't have direct access to the view.
但我无法调用此方法,因为我无法直接访问视图。
Another solution I think about is to somehow expose user control ViewModel as one of its properties (so I can bind it and give command directly to ViewModel). But I don't want user control users to know anything about user control ViewModel.
我想到的另一个解决方案是以某种方式将用户控件 ViewModel 作为其属性之一公开(这样我就可以绑定它并直接向 ViewModel 发出命令)。但我不希望用户控件用户了解有关用户控件 ViewModel 的任何信息。
So what is the right way to solve this problem?
那么解决这个问题的正确方法是什么?
回答by Mike Fuchs
I feel I just found a rather nice MVVM solution to this problem. I wrote a behavior that is exposing a type property WindowTypeand a boolean property Open. DataBinding the latter allows the ViewModel to open and close the windows easily, without knowing anything about the View.
我觉得我刚刚找到了一个相当不错的 MVVM 解决方案来解决这个问题。我写了一个暴露类型属性WindowType和布尔属性的行为Open。DataBinding 允许 ViewModel 轻松打开和关闭窗口,而无需了解有关 View 的任何信息。
Gotta love behaviors... :)
必须爱行为... :)


Xaml:
Xml:
<UserControl x:Class="WpfApplication1.OpenCloseWindowDemo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApplication1"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<local:ViewModel />
</UserControl.DataContext>
<i:Interaction.Behaviors>
<!-- TwoWay binding is necessary, otherwise after user closed a window directly, it cannot be opened again -->
<local:OpenCloseWindowBehavior WindowType="local:BlackWindow" Open="{Binding BlackOpen, Mode=TwoWay}" />
<local:OpenCloseWindowBehavior WindowType="local:YellowWindow" Open="{Binding YellowOpen, Mode=TwoWay}" />
<local:OpenCloseWindowBehavior WindowType="local:PurpleWindow" Open="{Binding PurpleOpen, Mode=TwoWay}" />
</i:Interaction.Behaviors>
<UserControl.Resources>
<Thickness x:Key="StdMargin">5</Thickness>
<Style TargetType="Button" >
<Setter Property="MinWidth" Value="60" />
<Setter Property="Margin" Value="{StaticResource StdMargin}" />
</Style>
<Style TargetType="Border" >
<Setter Property="Margin" Value="{StaticResource StdMargin}" />
</Style>
</UserControl.Resources>
<Grid>
<StackPanel>
<StackPanel Orientation="Horizontal">
<Border Background="Black" Width="30" />
<Button Content="Open" Command="{Binding OpenBlackCommand}" CommandParameter="True" />
<Button Content="Close" Command="{Binding OpenBlackCommand}" CommandParameter="False" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<Border Background="Yellow" Width="30" />
<Button Content="Open" Command="{Binding OpenYellowCommand}" CommandParameter="True" />
<Button Content="Close" Command="{Binding OpenYellowCommand}" CommandParameter="False" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<Border Background="Purple" Width="30" />
<Button Content="Open" Command="{Binding OpenPurpleCommand}" CommandParameter="True" />
<Button Content="Close" Command="{Binding OpenPurpleCommand}" CommandParameter="False" />
</StackPanel>
</StackPanel>
</Grid>
</UserControl>
YellowWindow (Black/Purple alike):
YellowWindow(黑色/紫色相似):
<Window x:Class="WpfApplication1.YellowWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="YellowWindow" Height="300" Width="300">
<Grid Background="Yellow" />
</Window>
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<bool>(OpenBlack);
this.OpenYellowCommand = new ActionCommand<bool>(OpenYellow);
this.OpenPurpleCommand = new ActionCommand<bool>(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<T> : ICommand
{
public event EventHandler CanExecuteChanged;
private Action<T> _action;
public ActionCommand(Action<T> 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<UserControl>
{
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));
/// <summary>
/// Opens or closes a window of type 'WindowType'.
/// </summary>
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
}
}
}
}
回答by JerKimball
I have handled this sort of situation in the past by bringing in the concept of a WindowManager, which is a horriblename for it, so let's pair it with a WindowViewModel, which is only slightly less horrible - but the basic idea is:
过去我通过引入 a 的概念来处理这种情况WindowManager,这是一个可怕的名字,所以让我们将它与 a 配对WindowViewModel,它只是稍微不那么可怕 - 但基本思想是:
public class WindowManager
{
public WindowManager()
{
VisibleWindows = new ObservableCollection<WindowViewModel>();
VisibleWindows.CollectionChanged += OnVisibleWindowsChanged;
}
public ObservableCollection<WindowViewModel> 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));
}
}
note: I'm just throwing this together very haphazardly; you'd of course want to tune this idea to your specific needs.
注意:我只是很随意地把它放在一起;您当然希望根据您的特定需求调整这个想法。
But anywho, the basic premise is your commands can work on the WindowViewModelobjects, toggle the IsOpenflag appropriately, and the manager class handles opening/closing any new windows. There are dozens of possibleways to do this, but it's worked in a pinch for me in the past (when actually implemented and not tossed together on my phone, that is)
但是任何人,基本前提是您的命令可以对WindowViewModel对象起作用,IsOpen适当地切换标志,并且管理器类处理打开/关闭任何新窗口。有几十种可能的方法可以做到这一点,但它在过去对我来说非常有用(实际上是在我的手机上实施而不是放在一起时)
回答by Sander
A reasonable way for purists is creating a service that handles your navigation. Short summary: create a NavigationService, register your view at the NavigationService and use the NavigationService from within the view model to navigate.
对于纯粹主义者来说,一个合理的方法是创建一个服务来处理您的导航。简短摘要:创建一个 NavigationService,在 NavigationService 上注册您的视图并使用视图模型中的 NavigationService 进行导航。
Example:
例子:
class NavigationService
{
private Window _a;
public void RegisterViewA(Window a) { _a = a; }
public void CloseWindowA() { a.Close(); }
}
To get a reference to NavigationService you could make an abstraction on top of it (i.e. INavigationService) and register/get it via a IoC. More properly you could even make two abstractions, one that contains the methods for registration (used by the view) and one that contains the actuators (used by the view model).
要获得对 NavigationService 的引用,您可以对其进行抽象(即 INavigationService)并通过 IoC 注册/获取它。更恰当地说,您甚至可以创建两个抽象,一个包含注册方法(由视图使用),另一个包含执行器(由视图模型使用)。
For a more detailed example you could check out the implementation of Gill Cleeren which heavily depends on IoC:
有关更详细的示例,您可以查看严重依赖 IoC 的 Gill Cleeren 的实现:
http://www.silverlightshow.net/video/Applied-MVVM-in-Win8-Webinar.aspxstarting at 00:36:30
http://www.silverlightshow.net/video/Applied-MVVM-in-Win8-Webinar.aspx00:36:30 开始
回答by Oli Wennell
One way to achieve this would be for the view-model to request that the child windows should be closed:
实现此目的的一种方法是让视图模型请求应关闭子窗口:
public class ExampleUserControl_ViewModel
{
public Action ChildWindowsCloseRequested;
...
}
The view would then subscribe to its view-model's event, and take care of closing the windows when it's fired.
然后视图将订阅其视图模型的事件,并在它被触发时关闭窗口。
public class ExampleUserControl : UserControl
{
public ExampleUserControl()
{
var viewModel = new ExampleUserControl_ViewModel();
viewModel.ChildWindowsCloseRequested += OnChildWindowsCloseRequested;
DataContext = viewModel;
}
private void OnChildWindowsCloseRequested()
{
// ... close child windows
}
...
}
So here the view-model can ensure the child windows are closed without having any knowledge of the view.
所以这里的视图模型可以确保子窗口在不了解视图的情况下关闭。
回答by ygoe
Most answers to this question involve a state variable that is controlled by the ViewModel and the View acts on changes to this variable. This is good for stateful commandslike opening or closing a window, or simply showing or hiding some controls. It doesn't work well for stateless event commandsthough. You could trigger some action on the rising edge of the signal but need to set the signal to low (false) again or it won't ever trigger again.
这个问题的大多数答案都涉及一个由 ViewModel 控制的状态变量,而 View 会对该变量的更改进行操作。这适用于有状态的命令,例如打开或关闭窗口,或者只是显示或隐藏某些控件。但是对于无状态事件命令,它不能很好地工作。您可以在信号的上升沿触发某些操作,但需要再次将信号设置为低(假),否则它将永远不会再次触发。
I have written an article about the ViewCommand patternwhich solves this problem. It is basically the reverse direction of regular Commands that go from the View to the current ViewModel. It involves an interface that each ViewModel can implement to send commands to all currently connected Views. A View can be extended to register with each assigned ViewModel when its DataContext property changes. This registration adds the View to the list of Views in the ViewModel. Whenever the ViewModel needs to run a command in a View, it goes through all registered Views and runs the command on them if it exists. This makes use of reflection to find the ViewCommand methods in the View class, but so does Binding in the opposite direction.
我写了一篇关于解决这个问题的ViewCommand 模式的文章。它基本上是从视图到当前视图模型的常规命令的相反方向。它涉及一个接口,每个 ViewModel 都可以实现该接口向所有当前连接的 Views 发送命令。当 View 的 DataContext 属性更改时,可以扩展 View 以向每个分配的 ViewModel 注册。此注册将视图添加到 ViewModel 中的视图列表中。每当 ViewModel 需要在视图中运行命令时,它都会遍历所有已注册的视图并在它们上运行命令(如果存在)。这利用反射来查找 View 类中的 ViewCommand 方法,但相反方向的 Binding 也是如此。
The ViewCommand method in the View class:
View类中的ViewCommand方法:
public partial class TextItemView : UserControl
{
[ViewCommand]
public void FocusText()
{
MyTextBox.Focus();
}
}
This is called from a ViewModel:
这是从 ViewModel 调用的:
private void OnAddText()
{
ViewCommandManager.Invoke("FocusText");
}
The article is available on my websiteand in an older version on CodeProject.
该文章可在我的网站和CodeProject 上的旧版本中找到。
The included code (BSD licence) provides measures to allow renaming methods during code obfuscation.
包含的代码(BSD 许可证)提供了允许在代码混淆期间重命名方法的措施。

