我试图学习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行事件、命令和行为只是为了避免以“模式”和“纯粹”为名的一行方法有点荒谬的人....


当前回答

以下是我最初所做的,它确实有效,但它似乎相当冗长和丑陋(全局静态的任何东西都不好)

1: App.xaml.cs

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

2: LoginForm.xaml

// bind the global command to a local eventhandler
<CommandBinding Command="client:App.LoggedIn" Executed="OnLoggedIn" />

3: LoginForm.xaml.cs

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

4: LoginFormViewModel.cs

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

我后来删除了所有这些代码,只让LoginFormViewModel在它的视图上调用Close方法。它最终变得更好,也更容易理解。在我看来,模式的意义在于让人们更容易理解你的应用程序在做什么,在这种情况下,MVVM使它比我没有使用它更难理解,而且现在是一个反模式。

其他回答

在你的视图/任何用户控件(或你想关闭的窗口)中创建一个依赖属性。像下图:

 public bool CloseTrigger
        {
            get { return (bool)GetValue(CloseTriggerProperty); }
            set { SetValue(CloseTriggerProperty, value); }
        }

        public static readonly DependencyProperty CloseTriggerProperty =
            DependencyProperty.Register("CloseTrigger", typeof(bool), typeof(ControlEventBase), new PropertyMetadata(new PropertyChangedCallback(OnCloseTriggerChanged)));

        private static void OnCloseTriggerChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
        {
            //write Window Exit Code
        }

并从ViewModel的属性中绑定它:

<Window x:Class="WpfStackOverflowTempProject.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"  Width="525"
        CloseTrigger="{Binding Path=CloseWindow,Mode=TwoWay}"

VeiwModel中的属性:

private bool closeWindow;

    public bool CloseWindow
    {
        get { return closeWindow; }
        set 
        { 
            closeWindow = value;
            RaiseChane("CloseWindow");
        }
    }

现在通过改变ViewModel中的CloseWindow值来触发关闭操作。:)

从我的角度来看,这个问题很好,因为同样的方法不仅适用于“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.欢迎任何评论或建议。

这可能很晚了,但我遇到了同样的问题,我找到了一个适合我的解决方案。

我不知道如何创建一个没有对话框的应用程序(也许这只是一个思维障碍)。所以我在僵局与MVVM和显示一个对话框。所以我看到了这篇CodeProject文章:

http://www.codeproject.com/KB/WPF/XAMLDialog.aspx

这是一个UserControl,基本上允许一个窗口位于另一个窗口的可视树中(在xaml中不允许)。它还公开了一个名为isdisplays的布尔型DependencyProperty。

你可以设置一种样式,通常是在一个资源字典中,当控件的Content属性!= null时通过触发器显示对话框:

<Style TargetType="{x:Type d:Dialog}">
    <Style.Triggers>
        <Trigger Property="HasContent"  Value="True">
            <Setter Property="Showing" Value="True" />
        </Trigger>
    </Style.Triggers>
</Style>

在你想要显示对话框的视图中,只需这样做:

<d:Dialog Content="{Binding Path=DialogViewModel}"/>

在你的ViewModel中,你所要做的就是将属性设置为一个值(注意:ViewModel类必须支持INotifyPropertyChanged,以便视图知道发生了什么)。

像这样:

DialogViewModel = new DisplayViewModel();

为了匹配ViewModel和View,你应该在资源词典中有这样的东西:

<DataTemplate DataType="{x:Type vm:DisplayViewModel}">
    <vw:DisplayView/>
</DataTemplate>

通过所有这些,您可以得到一行代码来显示对话框。您遇到的问题是,仅使用上述代码无法真正关闭对话框。这就是为什么你必须在DisplayViewModel基类中放入一个事件,而不是上面的代码,写这个

        var vm = new DisplayViewModel();
        vm.RequestClose += new RequestCloseHandler(DisplayViewModel_RequestClose);
        DialogViewModel = vm;

然后,您可以通过回调处理对话框的结果。

这可能看起来有点复杂,但一旦奠定了基础,就非常简单了。同样,这是我的实现,我相信还有其他人:)

希望这能帮到你,它救了我。

public partial class MyWindow: Window
{
    public ApplicationSelection()
    {
      InitializeComponent();

      MyViewModel viewModel = new MyViewModel();

      DataContext = viewModel;

      viewModel.RequestClose += () => { Close(); };

    }
}

public class MyViewModel
{

  //...Your code...

  public event Action RequestClose;

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

  public void SomeFunction()
  {
     //...Do something...
     Close();
  }
}

我实现了Joe White的解决方案,但遇到了偶尔出现的“只有在创建Window并显示为对话框后才能设置dialgresult”错误。

在视图关闭后,我一直保持ViewModel,偶尔我也会使用相同的VM打开一个新的视图。看起来,在旧视图被垃圾收集之前关闭新视图会导致dialgresultchanged试图在关闭的窗口上设置dialgresult属性,从而引发错误。

我的解决方案是改变dialgresultchanged来检查窗口的IsLoaded属性:

private static void DialogResultChanged(
    DependencyObject d,
    DependencyPropertyChangedEventArgs e)
{
    var window = d as Window;
    if (window != null && window.IsLoaded)
        window.DialogResult = e.NewValue as bool?;
}

做出此更改后,关闭对话框的任何附件都将被忽略。