从ViewModel关闭窗口

WPF*_*oob 81 c# wpf mvvm

我使用a创建一个登录,window control以允许用户登录WPF我正在创建的应用程序.

到目前为止,我已经创建了一种方法,用于检查用户是否已输入正确的凭据,usernamepasswordtextbox登录屏幕上输入binding两个properties.

我通过创建一个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();
} 
Run Code Online (Sandbox Code Playgroud)

我也有一个command,我bind到中我的按钮,xaml像这样;

<Button Name="btnLogin" IsDefault="True" Content="Login" Command="{Binding ShowLoginCommand}" />
Run Code Online (Sandbox Code Playgroud)

当我输入用户名和密码时,它会执行适当的代码,无论是正确还是错误.但是,当用户名和密码都正确时,如何从ViewModel关闭此窗口?

我之前尝试使用过dialog modal但是它没有完全奏效.此外,在我的app.xaml中,我做了类似下面的操作,首先加载登录页面,然后加载实际的应用程序.

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

问题:如何Window control从ViewModel 关闭登录?

提前致谢.

Joe*_*oel 131

您可以使用窗口将窗口传递给ViewModel CommandParameter.请参阅下面的示例.

我已经实现了一个CloseWindow方法,它将Windows作为参数并关闭它.窗口通过传递给ViewModel CommandParameter.请注意,您需要为x:Name应该关闭的窗口定义一个.在我的XAML窗口中,我通过调用此方法Command并将窗口本身作为参数传递给ViewModel使用CommandParameter.

Command="{Binding CloseWindowCommand, Mode=OneWay}" 
CommandParameter="{Binding ElementName=TestWindow}"
Run Code Online (Sandbox Code Playgroud)

视图模型

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

视图

<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>
Run Code Online (Sandbox Code Playgroud)

请注意,我使用的是MVVM轻量级框架,但主体适用于每个wpf应用程序.

此解决方案违反了MVVM模式,因为视图模型不应该知道有关UI实现的任何信息.如果要严格遵循MVVM编程范例,则必须使用接口抽象视图的类型.

MVVM一致解决方案(前EDIT2)

用户Crono在评论部分提到了一个有效点:

将Window对象传递给视图模型会破坏MVVM模式IMHO,因为它会强制您的虚拟机知道它正在查看的内容.

您可以通过引入包含close方法的接口来解决此问题.

接口:

public interface ICloseable
{
    void Close();
}
Run Code Online (Sandbox Code Playgroud)

您重构的ViewModel将如下所示:

视图模型

public RelayCommand<ICloseable> CloseWindowCommand { get; private set; }

public MainViewModel()
{
    this.CloseWindowCommand = new RelayCommand<IClosable>(this.CloseWindow);
}

private void CloseWindow(ICloseable window)
{
    if (window != null)
    {
        window.Close();
    }
}
Run Code Online (Sandbox Code Playgroud)

您必须ICloseable在视图中引用和实现该接口

查看(代码背后)

public partial class MainWindow : Window, ICloseable
{
    public MainWindow()
    {
        InitializeComponent();
    }
}
Run Code Online (Sandbox Code Playgroud)

回答原始问题:(前EDIT1)

您的登录按钮(添加了CommandParameter):

<Button Name="btnLogin" IsDefault="True" Content="Login" Command="{Binding ShowLoginCommand}" CommandParameter="{Binding ElementName=LoginWindow}"/>
Run Code Online (Sandbox Code Playgroud)

你的代码:

 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();
     }
 }
Run Code Online (Sandbox Code Playgroud)

  • 将"Window"对象传递给视图模型会破坏MVVM模式IMHO,因为它会强制您的虚拟机知道它正在查看的内容.如果视图是MDI界面中的停靠选项卡,该怎么办?执行此操作的正确方法IMHO是传递某种实现Close方法的IUIHost接口,并具有您希望显示您的vm实现它的任何视图. (32认同)
  • 如果你不喜欢命名你的窗口,你也可以像这样绑定参数:`CommandParameter ="{Binding RelativeSource = {RelativeSource AncestorType = {x:Type Window}}}"` (12认同)
  • 没关系,因为界面隐藏了ViewModel的具体实现.ViewModel对视图一无所知,只是它实现了Close()方法.因此,视图可以是任何东西:WPF窗口,WinForms表单,UWP应用程序甚至WPF网格.它将视图与视图模型分离. (2认同)

Ste*_*eck 31

保持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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后在您的窗口中,您只需将CloseTrigger绑定到一个布尔值,该值将在您希望窗口关闭时设置.

<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>
Run Code Online (Sandbox Code Playgroud)

最后,您希望窗口关闭时,您的DataContext/ViewModel将具有一个属性,如下所示:

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;
}
Run Code Online (Sandbox Code Playgroud)

(设置你的Window.DataContext = new MainWindowViewModel())


Chr*_*isO 30

当我需要这样做时,我通常会在视图模型上放置一个事件,然后将它连接到Window.Close()将视图模型绑定到窗口时

public class LoginViewModel
{
    public event EventHandler OnRequestClose;

    private void Login()
    {
        // Login logic here
        OnRequestClose(this, new EventArgs());
    }
}
Run Code Online (Sandbox Code Playgroud)

并在创建登录窗口时

var vm = new LoginViewModel();
var loginWindow = new LoginWindow
{
    DataContext = vm
};
vm.OnRequestClose += (s, e) => loginWindow.Close();

loginWindow.ShowDialog(); 
Run Code Online (Sandbox Code Playgroud)

  • 匿名代表很快就会被写入,但值得注意的是,该事件无法取消注册(这可能是也可能不是问题).通常使用完整的事件处理程序会更好. (11认同)
  • 这比公认的答案要好得多,因为它不会破坏 MVVM 模式。 (2认同)
  • 简单、明显且有效。这对我来说效果很好。 (2认同)

小智 18

可能会迟到,但这是我的答案

foreach (Window item in Application.Current.Windows)
{
    if (item.DataContext == this) item.Close();
}
Run Code Online (Sandbox Code Playgroud)


DHN*_*DHN 12

这是我在几个项目中使用的东西.它可能看起来像一个黑客,但它工作正常.

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,您可以绑定DialogResult到VM并设置其属性值.在Window将关闭,当值设置.

<!-- Assuming that the VM is bound to the DataContext and the bound VM has a property DialogResult -->
<Window someNs:AttachedProperties.DialogResult={Binding DialogResult} />
Run Code Online (Sandbox Code Playgroud)

这是我们生产环境中运行的摘要

<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>
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,我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
}
Run Code Online (Sandbox Code Playgroud)


Ras*_*ssK 12

简单的方法

public interface IRequireViewIdentification
{
    Guid ViewID { get; }
}
Run Code Online (Sandbox Code Playgroud)

实现ViewModel

public class MyViewVM : IRequireViewIdentification
{
    private Guid _viewId;

    public Guid ViewID
    {
        get { return _viewId; }
    }

    public MyViewVM()
    {
        _viewId = Guid.NewGuid();
    }
}
Run Code Online (Sandbox Code Playgroud)

添加常规窗口管理器帮助程序

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();
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

并在viewmodel中关闭它

WindowManager.CloseWindow(ViewID);
Run Code Online (Sandbox Code Playgroud)


Ano*_*non 5

这个怎么样?

视图模型:

class ViewModel
{
    public Action CloseAction { get; set; }
    private void Stuff()
    {
       // Do Stuff
       CloseAction(); // closes the window
    }
}
Run Code Online (Sandbox Code Playgroud)

在您的 ViewModel 中,使用 CloseAction() 关闭窗口,就像上面的示例一样。

看法:

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());
}
Run Code Online (Sandbox Code Playgroud)


Ser*_*lok 5

我知道这是一篇旧帖子,可能没有人会滚动这么远,我知道我没有。所以,经过几个小时的尝试不同的东西,我找到了这个博客,伙计把它杀了。最简单的方法来做到这一点,试过了,它就像一个魅力。

博客

在视图模型中:

...

public bool CanClose { get; set; }

private RelayCommand closeCommand;
public ICommand CloseCommand
{
    get
    {
        if(closeCommand == null)
        (
            closeCommand = new RelayCommand(param => Close(), param => CanClose);
        )
    }
}

public void Close()
{
    this.Close();
}

...
Run Code Online (Sandbox Code Playgroud)

向 ViewModel 添加 Action 属性,但从 View 的代码隐藏文件中定义它。这将让我们在 ViewModel 上动态定义一个指向 View 的引用。

在 ViewModel 上,我们只需添加:

public Action CloseAction { get; set; }
Run Code Online (Sandbox Code Playgroud)

在视图上,我们将其定义为:

public View()
{
    InitializeComponent() // this draws the View
    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());
}
Run Code Online (Sandbox Code Playgroud)