我试图学习WPF和MVVM问题,但遇到了障碍。 这个问题与这个问题类似,但不完全相同(handling-dialog -in-wpf-with-mvvm)…

我有一个使用MVVM模式编写的“Login”表单。

该表单有一个ViewModel,其中包含用户名和密码,它们使用普通的数据绑定绑定到XAML中的视图。 它还有一个“登录”命令,该命令被绑定到表单上的“登录”按钮,agan使用正常的数据绑定。

当“Login”命令触发时,它会调用ViewModel中的一个函数,该函数会触发并通过网络发送数据以登录。当这个函数完成时,有2个动作:

登录无效-我们只是显示一个消息框,一切正常 登录是有效的,我们需要关闭login表单,并让它返回true作为它的dialgresult…

问题是,ViewModel对实际的视图一无所知,所以它如何关闭视图并告诉它返回一个特定的dialgresult ??我可以在代码背后粘贴一些代码,和/或通过视图模型传递视图,但这似乎会击败整个点MVVM完全…


更新

最后,我违背了MVVM模式的“纯度”,让视图发布了一个Closed事件,并公开了一个Close方法。ViewModel会调用view。close。视图仅通过接口了解,并通过IOC容器连接,因此不会损失可测试性或可维护性。

公认的答案是-5票,这似乎相当愚蠢!虽然我很清楚通过“纯粹”解决问题所获得的良好感觉,但我肯定不是唯一一个认为200行事件、命令和行为只是为了避免以“模式”和“纯粹”为名的一行方法有点荒谬的人....


当前回答

假设你的登录对话框是第一个被创建的窗口,在你的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;
        }
    }

其他回答

从我的角度来看,这个问题很好,因为同样的方法不仅适用于“Login”窗口,而且适用于任何类型的窗口。我看了很多建议,没有一个适合我。请审阅我从MVVM设计模式文章中获得的建议。

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

为了让窗口关闭,你的窗口的OnLoaded方法应该被重写:

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

你的应用程序的OnStartup方法:

    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();
    }

我想在WorkspaceViewModel中的RequestClose事件和CloseCommand属性实现是非常清楚的,但我将显示它们是一致的:

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;
    }
}

和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
}

附注:不要因为那些消息来源而对我不好!如果我昨天就买了,就能省几个小时……

P.P.S.欢迎任何评论或建议。

另一个解决方案是在视图模型中使用INotifyPropertyChanged创建属性,如dialgresult,然后在代码背后写:

public class SomeWindow: ChildWindow
{
    private SomeViewModel _someViewModel;

    public SomeWindow()
    {
        InitializeComponent();

        this.Loaded += SomeWindow_Loaded;
        this.Closed += SomeWindow_Closed;
    }

    void SomeWindow_Loaded(object sender, RoutedEventArgs e)
    {
        _someViewModel = this.DataContext as SomeViewModel;
        _someViewModel.PropertyChanged += _someViewModel_PropertyChanged;
    }

    void SomeWindow_Closed(object sender, System.EventArgs e)
    {
        _someViewModel.PropertyChanged -= _someViewModel_PropertyChanged;
        this.Loaded -= SomeWindow_Loaded;
        this.Closed -= SomeWindow_Closed;
    }

    void _someViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == SomeViewModel.DialogResultPropertyName)
        {
            this.DialogResult = _someViewModel.DialogResult;
        }
    }
}

最重要的片段是_someViewModel_PropertyChanged。 dialgresultpropertyname可以是SomeViewModel中的某个公共const字符串。

我使用这种技巧在视图控件中做一些更改,以防在ViewModel中很难做到这一点。OnPropertyChanged在ViewModel中,你可以在View中做任何你想做的事情。ViewModel仍然是“单元可测试的”,后面代码中的一小行代码没有什么区别。

这是一个简单而干净的解决方案——您向ViewModel添加一个事件,并指示窗口在该事件触发时关闭自己。

要了解更多细节,请参阅我的博客文章,从ViewModel关闭窗口。

XAML:

<Window
  x:Name="this"
  xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"  
  xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions">
  <i:Interaction.Triggers>
    <i:EventTrigger SourceObject="{Binding}" EventName="Closed">
      <ei:CallMethodAction
        TargetObject="{Binding ElementName=this}"
        MethodName="Close"/>
    </i:EventTrigger>
  </i:Interaction.Triggers>
<Window>

ViewModel:

private ICommand _SaveAndCloseCommand;
public ICommand SaveAndCloseCommand
{
  get
  {
    return _SaveAndCloseCommand ??
      (_SaveAndCloseCommand = new DelegateCommand(SaveAndClose));
  }
}
private void SaveAndClose()
{
  Save();
  Close();
}

public event EventHandler Closed;
private void Close()
{
  if (Closed != null) Closed(this, EventArgs.Empty);
}

注意:这个例子使用了Prism的DelegateCommand(参见Prism: commands),但是可以使用任何ICommand实现。

您可以使用这个官方包中的行为。

供参考,我遇到了同样的问题,我想我找到了一个不需要全局变量或静态变量的工作,尽管它可能不是最好的答案。我让你们自己决定。

在我的例子中,实例化要显示的窗口的ViewModel(让我们称之为ViewModelMain)也知道LoginFormViewModel(使用上面的情况作为例子)。

所以我所做的是在LoginFormViewModel上创建一个属性,它的类型是ICommand(让我们称之为CloseWindowCommand)。然后,在窗口上调用. showdialog()之前,我将LoginFormViewModel上的CloseWindowCommand属性设置为我实例化的窗口的Window . close()方法。然后在LoginFormViewModel中,我所要做的就是调用CloseWindowCommand.Execute()来关闭窗口。

我认为这是一种变通/hack,但它在没有真正打破MVVM模式的情况下工作得很好。

你可以随意批评这个过程,我可以接受!:)

Application.Current.MainWindow.Close() 

这够了!