WPF MVVM:如何closures一个窗口

我有一个Button ,当它被点击时closures我的窗口:

 <Button x:Name="buttonOk" IsCancel="True">Ok</Button> 

这很好,直到我添加一个Command Button ie

 <Button x:Name="buttonOk" Command="{Binding SaveCommand}" IsCancel="True">Ok</Button> 

现在不是因为我正在处理Command而没有closures。 我可以通过把一个EventHandler和调用this.Close() ie来解决这个问题

 <Button x:Name="buttonOk" Click="closeWindow" Command="{Binding SaveCommand}" IsCancel="True">Ok</Button> 

但现在我有我的代码背后的代码,即方法SaveCommand 。 我正在使用MVVM模式, SaveCommand是我的代码中唯一的代码。

我怎样才能做到这一点,以便不使用代码?

我刚刚完成了关于这个话题的博客文章 。 简而言之,使用getset访问器将Action属性添加到ViewModel。 然后从View构造函数中定义Action 。 最后,在应该closures窗口的绑定命令中调用您的操作。

在ViewModel中:

 public Action CloseAction { get; set;} 

并在View构造函数中:

 private View() { InitializeComponent(); ViewModel vm = new ViewModel(); this.DataContext = vm; if ( vm.CloseAction == null ) vm.CloseAction = new Action(this.Close); } 

最后,无论应该closures窗口的任何绑定命令,我们都可以简单地调用

 CloseAction(); // Calls Close() method of the View 

这对我工作,似乎是一个相当优雅的解决scheme,并为我节省了一堆编码。

不幸的是,显示窗口是MVVM真正的痛苦,所以你需要做相当多的基础设施工作,或者使用像Cinch这样的MVVM框架。 如果你想投入时间自己做, 这里是一个链接如何Cinch做到这一点。

它很好,你试图保持任何逻辑的观点,但它真的不是世界的尽头,如果你这样做。 在这种情况下,听起来不会造成太多问题。

正如有人评论的,我发布的代码不是MVVM友好的,第二种解决scheme呢?

第一,不是MVVM解决scheme(我不会删除这个作为参考)

XAML:

 <Button Name="okButton" Command="{Binding OkCommand}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">OK</Button> 

视图模型:

 public ICommand OkCommand { get { if (_okCommand == null) { _okCommand = new ActionCommand<Window>(DoOk, CanDoOk); } return _okCommand ; } } void DoOk(Window win) { <!--Your Code--> win.DialogResult = true; win.Close(); } bool CanDoOk(Window win) { return true; } 

第二,可能更好的解决scheme:使用附加的行为

XAML

 <Button Content="Ok and Close" Command="{Binding OkCommand}" b:CloseOnClickBehaviour.IsEnabled="True" /> 

查看模型

 public ICommand OkCommand { get { return _okCommand; } } 

行为类与此类似的东西:

 public static class CloseOnClickBehaviour { public static readonly DependencyProperty IsEnabledProperty = DependencyProperty.RegisterAttached( "IsEnabled", typeof(bool), typeof(CloseOnClickBehaviour), new PropertyMetadata(false, OnIsEnabledPropertyChanged) ); public static bool GetIsEnabled(DependencyObject obj) { var val = obj.GetValue(IsEnabledProperty); return (bool)val; } public static void SetIsEnabled(DependencyObject obj, bool value) { obj.SetValue(IsEnabledProperty, value); } static void OnIsEnabledPropertyChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs args) { var button = dpo as Button; if (button == null) return; var oldValue = (bool)args.OldValue; var newValue = (bool)args.NewValue; if (!oldValue && newValue) { button.Click += OnClick; } else if (oldValue && !newValue) { button.PreviewMouseLeftButtonDown -= OnClick; } } static void OnClick(object sender, RoutedEventArgs e) { var button = sender as Button; if (button == null) return; var win = Window.GetWindow(button); if (win == null) return; win.Close(); } } 

我个人使用一种行为来做这样的事情:

 public class WindowCloseBehaviour : Behavior<Window> { public static readonly DependencyProperty CommandProperty = DependencyProperty.Register( "Command", typeof(ICommand), typeof(WindowCloseBehaviour)); public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register( "CommandParameter", typeof(object), typeof(WindowCloseBehaviour)); public static readonly DependencyProperty CloseButtonProperty = DependencyProperty.Register( "CloseButton", typeof(Button), typeof(WindowCloseBehaviour), new FrameworkPropertyMetadata(null, OnButtonChanged)); public ICommand Command { get { return (ICommand)GetValue(CommandProperty); } set { SetValue(CommandProperty, value); } } public object CommandParameter { get { return GetValue(CommandParameterProperty); } set { SetValue(CommandParameterProperty, value); } } public Button CloseButton { get { return (Button)GetValue(CloseButtonProperty); } set { SetValue(CloseButtonProperty, value); } } private static void OnButtonChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var window = (Window)((WindowCloseBehaviour)d).AssociatedObject; ((Button) e.NewValue).Click += (s, e1) => { var command = ((WindowCloseBehaviour)d).Command; var commandParameter = ((WindowCloseBehaviour)d).CommandParameter; if (command != null) { command.Execute(commandParameter); } window.Close(); }; } } 

然后,您可以将其附加到您的WindowButton来完成这项工作:

 <Window x:Class="WpfApplication6.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:local="clr-namespace:WpfApplication6" Title="Window1" Height="300" Width="300"> <i:Interaction.Behaviors> <local:WindowCloseBehaviour CloseButton="{Binding ElementName=closeButton}"/> </i:Interaction.Behaviors> <Grid> <Button Name="closeButton">Close</Button> </Grid> </Window> 

我在这里添加了CommandCommandParameter ,所以你可以在Windowclosures之前运行一个命令。

对于小应用程序,我使用自己的应用程序控制器来显示,closures和处理窗口和DataContext。 这是应用程序UI的中心点。

这是这样的:

 //It is singleton, I will just post 2 methods and their invocations public void ShowNewWindow(Window window, object dataContext = null, bool dialog = true) { window.DataContext = dataContext; addToWindowRegistry(dataContext, window); if (dialog) window.ShowDialog(); else window.Show(); } public void CloseWindow(object dataContextSender) { var correspondingWindows = windowRegistry.Where(c => c.DataContext.Equals(dataContextSender)).ToList(); foreach (var pair in correspondingWindows) { pair.Window.Close(); } } 

ViewModels的调用:

 // Show new Window with DataContext ApplicationController.Instance.ShowNewWindow( new ClientCardsWindow(), new ClientCardsVM(), false); // Close Current Window from viewModel ApplicationController.Instance.CloseWindow(this); 

当然你可以在我的解决scheme中find一些限制。 再次:我用它来做小项目,这就够了。 如果你有兴趣,我可以在这里或其他地方发布完整的代码/

我试图用一些通用的MVVM方式来解决这个问题,但是我总是发现我最终会产生不必要的复杂逻辑。 为了实现接近的行为,我从背后没有代码的规则中做出了一个例外,并在后面的代码中简单地使用了好的ol事件:

XAML:

 <Button Content="Close" Click="OnCloseClicked" /> 

代码后面:

 private void OnCloseClicked(object sender, EventArgs e) { Visibility = Visibility.Collapsed; } 

虽然我希望这会更好的支持使用命令/ MVVM,我只是认为没有比使用事件更简单和更清晰的解决scheme。

我使用Publish Subscribe模式来处理复杂的类依赖关系:

视图模型:

  public class ViewModel : ViewModelBase { public ViewModel() { CloseComand = new DelegateCommand((obj) => { MessageBus.Instance.Publish(Messages.REQUEST_DEPLOYMENT_SETTINGS_CLOSED, null); }); } } 

窗口:

 public partial class SomeWindow : Window { Subscription _subscription = new Subscription(); public SomeWindow() { InitializeComponent(); _subscription.Subscribe(Messages.REQUEST_DEPLOYMENT_SETTINGS_CLOSED, obj => { this.Close(); }); } } 

您可以利用Bizmonger.Patterns获取MessageBus。

MessageBus

 public class MessageBus { #region Singleton static MessageBus _messageBus = null; private MessageBus() { } public static MessageBus Instance { get { if (_messageBus == null) { _messageBus = new MessageBus(); } return _messageBus; } } #endregion #region Members List<Observer> _observers = new List<Observer>(); List<Observer> _oneTimeObservers = new List<Observer>(); List<Observer> _waitingSubscribers = new List<Observer>(); List<Observer> _waitingUnsubscribers = new List<Observer>(); int _publishingCount = 0; #endregion public void Subscribe(string message, Action<object> response) { Subscribe(message, response, _observers); } public void SubscribeFirstPublication(string message, Action<object> response) { Subscribe(message, response, _oneTimeObservers); } public int Unsubscribe(string message, Action<object> response) { var observers = new List<Observer>(_observers.Where(o => o.Respond == response).ToList()); observers.AddRange(_waitingSubscribers.Where(o => o.Respond == response)); observers.AddRange(_oneTimeObservers.Where(o => o.Respond == response)); if (_publishingCount == 0) { observers.ForEach(o => _observers.Remove(o)); } else { _waitingUnsubscribers.AddRange(observers); } return observers.Count; } public int Unsubscribe(string subscription) { var observers = new List<Observer>(_observers.Where(o => o.Subscription == subscription).ToList()); observers.AddRange(_waitingSubscribers.Where(o => o.Subscription == subscription)); observers.AddRange(_oneTimeObservers.Where(o => o.Subscription == subscription)); if (_publishingCount == 0) { observers.ForEach(o => _observers.Remove(o)); } else { _waitingUnsubscribers.AddRange(observers); } return observers.Count; } public void Publish(string message, object payload) { _publishingCount++; Publish(_observers, message, payload); Publish(_oneTimeObservers, message, payload); Publish(_waitingSubscribers, message, payload); _oneTimeObservers.RemoveAll(o => o.Subscription == message); _waitingUnsubscribers.Clear(); _publishingCount--; } private void Publish(List<Observer> observers, string message, object payload) { Debug.Assert(_publishingCount >= 0); var subscribers = observers.Where(o => o.Subscription.ToLower() == message.ToLower()); foreach (var subscriber in subscribers) { subscriber.Respond(payload); } } public IEnumerable<Observer> GetObservers(string subscription) { var observers = new List<Observer>(_observers.Where(o => o.Subscription == subscription)); return observers; } public void Clear() { _observers.Clear(); _oneTimeObservers.Clear(); } #region Helpers private void Subscribe(string message, Action<object> response, List<Observer> observers) { Debug.Assert(_publishingCount >= 0); var observer = new Observer() { Subscription = message, Respond = response }; if (_publishingCount == 0) { observers.Add(observer); } else { _waitingSubscribers.Add(observer); } } #endregion } 

}

订阅

 public class Subscription { #region Members List<Observer> _observerList = new List<Observer>(); #endregion public void Unsubscribe(string subscription) { var observers = _observerList.Where(o => o.Subscription == subscription); foreach (var observer in observers) { MessageBus.Instance.Unsubscribe(observer.Subscription, observer.Respond); } _observerList.Where(o => o.Subscription == subscription).ToList().ForEach(o => _observerList.Remove(o)); } public void Subscribe(string subscription, Action<object> response) { MessageBus.Instance.Subscribe(subscription, response); _observerList.Add(new Observer() { Subscription = subscription, Respond = response }); } public void SubscribeFirstPublication(string subscription, Action<object> response) { MessageBus.Instance.SubscribeFirstPublication(subscription, response); } } 

这个任务有一个有用的行为,它不会中断MVVM,这是一个Expression Blend 3引入的行为,允许View完全在ViewModel中定义的命令。

此行为演示了一种允许ViewModel在Model-View-ViewModel应用程序中pipe理视图的closures事件的简单技术。

这允许你在View(UserControl)中挂钩一个行为,它将提供对控件的Window的控制,允许ViewModel控制是否可以通过标准ICommandsclosures该窗口。

使用行为允许ViewModel在MV-VM中pipe理View Lifetime

http://gallery.expression.microsoft.com/WindowCloseBehavior/

上面的链接已经存档到http://code.msdn.microsoft.com/Window-Close-Attached-fef26a66#content

我在这个主题上奋斗了一段时间,并最终采用了与MVVM一致的最简单的方法:让button执行完成所有繁重工作的Command,并让button的Click处理程序closures窗口。

XAML

 <Button x:Name="buttonOk" Click="closeWindow" Command="{Binding SaveCommand}" /> 

XAML.cs

 public void closeWindow() { this.DialogResult = true; } 

SaveCommand.cs

  // I'm in my own file, not the code-behind! 

诚然,还有代码隐藏,但没有什么内在的坏东西。 从OO的angular度来看,对我来说,最有意义的是告诉窗户closures自己。

我们在.xaml定义中有name属性:

 x:Name="WindowsForm" 

然后我们有这个button:

 <Button Command="{Binding CloseCommand}" CommandParameter="{Binding ElementName=WindowsForm}" /> 

然后在ViewModel中:

 public DelegateCommand <Object> CloseCommand { get; private set; } Constructor for that view model: this.CloseCommand = new DelegateCommand<object>(this.CloseAction); 

最后,行动方法:

 private void CloseAction (object obj) { Window Win = obj as Window; Win.Close(); } 

我用这个代码closures了一个应用程序的popup窗口。

非常干净和MVVM的方式是使用Microsoft.Interactivity.Core定义的InteractionTriggerCallMethodAction

您将需要添加两个名称空间如下

 xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions" 

和Assemblies System.Windows.InteractivityMicrosoft.Expression.Interactions然后下面的xaml代码将工作。

 <Button Content="Save" Command="{Binding SaveCommand}"> <i:Interaction.Triggers> <i:EventTrigger EventName="Click"> <ei:CallMethodAction MethodName="Close" TargetObject="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" /> </i:EventTrigger> </i:Interaction.Triggers> </Button> 

你不需要任何代码或任何其他的东西,也可以调用任何其他方法的Window

我一直在寻找解决相同的问题,并发现下面的工作正常。 解决方法与OP在他的问题中提到的有些不同:

  1. 不需要IsCancel属性。

  2. 后面的代码不应该closures窗口。 只需设置DialogResult

在我的情况下,它首先执行后面的代码,然后查看绑定到button的模型命令。

XAML

 <Button x:Name="buttonOk" Click="Save_Click" Command="{Binding SaveCommand}">OK</Button> 

代码在后面

 private void Apply_OnClick(object sender, RoutedEventArgs e) { this.DialogResult = true; } 

查看模型

 private void Save() { // Save data. } 

希望这可以帮助。

你可以改变这个问题,这样做 – 提出另一个解决scheme。 如何在MVVM环境中启用视图,视图模型和其他内容之间的通信? 你可以使用中介模式。 这基本上是一个通知系统。 对于实际的调解员实施,谷歌为它或问我,我可以给它发电子邮件。

制作一个命令,其目的是closures视图。

 public void Execute( object parameter ) { this.viewModel.DisposeMyStuff(); Mediator.NotifyColleagues(Mediator.Token.ConfigWindowShouldClose); } 

调解员将提出通知(令牌)

在View代码隐藏构造函数中听到这样的通知(令牌):

 public ClientConfigView() { InitializeComponent(); Mediator.ListenOn(Mediator.Token.ConfigWindowShouldClose, callback => this.Close() ); } 

我在Silverlight中有以下解决scheme。 也将在WPF中。

ChildWindowExt.cs:

 namespace System.Windows.Controls { public class ChildWindowExt : ChildWindow { public static readonly DependencyProperty IsOpenedProperty = DependencyProperty.Register( "IsOpened", typeof(bool), typeof(ChildWindowExt), new PropertyMetadata(false, IsOpenedChanged)); private static void IsOpenedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if ((bool)e.NewValue == false) { ChildWindowExt window = d as ChildWindowExt; window.Close(); } else if ((bool)e.NewValue == true) { ChildWindowExt window = d as ChildWindowExt; window.Show(); } } public bool IsOpened { get { return (bool)GetValue(IsOpenedProperty); } set { SetValue(IsOpenedProperty, value); } } protected override void OnClosing(ComponentModel.CancelEventArgs e) { this.IsOpened = false; base.OnClosing(e); } protected override void OnOpened() { this.IsOpened = true; base.OnOpened(); } } } 

ItemWindow.xaml:

 <extControls:ChildWindowExt x:Class="MyProject.ItemWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:extControls="clr-namespace:System.Windows.Controls" Title="{Binding Title}" IsOpened="{Binding IsOpened, Mode=TwoWay}" Width="640" Height="480"> <Grid x:Name="LayoutRoot"> <Button Command="{Binding UpdateCommand}" Content="OK" Width="70" HorizontalAlignment="Center" VerticalAlignment="Center"/> </Grid> </extControls:ChildWindowExt> 

ItemViewModel.cs:

 private bool _IsOpened; public bool IsOpened { get { return _IsOpened; } set { if (!Equals(_IsOpened, value)) { _IsOpened = value; RaisePropertyChanged("IsOpened"); } } } private RelayCommand _UpdateCommand; /// <summary> /// Insert / Update data entity /// </summary> public RelayCommand UpdateCommand { get { if (_UpdateCommand == null) { _UpdateCommand = new RelayCommand( () => { // Insert / Update data entity ... IsOpened = false; }, () => { return true; }); } return _UpdateCommand; } } 

ItemsViewModel.cs:

  private RelayCommand _InsertItemCommand; /// <summary> /// /// </summary> public RelayCommand InsertItemCommand { get { if (_InsertItemCommand == null) { _InsertItemCommand = new RelayCommand( () => { ItemWindow itemWin = new ItemWindow(); itemWin.DataContext = new ItemViewModel(); itemWin.Show(); // OR // ItemWindow itemWin = new ItemWindow(); // ItemViewModel newItem = new ItemViewModel(); // itemWin.DataContext = newItem; // newItem.IsOpened = true; }, () => { return true; }); } return _InsertItemCommand; } } 

MainPage.xaml中:

 <Grid x:Name="LayoutRoot"> <Button Command="{Binding InsertItemCommand}" Content="Add New" Width="70" HorizontalAlignment="Left" VerticalAlignment="Center" /> </Grid> 

祝你们好想法和项目;-)

这可能会帮助你,closures一个wpf窗口使用mvvm与最小代码背后: http ://jkshay.com/closing-a-wpf-window-using-mvvm-and-minimal-code-behind/

我认为最简单的方式还没有包括(几乎)。 而不是使用附加的属性添加新的依赖关系的行为:

  using System; using System.Windows; using System.Windows.Controls; public class DialogButtonManager { public static readonly DependencyProperty IsAcceptButtonProperty = DependencyProperty.RegisterAttached("IsAcceptButton", typeof(bool), typeof(DialogButtonManager), new FrameworkPropertyMetadata(OnIsAcceptButtonPropertyChanged)); public static readonly DependencyProperty IsCancelButtonProperty = DependencyProperty.RegisterAttached("IsCancelButton", typeof(bool), typeof(DialogButtonManager), new FrameworkPropertyMetadata(OnIsCancelButtonPropertyChanged)); public static void SetIsAcceptButton(UIElement element, bool value) { element.SetValue(IsAcceptButtonProperty, value); } public static bool GetIsAcceptButton(UIElement element) { return (bool)element.GetValue(IsAcceptButtonProperty); } public static void SetIsCancelButton(UIElement element, bool value) { element.SetValue(IsCancelButtonProperty, value); } public static bool GetIsCancelButton(UIElement element) { return (bool)element.GetValue(IsCancelButtonProperty); } private static void OnIsAcceptButtonPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { Button button = sender as Button; if (button != null) { if ((bool)e.NewValue) { SetAcceptButton(button); } else { ResetAcceptButton(button); } } } private static void OnIsCancelButtonPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { Button button = sender as Button; if (button != null) { if ((bool)e.NewValue) { SetCancelButton(button); } else { ResetCancelButton(button); } } } private static void SetAcceptButton(Button button) { Window window = Window.GetWindow(button); button.Command = new RelayCommand(new Action<object>(ExecuteAccept)); button.CommandParameter = window; } private static void ResetAcceptButton(Button button) { button.Command = null; button.CommandParameter = null; } private static void ExecuteAccept(object buttonWindow) { Window window = (Window)buttonWindow; window.DialogResult = true; } private static void SetCancelButton(Button button) { Window window = Window.GetWindow(button); button.Command = new RelayCommand(new Action<object>(ExecuteCancel)); button.CommandParameter = window; } private static void ResetCancelButton(Button button) { button.Command = null; button.CommandParameter = null; } private static void ExecuteCancel(object buttonWindow) { Window window = (Window)buttonWindow; window.DialogResult = false; } } 

然后将其设置在对话框button上:

 <UniformGrid Grid.Row="2" Grid.Column="1" Rows="1" Columns="2" Margin="3" > <Button Content="Accept" IsDefault="True" Padding="3" Margin="3,0,3,0" DialogButtonManager.IsAcceptButton="True" /> <Button Content="Cancel" IsCancel="True" Padding="3" Margin="3,0,3,0" DialogButtonManager.IsCancelButton="True" /> </UniformGrid> 

你可以做到这一点,没有代码。 创build命令,在视图模式的Execute方法中调用“Save”方法,然后在编辑窗口调用close方法,你可以通过parameter passing给命令:

 public void Execute(object parameter) { _mainViewModel.SaveSomething(); var editWindow = parameter as MyEditWindow; editWindow?.Close(); } 

保存并closuresbuttonXAML:

 <Button Content"Save&Close" Command="{Binding SaveCmd}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}" IsDefault="True" /> 

我也必须处理这个问题,所以在这里我的解决scheme。 这对我很有效。

1.创build类DelegateCommand

  public class DelegateCommand<T> : ICommand { private Predicate<T> _canExecuteMethod; private readonly Action<T> _executeMethod; public event EventHandler CanExecuteChanged; public DelegateCommand(Action<T> executeMethod) : this(executeMethod, null) { } public DelegateCommand(Action<T> executeMethod, Predicate<T> canExecuteMethod) { this._canExecuteMethod = canExecuteMethod; this._executeMethod = executeMethod ?? throw new ArgumentNullException(nameof(executeMethod), "Command is not specified."); } public void RaiseCanExecuteChanged() { if (this.CanExecuteChanged != null) CanExecuteChanged(this, null); } public bool CanExecute(object parameter) { return _canExecuteMethod == null || _canExecuteMethod((T)parameter) == true; } public void Execute(object parameter) { _executeMethod((T)parameter); } } 

2.定义你的命令

  public DelegateCommand<Window> CloseWindowCommand { get; private set; } public MyViewModel()//ctor of your viewmodel { //do something CloseWindowCommand = new DelegateCommand<Window>(CloseWindow); } public void CloseWindow(Window win) // this method is also in your viewmodel { //do something win?.Close(); } 

3.在视图中绑定你的命令

 public MyView(Window win) //ctor of your view, window as parameter { InitializeComponent(); MyButton.CommandParameter = win; MyButton.Command = ((MyViewModel)this.DataContext).CloseWindowCommand; } 

4.现在窗户

  Window win = new Window() { Title = "My Window", Height = 800, Width = 800, WindowStartupLocation = WindowStartupLocation.CenterScreen, }; win.Content = new MyView(win); win.ShowDialog(); 

因此,您也可以绑定xaml文件中的命令,并使用FindAncestor查找窗口并将其绑定到命令参数。