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
是我的代码中唯一的代码。
我怎样才能做到这一点,以便不使用代码?
我刚刚完成了关于这个话题的博客文章 。 简而言之,使用get
和set
访问器将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(); }; } }
然后,您可以将其附加到您的Window
和Button
来完成这项工作:
<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>
我在这里添加了Command
和CommandParameter
,所以你可以在Window
closures之前运行一个命令。
对于小应用程序,我使用自己的应用程序控制器来显示,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
定义的InteractionTrigger
和CallMethodAction
您将需要添加两个名称空间如下
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
和Assemblies System.Windows.Interactivity和Microsoft.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在他的问题中提到的有些不同:
-
不需要
IsCancel
属性。 -
后面的代码不应该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查找窗口并将其绑定到命令参数。
- 无法加载文件或程序集“Microsoft.ReportViewer.Common,版本= 11.0.0.0
- 如何修复.NET的Windows应用程序在启动时崩溃exception代码:0xE0434352?
- System.Speech.Recognition和Microsoft.Speech.Recognition有什么区别?
- .NETreflection代价如何?
- MarshalByRefObject的主要用途是什么?
- 该组件没有uri标识的资源
- 有什么情况下最好使用一个普通的旧的Thread对象而不是一个新的构造?
- .NET框架如何为OutOfMemoryException分配内存?
- 在.NET中按创builddate获取文件