C# 消息框、视图或视图模型的所有者是谁?

C# 消息框、视图或视图模型的所有者是谁?,c#,wpf,mvvm,mahapps.metro,C#,Wpf,Mvvm,Mahapps.metro,我正在使用Mahapps.Metro开发一个WPF应用程序,这是一个提供“现代”UI样式的Nuget包 我创建了一个对话框,它是一个选择对话框,您可以在其中选择左侧的项目,单击右箭头按钮,然后项目移动到右侧 我的对话框中的一个验证规则是,在按下按钮之前必须至少选择一个项目,因此(在我的视图后面的代码中)我打开一个消息框,如果用户没有选择至少一个项目,则通知用户: private void AddButton_Click(object sender, RoutedEventArgs e) {

我正在使用Mahapps.Metro开发一个WPF应用程序,这是一个提供“现代”UI样式的Nuget包

我创建了一个对话框,它是一个选择对话框,您可以在其中选择左侧的项目,单击右箭头按钮,然后项目移动到右侧

我的对话框中的一个验证规则是,在按下按钮之前必须至少选择一个项目,因此(在我的视图后面的代码中)我打开一个消息框,如果用户没有选择至少一个项目,则通知用户:

private void AddButton_Click(object sender, RoutedEventArgs e)
{
    if (!IsAnySelected(Users))
    {
        // MetroWindow call
        this.ShowMessageAsync("Permissions", "Please select a User.");
        return;

        // Call ViewModel `AddPermissions()` method here.
    }
}

bool IsAnySelected(DataGrid dataGrid)
{
    foreach(dynamic d in dataGrid.ItemsSource)
    { 
        if (d.IsSelected) return true;
    }
    return false;
}
(数据网格
绑定到ViewModel中的
可观察集合

因为WPF中的普通消息框是不可设置样式的,所以Mahapps提供了自己的消息框。正是在这里,我发现当我试图在视图中打开消息框时,MahApps抛出了一个空引用异常。这与我的设置有关,因为他们的演示效果很好

原来有人提供了打开Mahapps消息框的方法我的问题是,你为什么要这样做?

视图不负责任何可视元素(包括消息框)吗?在视图的代码中,验证不是允许做的一件事吗

请注意,这种方法会导致新的问题,即您现在需要一种方法来:


您想要做的与启用一个按钮并在单击时保存某些内容并没有什么不同。在您的情况下,它是启用对话框并显示它。挑战在于如何在不耦合到对话框窗口的情况下从ViewModel执行此操作

有两件事你需要注意:

  • 可以显示对话框吗
  • 显示对话框
  • 在ViewModel中,可以执行以下操作:

    public class MainViewModel
    {
        private IDialog dialog;
        private ICommand showCommand;
        public MainViewModel() : this(null)
        {
        }
    
        public MainViewModel(IDialog dialog)
        {
            this.dialog = dialog;
            this.showCommand = new ShowCommand(this.ShowCommandHandler);
        }
    
        private void ShowCommandHandler(object sender, EventArgs e)
        {
            this.dialog.Show();
        }
    
        public ICommand ShowCommand { get { return this.showCommand; } }
    }
    
    请注意上面的
    ShowCommandHandler
    和构造函数重载,它使用
    IDialog
    。以下是
    IDialog

    public interface IDialog
    {
        void Show();
    }
    
    在我的示例中,
    IDialog
    已经显示,但在您的示例中,它将是显示滑动对话框的某种方法。如果是
    Show
    ,太好了!否则,将其更改为与对话框中的方法匹配

    命令如下:

    public class ShowCommand : ICommand
    {
        public event EventHandler CanExecuteChanged;
        public EventHandler handler;
        public ShowCommand(EventHandler handler)
        {
            this.handler = handler;
        }
    
        public bool CanExecute(object parameter)
        {
            // Decide whether this command can be executed. 
            // Check if anything is selected from within your collection
            throw new NotImplementedException();
        }
    
        public void Execute(object parameter)
        {
            this.handler(this, EventArgs.Empty);
        }
    }
    
    最后,在主视图中,执行以下操作:

    public partial class MainView : Window
    {
        public MainView()
        {
            this.InitializeComponent();
            this.DataContext = new MainViewModel(/*pass your dialog here*/);
        }
    }
    

    请注意,这与您在视图上有一个save
    按钮
    没有什么不同,该按钮的
    Enabled
    属性将绑定到命令中的
    IsEnabled
    。然后,一旦按钮被启用,用户单击它,对象被保存。在这种情况下,它的滑动能力(或显示能力)是绑定的,而不是保存,它简单地调用<代码> >代码> > <代码> I/Actudio>

    < p>您所要完成的一切都可以从视图中完成,不需要考虑VIEWMDEM,除非您有更高级的要求。ShowMessageAsync方法是异步的,但是您没有将方法签名作为异步的,并且您不等待返回

    您还可以完全摆脱IsAnySelected方法,只使用Linq。下:

    private async void AddButton_Click(object sender, RoutedEventArgs e)
    {
    
    var IsAnySelectedUsers = dataGrid.ItemsSource.Cast<User>().Any(p => p.IsSelected);
    
    if (!IsAnySelectedUsers)
    {
        await this.ShowMessageAsync("Permissions", "Please select a User.");
    }
    
    private async void AddButton\u单击(对象发送器,路由目标)
    {
    var IsAnySelectedUsers=dataGrid.ItemsSource.Cast().Any(p=>p.IsSelected);
    如果(!IsAnySelectedUsers)
    {
    等待此消息。ShowMessageAsync(“权限”,“请选择用户”);
    }
    
    }


    我使用Linq来查询Datagrid项目资源(我的Datagrid使用的是一个用户集合,因此,如果这不是您的基础类,那么您需要将其替换为Datagrid使用的任何基础类)

    我一直采用通过回调接口公开用户对话框的方法。OpenFileDialog、SaveFileDialog、MessageBox、FolderSelectionDialog等由接口定义:

    public interface IMainViewCallbacks
    {
            bool GetPathViaOpenDialog(out string filePath, string szFilter, 
                     string szDefaultExt, string szInitialDir);
            bool GetPathViaSaveDialog(out string filePath, string szFilter, 
                     string szDefaultExt, string szInitialDir);
            bool GetFolderPath(out string folderPath);
            MessageBoxResult MessageBox(string messageBoxText, string caption, 
                      MessageBoxButton button, MessageBoxImage icon);
    }
    
    public class MainViewModel
    {
        IMainViewCallbacks Calllbacks = null;
        public MainViewModel(IMainViewCallbacks cb)
        {
           // stash the callbacks for later.
           this.Callbacks = cb;
        }
    
        // pseudocode for the command that consumes the callback
        public ICommand .... 
        {
            Execute() { this.Callbacks.GetPathViaOpenDialog(); }
        } 
    }
    
    然后,在视图codebehind中实现该接口

    在视图ctor中创建viewmodel时,传递以下信息:

    public class MainViewMode : IMainViewCallbacks
    {
       private vm = null;
       public MainWindow()
       {
          vm = new MainViewModel(this);
          this.DataContext = vm;
       }
    }
    
    最后,向viewmodel ctor添加一个参数以接收接口:

    public interface IMainViewCallbacks
    {
            bool GetPathViaOpenDialog(out string filePath, string szFilter, 
                     string szDefaultExt, string szInitialDir);
            bool GetPathViaSaveDialog(out string filePath, string szFilter, 
                     string szDefaultExt, string szInitialDir);
            bool GetFolderPath(out string folderPath);
            MessageBoxResult MessageBox(string messageBoxText, string caption, 
                      MessageBoxButton button, MessageBoxImage icon);
    }
    
    public class MainViewModel
    {
        IMainViewCallbacks Calllbacks = null;
        public MainViewModel(IMainViewCallbacks cb)
        {
           // stash the callbacks for later.
           this.Callbacks = cb;
        }
    
        // pseudocode for the command that consumes the callback
        public ICommand .... 
        {
            Execute() { this.Callbacks.GetPathViaOpenDialog(); }
        } 
    }
    

    这是单元可测试的;单元测试视图提供的界面可以假装接收到用户输入,只返回一个常量值。

    打开消息框类似于发出
    命令
    ,而
    命令
    将由ViewModel处理。在这种情况下,该命令用于打开对话框。这没什么错,因为这是ViewModel的责任。@CodingYoshi:当然可以,但出于验证目的,我无权访问ViewModel中的表单控件。相反,我将不得不依赖视图模型的数据绑定
    ObservableCollection
    中的
    IsSelected
    字段的值(这是一种可行的方法,如果是迂回的话,但我的直觉仍然是ViewModel不需要支持UI组件)。Mahapps家伙在ViewModel中实现这一点的技术将Mahapps.Metro与ViewModel紧密地结合在一起,我对此也不感兴趣。即使您可以访问控件,在VM中使用它也不是一个好主意,因为这样您的VM就与UI元素耦合在一起了。换句话说,您将无法对其进行单元测试。因此,使用
    IsSelected
    非常好。虚拟机负责协调工作,所以我认为如果它是独立的UI组件就可以了(你可以在两者之间创建另一个项目,称之为coordinator之类的东西,但我不会这么做)。根据模式,讨论可以来回进行,争论的任何一方都有好处。如果我在VM中这样做,我将确保在所有屏幕中保持一致。消息框是一个UI元素。如果视图模型忙于打开模态消息框,你他妈的要如何对其进行单元测试?这难道不意味着视图模型现在必须了解视图吗?嗯,差不多了。正如我在问题中所描述的,一旦验证成功,我打算将控制权传递给ViewModel中的方法。