谁拥有消息框,视图或ViewModel?

时间:2018-02-16 23:10:51

标签: c# wpf mvvm mahapps.metro

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

我创建了一个对话框,这是一个选择对话框,您可以在左侧选择项目,单击右箭头按钮,项目移动到右侧。

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

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

DataGrid绑定到ViewModel中的ObservableCollection

由于WPF中的普通消息框不具有样式,因此Mahapps提供了自己的消息框。我在这里发现,当我尝试在视图中打开一个消息框时,MahApp会抛出一个空引用异常。它与我的设置有关,因为他们的演示工作得很好。

事实证明有人提供了打开Mahapps消息框的方法in the View Model instead of the view. 我的问题是,您为什么要这样做?

View是否对任何可视元素(包括消息框)负责,并且不是验证在View的Code Behind中允许做的一件事吗?

请注意,此方法会导致新的皱纹,现在您需要一种方法来fire the View Model's method or ICommand from CodeBehind

(DataContext as SecurityDialogViewModel).AddPermissions();

3 个答案:

答案 0 :(得分:1)

我一直采用通过回调接口公开用户对话框的方法。 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);
}

然后,在视图代码隐藏中实现接口。

在视图ctor中创建viewmodel时,请传递this

public class MainViewMode : IMainViewCallbacks
{
   private vm = null;
   public MainWindow()
   {
      vm = new MainViewModel(this);
      this.DataContext = vm;
   }
}

最后,向viewmodel ctor添加一个参数以接收接口:

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

这是可以单元测试的;单元测试视图提供的接口可以伪造已接收到用户输入并且只返回一个常量值。

答案 1 :(得分:0)

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

您需要照顾两件事:

  1. 是否可以显示对话框?
  2. 显示对话框
  3. 在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*/);
        }
    }
    

    请注意,这与您在视图中保存Button并且按钮的Enabled属性将在命令中绑定到IsEnabled没有什么不同。然后,一旦启用该按钮,用户就会单击该按钮并保存该对象。在这种情况下,它的滑动能力(或显示能力)是绑定的,但它不是保存,而只是在Show上调用IDialog

答案 2 :(得分:0)

您尝试完成的所有操作都可以从视图中完成,无需考虑ViewModel,除非您有更高级的要求。 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.");
}

}

我正在使用Linq来查询Datagrid ItemsSource(我的数据网格正在使用User的集合,所以如果那不是你的底层类,你需要将它替换为用于你的任何底层类数据网格)

相关问题