ViewModel应如何关闭表单?

Ori*_*rds 239 c# wpf mvvm

我正在尝试学习WPF和MVVM问题,但是遇到了障碍.这个问题与此问题相似但不完全相同(处理对话框-wpf-with-mvvm) ...

我有一个使用MVVM模式编写的"登录"表单.

此表单有一个ViewModel,它包含用户名和密码,这些用户名和密码使用普通数据绑定绑定到XAML中的视图.它还有一个"登录"命令,该命令绑定到表单上的"登录"按钮,使用正常数据绑定.

当"登录"命令触发时,它会调用ViewModel中的一个函数,该函数将关闭并通过网络发送数据以进行登录.当此函数完成时,有2个操作:

  1. 登录无效 - 我们只显示一个MessageBox,一切都很好

  2. 登录有效,我们需要关闭登录表单并让它返回true作为其DialogResult...

问题是,ViewModel对实际视图一无所知,那么如何关闭视图并告诉它返回一个特定的DialogResult?我可以在CodeBehind中粘贴一些代码,和/或将View传递给ViewModel,但这似乎完全打败了MVVM的全部内容......


更新

最后,我刚刚违反了MVVM模式的"纯度",并让View发布了一个Closed事件,并公开了一个Close方法.ViewModel然后才会调用view.Close.该视图仅通过接口已知并通过IOC容器连接,因此不会丢失可测试性或可维护性.

接受的答案是-5票,这似乎很愚蠢!虽然我很清楚通过在"纯粹"时解决问题所获得的良好感受,当然我不是唯一一个认为200行事件,命令和行为只是为了避免单行方法"模式"和"纯度"的名称有点荒谬......

Joe*_*ite 314

我被Thejuan的答案所启发,写了一个更简单的附属物.没有风格,没有触发器; 相反,你可以这样做:

<Window ...
        xmlns:xc="clr-namespace:ExCastle.Wpf"
        xc:DialogCloser.DialogResult="{Binding DialogResult}">
Run Code Online (Sandbox Code Playgroud)

这几乎就像WPF团队做得对,并且首先使DialogResult成为依赖属性一样干净.只需bool? DialogResult在ViewModel上放置一个属性并实现INotifyPropertyChanged即可,您的ViewModel可以通过设置属性来关闭Window(并设置其DialogResult).应该是MVVM.

这是DialogCloser的代码:

using System.Windows;

namespace ExCastle.Wpf
{
    public static class DialogCloser
    {
        public static readonly DependencyProperty DialogResultProperty =
            DependencyProperty.RegisterAttached(
                "DialogResult",
                typeof(bool?),
                typeof(DialogCloser),
                new PropertyMetadata(DialogResultChanged));

        private static void DialogResultChanged(
            DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var window = d as Window;
            if (window != null)
                window.DialogResult = e.NewValue as bool?;
        }
        public static void SetDialogResult(Window target, bool? value)
        {
            target.SetValue(DialogResultProperty, value);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我也在我的博客上发布了这个.

  • @HiTech Magic,听起来好像是首先使用单例ViewModel.(咧嘴)说真的,为什么你想要一个单独的ViewModel?在全局变量中保持可变状态是个坏主意.使测试成为一场噩梦,测试是您首先使用MVVM的原因之一. (10认同)
  • 这个是我最喜欢的答案!写好附属物的好工作. (3认同)
  • MVVM的重点不在于将逻辑与任何特定UI紧密结合吗?在这种情况下,布尔?绝对不能被其他UI(如WinForm)使用,而DialogCloser特定于WPF.那么这如何适合作为解决方案呢?另外,为什么写2x-10x代码只是为了通过绑定关闭一个窗口? (3认同)

Bud*_*dda 64

从我的角度来看,问题非常好,因为同样的方法不仅用于"登录"窗口,而且用于任何类型的窗口.我已经回顾了很多建议,没有一个对我来说没问题.请查看我从MVVM设计模式文章中获取的建议.

每个ViewModel类都应该继承WorkspaceViewModel具有该类型的RequestClose事件和CloseCommand属性的ICommand类.该CloseCommand属性的默认实现将引发该RequestClose事件.

为了关闭OnLoaded窗口,应该覆盖窗口的方法:

void CustomerWindow_Loaded(object sender, RoutedEventArgs e)
{
    CustomerViewModel customer = CustomerViewModel.GetYourCustomer();
    DataContext = customer;
    customer.RequestClose += () => { Close(); };
}
Run Code Online (Sandbox Code Playgroud)

OnStartup你的app方法:

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        MainWindow window = new MainWindow();
        var viewModel = new MainWindowViewModel();
        viewModel.RequestClose += window.Close;
        window.DataContext = viewModel;

        window.Show();
    }
Run Code Online (Sandbox Code Playgroud)

我猜这个RequestClose事件和CloseCommand属性实现WorkspaceViewModel非常清楚,但我会证明它们是一致的:

public abstract class WorkspaceViewModel : ViewModelBase
// There's nothing interesting in ViewModelBase as it only implements the INotifyPropertyChanged interface
{
    RelayCommand _closeCommand;
    public ICommand CloseCommand
    {
        get
        {
            if (_closeCommand == null)
            {
                _closeCommand = new RelayCommand(
                   param => Close(),
                   param => CanClose()
                   );
            }
            return _closeCommand;
        }
    }

    public event Action RequestClose;

    public virtual void Close()
    {
        if ( RequestClose != null )
        {
            RequestClose();
        }
    }

    public virtual bool CanClose()
    {
        return true;
    }
}
Run Code Online (Sandbox Code Playgroud)

和源代码RelayCommand:

public class RelayCommand : ICommand
{
    #region Constructors

    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");

        _execute = execute;
        _canExecute = canExecute;
    }
    #endregion // Constructors

    #region ICommand Members

    [DebuggerStepThrough]
    public bool CanExecute(object parameter)
    {
        return _canExecute == null ? true : _canExecute(parameter);
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }

    #endregion // ICommand Members

    #region Fields

    readonly Action<object> _execute;
    readonly Predicate<object> _canExecute;

    #endregion // Fields
}
Run Code Online (Sandbox Code Playgroud)

PS不要因为这些来源对我不好!如果我昨天有他们,那将节省我几个小时......

PPS欢迎任何意见或建议.

  • 嗯,您在XAML文件后面的代码中迷上了“ customer.RequestClose”事件处理程序的事实是否不违反MVVM模式?您还可以首先绑定到关闭按钮上的`Click`事件处理程序,因为无论如何您都已经触摸了后面的代码并做了`this.Close()`!对? (2认同)

Ada*_*lls 18

我使用附加行为来关闭窗口.将ViewModel上的"signal"属性绑定到附加行为(我实际使用触发器)当它设置为true时,该行为将关闭窗口.

http://adammills.wordpress.com/2009/07/01/window-close-from-xaml/

  • 现在它可以通过单行附加行为来实现.请参阅我的回答:http://stackoverflow.com/questions/501886/wpf-mvvm-newbie-how-should-the-viewmodel-close-the-form/3329467#3329467 (4认同)

Eig*_*ite 15

有很多评论在这里争论MVVM的优点和缺点.对我来说,我同意Nir; 这是一个适当使用模式的问题,MVVM并不总是适合.人们似乎已经愿意牺牲所有最重要的软件设计原则,以使其适应MVVM.

那说,..我认为你的情况可能适合一点点重构.

在大多数情况下,我遇到过,WPF可以让你通过没有多个Windows.也许你可以尝试使用Frames和Pages而不是Windows DialogResult.

在你的情况下,我的建议是LoginFormViewModel处理LoginCommand,如果登录无效,请将属性设置为LoginFormViewModel适当的值(false或某些枚举值UserAuthenticationStates.FailedAuthentication).您可以为成功登录(true或其他一些枚举值)执行相同操作.然后,您将使用a DataTrigger响应各种用户身份验证状态,并可以使用简单的Setter方法更改其Source属性Frame.

让你的登录窗口返回一个DialogResult我认为是你感到困惑的地方; 这DialogResult实际上是ViewModel的一个属性.在我看来,WPF的经验有限,当一些事情感觉不对时,通常是因为我在考虑如何在WinForms中做同样的事情.

希望有所帮助.


Jim*_*ace 9

假设您的登录对话框是第一个创建的窗口,请在LoginViewModel类中尝试:

    void OnLoginResponse(bool loginSucceded)
    {
        if (loginSucceded)
        {
            Window1 window = new Window1() { DataContext = new MainWindowViewModel() };
            window.Show();

            App.Current.MainWindow.Close();
            App.Current.MainWindow = window;
        }
        else
        {
            LoginError = true;
        }
    }
Run Code Online (Sandbox Code Playgroud)


小智 6

我处理它的方法是在我的ViewModel中添加一个事件处理程序.当用户成功登录后,我将触发该事件.在我的视图中,我会附加到此事件,当它被解雇时,我会关闭窗口.

  • 这也是我通常做的事情.考虑到所有那些新奇的wpf命令,这看起来有点脏. (2认同)

Shi*_*mmy 6

这是一个简单而干净的解决方案 - 您向ViewModel添加一个事件,并指示Window在触发该事件时自行关闭.

有关详细信息,请参阅我的博客文章,ViewModel的关闭窗口.


Ori*_*rds 4

这是我最初所做的,它确实有效,但看起来相当冗长且丑陋(全局静态任何东西都不好)

1:应用程序.xaml.cs

public partial class App : Application
{
    // create a new global custom WPF Command
    public static readonly RoutedUICommand LoggedIn = new RoutedUICommand();
}
Run Code Online (Sandbox Code Playgroud)

2:登录表单.xaml

// bind the global command to a local eventhandler
<CommandBinding Command="client:App.LoggedIn" Executed="OnLoggedIn" />
Run Code Online (Sandbox Code Playgroud)

3:LoginForm.xaml.cs

// implement the local eventhandler in codebehind
private void OnLoggedIn( object sender, ExecutedRoutedEventArgs e )
{
    DialogResult = true;
    Close();
}
Run Code Online (Sandbox Code Playgroud)

4:LoginFormViewModel.cs

// fire the global command from the viewmodel
private void OnRemoteServerReturnedSuccess()
{
    App.LoggedIn.Execute(this, null);
}
Run Code Online (Sandbox Code Playgroud)

后来我删除了所有这些代码,只LoginFormViewModel在其视图上调用 Close 方法。它最终变得更好、更容易遵循。恕我直言,模式的要点是让人们更容易地理解你的应用程序在做什么,在这种情况下,MVVM 使它比我没有使用它时更难理解,现在是一种反模式