从ViewModelclosures窗口
我使用window control
创build一个login,以允许用户login到我创build的WPF
应用程序。
到目前为止,我已经创build了一个方法来检查用户是否在login屏幕上的textbox
input了username
和password
的正确凭据,并binding
两个properties
。
我通过创build一个bool
方法来实现这一点,就像这样;
public bool CheckLogin() { var user = context.Users.Where(i => i.Username == this.Username).SingleOrDefault(); if (user == null) { MessageBox.Show("Unable to Login, incorrect credentials."); return false; } else if (this.Username == user.Username || this.Password.ToString() == user.Password) { MessageBox.Show("Welcome " + user.Username + ", you have successfully logged in."); return true; } else { MessageBox.Show("Unable to Login, incorrect credentials."); return false; } } public ICommand ShowLoginCommand { get { if (this.showLoginCommand == null) { this.showLoginCommand = new RelayCommand(this.LoginExecute, null); } return this.showLoginCommand; } } private void LoginExecute() { this.CheckLogin(); }
我也有一个command
,我bind
到我的button在xaml
像这样;
<Button Name="btnLogin" IsDefault="True" Content="Login" Command="{Binding ShowLoginCommand}" />
当我input用户名和密码时,它会执行相应的代码,无论是正确还是错误。 但是,如果用户名和密码都正确,我怎么能从ViewModelclosures这个窗口?
我以前曾尝试使用dialog modal
但它并没有完全解决。 此外,在我的app.xaml中,我做了如下的事情,首先加载login页面,然后加载实际的应用程序。
private void ApplicationStart(object sender, StartupEventArgs e) { Current.ShutdownMode = ShutdownMode.OnExplicitShutdown; var dialog = new UserView(); if (dialog.ShowDialog() == true) { var mainWindow = new MainWindow(); Current.ShutdownMode = ShutdownMode.OnMainWindowClose; Current.MainWindow = mainWindow; mainWindow.Show(); } else { MessageBox.Show("Unable to load application.", "Error", MessageBoxButton.OK); Current.Shutdown(-1); } }
问题:如何从ViewModelclosuresloginWindow control
?
提前致谢。
您可以简单地使用CommandParameter
将窗口传递给您的ViewModel。 看到我的例子如下:
我已经实现了一个CloseWindow
方法,它将Windows作为参数并closures它。 该窗口通过CommandParameter
传递给ViewModel。 请注意,您需要为应该closures的窗口定义一个x:Name
。 在我的XAML窗口中,我通过Command
调用这个方法,并使用CommandParameter
将窗口本身作为parameter passing给ViewModel。
Command="{Binding CloseWindowCommand, Mode=OneWay}" CommandParameter="{Binding ElementName=TestWindow}"
这是一个符合MVVM编程范例的干净而简单的解决scheme。
视图模型
public RelayCommand<Window> CloseWindowCommand { get; private set; } public MainViewModel() { this.CloseWindowCommand = new RelayCommand<Window>(this.CloseWindow); } private void CloseWindow(Window window) { if (window != null) { window.Close(); } }
XAML
<Window x:Class="ClientLibTestTool.ErrorView" x:Name="TestWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:localization="clr-namespace:ClientLibTestTool.ViewLanguages" DataContext="{Binding Main, Source={StaticResource Locator}}" Title="{x:Static localization:localization.HeaderErrorView}" Height="600" Width="800" ResizeMode="NoResize" WindowStartupLocation="CenterScreen"> <Grid> <Button Content="{x:Static localization:localization.ButtonClose}" Height="30" Width="100" Margin="0,0,10,10" IsCancel="True" VerticalAlignment="Bottom" HorizontalAlignment="Right" Command="{Binding CloseWindowCommand, Mode=OneWay}" CommandParameter="{Binding ElementName=TestWindow}"/> </Grid> </Window>
请注意,我使用MVVM轻型框架,但主体是相同的每个WPF应用程序。
编辑:
您的loginbutton(添加CommandParameter):
<Button Name="btnLogin" IsDefault="True" Content="Login" Command="{Binding ShowLoginCommand}" CommandParameter="{Binding ElementName=LoginWindow}"/>
你的代码:
public RelayCommand<Window> CloseWindowCommand { get; private set; } // the <Window> is important for your solution! public MainViewModel() { //initialize the CloseWindowCommand. Again, mind the <Window> //you don't have to do this in your constructor but it is good practice, thought this.CloseWindowCommand = new RelayCommand<Window>(this.CloseWindow); } public bool CheckLogin(Window loginWindow) //Added loginWindow Parameter { var user = context.Users.Where(i => i.Username == this.Username).SingleOrDefault(); if (user == null) { MessageBox.Show("Unable to Login, incorrect credentials."); return false; } else if (this.Username == user.Username || this.Password.ToString() == user.Password) { MessageBox.Show("Welcome "+ user.Username + ", you have successfully logged in."); this.CloseWindow(loginWindow); //Added call to CloseWindow Method return true; } else { MessageBox.Show("Unable to Login, incorrect credentials."); return false; } } //Added CloseWindow Method private void CloseWindow(Window window) { if (window != null) { window.Close(); } }
EDIT2:用户Crono在评论部分提到了一个有效的点:
将Window对象传递给视图模型会打破MVVM模式恕我直言,因为它迫使你的虚拟机知道它正在查看什么。
你可以通过引入一个包含close方法的接口来解决这个问题。
接口:
public interface IClosable { void Close(); }
您重构的ViewModel将如下所示:
视图模型
public RelayCommand<IClosable> CloseWindowCommand { get; private set; } public MainViewModel() { this.CloseWindowCommand = new RelayCommand<IClosable>(this.CloseWindow); } private void CloseWindow(IClosable window) { if (window != null) { window.Close(); } }
你需要参考/实现IClosable
接口你的看法以及查看(代码后面)
public partial class MainWindow : Window, IClosable { public MainWindow() { InitializeComponent(); } }
当我需要这样做时,我通常会在视图模型上放置一个事件,然后在将视图模型绑定到窗口时将其绑定到Window.Close()
public class LoginViewModel { public event EventHandler OnRequestClose; private void Login() { // Login logic here OnRequestClose(this, new EventArgs()); } }
而当创buildlogin窗口
var vm = new LoginViewModel(); var loginWindow = new LoginWindow { DataContext = vm }; vm.OnRequestClose += (s, e) => loginWindow.Close(); loginWindow.ShowDialog();
保持MVVM,我认为使用Blend SDK中的行为(System.Windows.Interactivity)或Prism的自定义交互请求可以很好地处理这种情况。
如果去行为路线,这是一般的想法:
public class CloseWindowBehavior : Behavior<Window> { public bool CloseTrigger { get { return (bool)GetValue(CloseTriggerProperty); } set { SetValue(CloseTriggerProperty, value); } } public static readonly DependencyProperty CloseTriggerProperty = DependencyProperty.Register("CloseTrigger", typeof(bool), typeof(CloseWindowBehavior), new PropertyMetadata(false, OnCloseTriggerChanged)); private static void OnCloseTriggerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var behavior = d as CloseWindowBehavior; if (behavior != null) { behavior.OnCloseTriggerChanged(); } } private void OnCloseTriggerChanged() { // when closetrigger is true, close the window if (this.CloseTrigger) { this.AssociatedObject.Close(); } } }
然后在你的窗口中,你只需要将CloseTrigger绑定到一个布尔值,当你想要closures窗口的时候就可以设置它。
<Window x:Class="TestApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" xmlns:local="clr-namespace:TestApp" Title="MainWindow" Height="350" Width="525"> <i:Interaction.Behaviors> <local:CloseWindowBehavior CloseTrigger="{Binding CloseTrigger}" /> </i:Interaction.Behaviors> <Grid> </Grid> </Window>
最后,你的DataContext / ViewModel将有一个属性,当你想要这个窗口closures时,你会设置:
public class MainWindowViewModel : INotifyPropertyChanged { private bool closeTrigger; /// <summary> /// Gets or Sets if the main window should be closed /// </summary> public bool CloseTrigger { get { return this.closeTrigger; } set { this.closeTrigger = value; RaisePropertyChanged("CloseTrigger"); } } public MainWindowViewModel() { // just setting for example, close the window CloseTrigger = true; } protected void RaisePropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } public event PropertyChangedEventHandler PropertyChanged; }
(设置你的Window.DataContext =新的MainWindowViewModel())
那么这里是我在几个项目中使用的东西。 它可能看起来像一个黑客,但它工作正常。
public class AttachedProperties : DependencyObject //adds a bindable DialogResult to window { public static readonly DependencyProperty DialogResultProperty = DependencyProperty.RegisterAttached("DialogResult", typeof(bool?), typeof(AttachedProperties), new PropertyMetaData(default(bool?), OnDialogResultChanged)); public bool? DialogResult { get { return (bool?)GetValue(DialogResultProperty); } set { SetValue(DialogResultProperty, value); } } private static void OnDialogResultChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var window = d as Window; if (window == null) return; window.DialogResult = (bool?)e.NewValue; } }
现在,您可以将DialogResult
绑定到VM并设置其属性的值。 当设置值时, Window
将closures。
<!-- Assuming that the VM is bound to the DataContext and the bound VM has a property DialogResult --> <Window someNs:AttachedProperties.DialogResult={Binding DialogResult} />
这是我们的生产环境中运行的一个摘要
<Window x:Class="AC.Frontend.Controls.DialogControl.Dialog" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:DialogControl="clr-namespace:AC.Frontend.Controls.DialogControl" xmlns:hlp="clr-namespace:AC.Frontend.Helper" MinHeight="150" MinWidth="300" ResizeMode="NoResize" SizeToContent="WidthAndHeight" WindowStartupLocation="CenterScreen" Title="{Binding Title}" hlp:AttachedProperties.DialogResult="{Binding DialogResult}" WindowStyle="ToolWindow" ShowInTaskbar="True" Language="{Binding UiCulture, Source={StaticResource Strings}}"> <!-- A lot more stuff here --> </Window>
正如你所看到的,我首先声明命名空间xmlns:hlp="clr-namespace:AC.Frontend.Helper"
,然后绑定hlp:AttachedProperties.DialogResult="{Binding DialogResult}"
。
AttachedProperty
看起来像这样。 这不是我昨天发布的,但恕我直言,它不应该有任何影响。
public class AttachedProperties { #region DialogResult public static readonly DependencyProperty DialogResultProperty = DependencyProperty.RegisterAttached("DialogResult", typeof (bool?), typeof (AttachedProperties), new PropertyMetadata(default(bool?), OnDialogResultChanged)); private static void OnDialogResultChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var wnd = d as Window; if (wnd == null) return; wnd.DialogResult = (bool?) e.NewValue; } public static bool? GetDialogResult(DependencyObject dp) { if (dp == null) throw new ArgumentNullException("dp"); return (bool?)dp.GetValue(DialogResultProperty); } public static void SetDialogResult(DependencyObject dp, object value) { if (dp == null) throw new ArgumentNullException("dp"); dp.SetValue(DialogResultProperty, value); } #endregion }
这是一个很简单的方法:
YourWindow.xaml.cs
//In your constructor public YourWindow() { InitializeComponent(); DataContext = new YourWindowViewModel(this); }
YourWindowViewModel.cs
private YourWindow window;//so we can kill the window //In your constructor public YourWindowViewModel(YourWindow window) { this.window = window; } //to close the window public void CloseWindow() { window.Close(); }
我没有看到你select的答案有什么问题,我只是认为这可能是一个更简单的方法来做到这一点!
简单的方法
public interface IRequireViewIdentification { Guid ViewID { get; } }
实现到ViewModel
public class MyViewVM : IRequireViewIdentification { private Guid _viewId; public Guid ViewID { get { return _viewId; } } public MyViewVM() { _viewId = Guid.NewGuid(); } }
添加一般窗口pipe理器助手
public static class WindowManager { public static void CloseWindow(Guid id) { foreach (Window window in Application.Current.Windows) { var w_id = window.DataContext as IRequireViewIdentification; if (w_id != null && w_id.ViewID.Equals(id)) { window.Close(); } } } }
并在viewmodel中像这样closures它
WindowManager.CloseWindow(ViewID);
您可以像这样在ViewModel中创build新的事件处理程序。
public event EventHandler RequestClose; protected void OnRequestClose() { if (RequestClose != null) RequestClose(this, EventArgs.Empty); }
然后为ExitCommand定义RelayCommand。
private RelayCommand _CloseCommand; public ICommand CloseCommand { get { if(this._CloseCommand==null) this._CloseCommand=new RelayCommand(CloseClick); return this._CloseCommand; } } private void CloseClick(object obj) { OnRequestClose(); }
然后在XAML文件集中
<Button Command="{Binding CloseCommand}" />
在xaml.cs文件中设置DataContext并订阅我们创build的事件。
public partial class MainWindow : Window { private ViewModel mainViewModel = null; public MainWindow() { InitializeComponent(); mainViewModel = new ViewModel(); this.DataContext = mainViewModel; mainViewModel.RequestClose += delegate(object sender, EventArgs args) { this.Close(); }; } }
我提供的方法是在ViewModel中声明事件,并使用混合InvokeMethodAction如下。
示例ViewModel
public class MainWindowViewModel : BindableBase, ICloseable { public DelegateCommand SomeCommand { get; private set; } #region ICloseable Implementation public event EventHandler CloseRequested; public void RaiseCloseNotification() { var handler = CloseRequested; if (handler != null) { handler.Invoke(this, EventArgs.Empty); } } #endregion public MainWindowViewModel() { SomeCommand = new DelegateCommand(() => { //when you decide to close window RaiseCloseNotification(); }); } }
我可以closures的界面如下,但不需要执行此操作。 ICloseable将帮助创build通用视图服务,所以如果你通过dependency injection来构造view和ViewModel,那么你可以做的是
internal interface ICloseable { event EventHandler CloseRequested; }
使用ICloseable
var viewModel = new MainWindowViewModel(); // As service is generic and don't know whether it can request close event var window = new Window() { Content = new MainView() }; var closeable = viewModel as ICloseable; if (closeable != null) { closeable.CloseRequested += (s, e) => window.Close(); }
而下面是Xaml,即使你没有实现接口,你也可以使用这个xaml,它只需要你的视图模型来提高CloseRquested。
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WPFRx" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions" xmlns:ViewModels="clr-namespace:WPFRx.ViewModels" x:Name="window" x:Class="WPFRx.MainWindow" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525" d:DataContext="{d:DesignInstance {x:Type ViewModels:MainWindowViewModel}}"> <i:Interaction.Triggers> <i:EventTrigger SourceObject="{Binding Mode=OneWay}" EventName="CloseRequested" > <ei:CallMethodAction TargetObject="{Binding ElementName=window}" MethodName="Close"/> </i:EventTrigger> </i:Interaction.Triggers> <Grid> <Button Content="Some Content" Command="{Binding SomeCommand}" Width="100" Height="25"/> </Grid>
这很简单。 您可以为Login – LoginViewModel创build您自己的ViewModel类。 你可以创build视图var dialog = new UserView(); 在你的LoginViewModel里面。 你可以设置Command LoginCommand到button。
<Button Name="btnLogin" IsDefault="True" Content="Login" Command="{Binding LoginCommand}" />
和
<Button Name="btnCancel" IsDefault="True" Content="Login" Command="{Binding CancelCommand}" />
ViewModel类:
public class LoginViewModel { Window dialog; public bool ShowLogin() { dialog = new UserView(); dialog.DataContext = this; // set up ViewModel into View if (dialog.ShowDialog() == true) { return true; } return false; } ICommand _loginCommand public ICommand LoginCommand { get { if (_loginCommand == null) _loginCommand = new RelayCommand(param => this.Login()); return _loginCommand; } } public void CloseLoginView() { if (dialog != null) dialog.Close(); } public void Login() { if(CheckLogin()==true) { CloseLoginView(); } else { // write error message } } public bool CheckLogin() { // ... check login code return true; } }
只需使用以下代码即可closures当前窗口:
Application.Current.Windows[0].Close();
以下是使用MVVM Light Messenger代替事件的简单示例。 视图模型在单击button时发送closures消息:
public MainViewModel() { QuitCommand = new RelayCommand(ExecuteQuitCommand); } public RelayCommand QuitCommand { get; private set; } private void ExecuteQuitCommand() { Messenger.Default.Send<CloseMessage>(new CloseMessage()); }
然后在窗口后面的代码中收到。
public Main() { InitializeComponent(); Messenger.Default.Register<CloseMessage>(this, HandleCloseMessage); } private void HandleCloseMessage(CloseMessage closeMessage) { Close(); }
您可以使用MVVMLight工具包中的Messenger
。 在你的ViewModel
发送这样的消息:
Messenger.Default.Send(new NotificationMessage("Close"));
然后在你的windows代码后面,在InitializeComponent
之后,注册这样的消息:
Messenger.Default.Register<NotificationMessage>(this, m=>{ if(m.Notification == "Close") { this.Close(); } });
您可以在这里find更多关于MVVMLight工具包的信息: Codeplex上的MVVMLight工具包
请注意,MVVM中没有“所有规则都没有代码隐藏”,您可以在代码隐藏视图中注册消息。
这个怎么样?
视图模型:
class ViewModel { public Action CloseAction { get; set; } private void Stuff() { // Do Stuff CloseAction(); // closes the window } }
在您的ViewModel中,使用CloseAction()来closures窗口,就像上面的例子一样。
视图:
public View() { InitializeComponent(); ViewModel vm = new ViewModel (); // this creates an instance of the ViewModel this.DataContext = vm; // this sets the newly created ViewModel as the DataContext for the View if (vm.CloseAction == null) vm.CloseAction = new Action(() => this.Close()); }
可能会迟到,但这是我的答案
foreach (Window item in Application.Current.Windows) { if (item.DataContext == this) item.Close(); }
System.Environment.Exit(0); 在视图模型将工作。