MVVM 模式:命令绑定和 ViewModel 执行之间的中间视图

MVVM pattern: an intermediate View between Command binding and ViewModel execute

场景 一些日期被加载到程序中(例如,在 class 中对学生进行评估,其中每个学生都是具有 his/her 评估数据的不同实体),并且它们的摘要显示在数据网格上。用户选择一些学生,并对他们的评价进行分析。分析过程需要一些参数,因此在分析之前会弹出一个 window 并让用户指定他喜欢的参数;然后执行分析过程。

实施总结 数据网格定义如下并绑定到 ViewModel:

<DataGrid x:Name="CachedSamplesDG" ItemsSource="{Binding cachedDataSummary}">                    
   <DataGrid.Columns>
      <DataGridTextColumn Header="name" Binding="{Binding name}"/>
      <DataGridTextColumn Header="score" Binding="{Binding score}"/>
   </DataGrid.Columns>
</DataGrid>

启动进程的按钮定义如下:

<Button x:Name="AnalysisBT" Content="Analyze" Command="{Binding AnalyzeCommand}" CommandParameter="{Binding ElementName=CachedSamplesDG, Path=SelectedItems}"/>

ViewModel 非常基础,总结如下:

internal class CachedDataSummaryViewModel
    {
        public CachedDataSummaryViewModel()
        {
            _cachedDataSummary = new ObservableCollection<CachedDataSummary>();
            AnalyzeCommand = new SamplesAnalyzeCommand(this);
        }


        private ObservableCollection<CachedDataSummary> _cachedDataSummary;
        public ObservableCollection<CachedDataSummary> cachedDataSummary { get { return _cachedDataSummary; } }


        public ICommand AnalyzeCommand { get; private set; }
    }

这里是分析命令的定义:

internal class SamplesAnalyzeCommand : ICommand
    {
        public SamplesAnalyzeCommand(CachedDataSummaryViewModel viewModel)
        {
            _viewModel = viewModel;
        }

        private CachedDataSummaryViewModel _viewModel;

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

        public bool CanExecute(object parameter)
        {
            // canExecute logic
        }

        public void Execute(object parameter)
        {
            // process mess ...
            // Here I need the selected rows of datagird, which "parameter" delegates them.
            // I also need some other parameters for analysis which user can set through another view
        }
    }

这是我当前流程的图表以及我下一步想做什么

问题 单击按钮时

  1. MainWindow
  2. 上应用一些 UI 更改
  3. 弹出 ProcessOptionsWindow
  4. ProcessOptionsWindow
  5. 获取设置参数
  6. 将选定的数据网格行和用户指定的参数传递给 SamplesAnalyzeCommand

实现此要求的最佳方法是什么?

只需使用像 Good or bad practice for Dialogs in wpf with MVVM? 这样的对话服务。

然后你可以在你的 ViewModel 中做这样的事情

 var result = this.uiDialogService.ShowDialog("Prozess Options Window", prozessOptionVM);

 ...
 var parameter1 = prozessOptionVM.Parameter1;

您可以为Process Options定义另一个Model和ViewModel,然后在SamplesAnalyzeCommand中,显示ProcessOptionsView。当用户完成 ProcessOptionsView 时,主 ViewModel 会收到通知(例如通过事件处理程序)并完成流程。

像这样:

internal class SamplesAnalyzeCommand : ICommand {
   ...
   public void Execute(object parameter)
   {
       this._viewModel.ShowProcessOptions(parameter);
   }
}

internal class CachedDataSummaryViewModel {
    public string Status {
         get {
             return this.status;
         }
         set {
             if (!string.Equals(this.status, value)) {
                 this.status = value;
                 // Notify property change to UI
             }
         }
    }
    ...
    internal void ShowProcessOptions(object paramter) {
        // Model
        var processOptions = new ProcessOptionsModel() {
            otherInfo = parameter
        };
        // View-Model
        var processOptionsViewModel = new ProcessOptionsViewModel();
        processOptionsViewModel.Model = processOptions;
        // View
        var processOptionsView = new ProcessOptionsView(
            processOptionsViewModel
        );
        // Edit2: Update status
        this.Status = "Selecting process options...";

        // You can use the event handler or dialog result
        processOptionsViewModel.OK += this.PerformProcess;
        processOptionsView.ShowDialog();
    }
    private void PerformProcess(object sender, EventArgs e) {
        var processOptionsView = sender as ProcessOptionsView;
        var processOptionsModel = processOptionsView.Model;
        var processOptions = processOptionsModel.Model;          

        // Edit2: Update status
        this.Status = "Performing process...";

        // use processOptions.OtherInfo for initial info
        // use processOptions.* for process options info
        // and perform the process here

        // Edit2: Update status
        this.Status = "Process Done.";
    }
    ...
}
class ProcessOptionsModel {
    public object OtherInfo {
        get;
        set;

    public int Parameter1 {
        get;
        set;
    }
    public IList<ProcessItem> SelectedItems {
        get;
        set;
    }
    ...
} 
class ProcessOptionsViewModel {
    public event EventHandler OK;
    private SamplesAnalyzeCommand model;
    private ICommand okCommand;
    public ProcessOptionsViewModel() {
         this.okCommand = new OKCommand(this.OnOK);
    }
    public SamplesAnalyzeCommand Model {
        get {
            return model;
        }
        set {
            this.model = value;
            // Property changed stuff here
        }
    }
    private void OnOK(object parameter) {
        if (this.OK != null) {
            this.OK = value;
        }
    }
}
class ProcessOptionsView {
     // Interacts with it's view-model and performs OK command if
     // user pressed OK or something
}

希望对您有所帮助。

编辑 (1):

按照 blindmeis 的建议,您可以使用一些对话服务来建立视图之间的连接。

编辑(2):

单击按钮后立即 UI 更改可以在 ShowProcessOptions 的 ShowProcessOptions 方法中完成。我认为您不希望在用户使用选项 window 时将 ui 更改反映到主要 window。 UI 用户关闭选项后的更改 window 可以在 PerformProcess 中完成。

如果您想像下面评论中提到的那样对选项选择(例如从文件中读取)进行抽象,您可以定义一个 IOptionsProvider 接口,并将 ProcessOptionsView 和 View-Model 放在它后面,但您仍然使用同款

interface IOptionsProvider {
    ProcessOptionsModel GetProcessOptions();
}

class ProcessOptionsView : IOptionsProvider {
    public ProcessOptionsModel GetProcessOptions() {
         if (this.ShowDialog()) {
              return this.ModelView.Model;
         }
         return null;
    }
}

class ProcessOptionsFromFile : IOptionsProvider {
    public ProcessOptionsModel GetProcessOptions() {
         // Create an instance of ProcessOptionsModel from File
    }

}

请注意,在这种情况下,我删除了 OK 事件,因为 GetProcessOptions 应该会阻塞,直到用户关闭主 window。如果您想要在 FromFile 案例中采用响应式方法,您可能需要处理异步内容,也许改为定义 GetProcessOptionsAsync。

在这种情况下,事情可能会变得有点复杂,但我想这是可以通过这种方式实现的。