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
}
}
这是我当前流程的图表以及我下一步想做什么
问题
单击按钮时
- 在 MainWindow
上应用一些 UI 更改
- 弹出 ProcessOptionsWindow
- 从 ProcessOptionsWindow
获取设置参数
- 将选定的数据网格行和用户指定的参数传递给
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。
在这种情况下,事情可能会变得有点复杂,但我想这是可以通过这种方式实现的。
场景 一些日期被加载到程序中(例如,在 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
}
}
这是我当前流程的图表以及我下一步想做什么
问题 单击按钮时
- 在 MainWindow 上应用一些 UI 更改
- 弹出 ProcessOptionsWindow
- 从 ProcessOptionsWindow 获取设置参数
- 将选定的数据网格行和用户指定的参数传递给
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。
在这种情况下,事情可能会变得有点复杂,但我想这是可以通过这种方式实现的。