简体   繁体   中英

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

Scenario Some date are loaded into a program (eg, evaluation of students in a class where each student is a distinct entity with his/her evaluation data) and a summary of them is shown on a datagrid. The user selects selects some of the students, and performs an analysis on their evaluation. The analysis process requires some parameters, therefore before analysis a window pops-up and lets user to specify his preferred parameters; then the analysis process executes.

Implementation summary The datagrid is defined as following and binded to a 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>

The button that starts the process is defined as following:

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

The ViewModel is pretty basic and summarized as following:

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

And here is the definition of analysis command:

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

An this is a diagram of my current process and what I would like to do next

该图

Question When the button is clicked

  1. Apply some UI changes on MainWindow
  2. Pop-up ProcessOptionsWindow
  3. Get set parameters from ProcessOptionsWindow
  4. Pass the selected datagrid rows and user specified parameters to SamplesAnalyzeCommand

What would be the best way to achieve this requirement ?

simply use a dialogservice like Good or bad practice for Dialogs in wpf with MVVM? .

then you can do something like this in your ViewModel

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

 ...
 var parameter1 = prozessOptionVM.Parameter1;

You can define another Model and ViewModel for Process Options, and then in the SamplesAnalyzeCommand, display the ProcessOptionsView. When user is done with the ProcessOptionsView, the main ViewModel gets notified (eg by an event handler) and completes the Process.

Something like this:

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
}

Hope it helps.

Edit (1):

As blindmeis suggested, you may use some Dialog Service to make the connection between the views.

Edit (2):

Immidiate UI changes after button click can be done in ShowProcessOptions method of the ShowProcessOptions. I don't think you want reflect ui changes of the options window while user works with it, to the main window. UI changes after user closes options window can be done in PerformProcess.

If you want to make an abstraction for options selection (eg reading from a file) as you mentioned in the comment below, you may define an IOptionsProvider interface, and put ProcessOptionsView and View-Model behind that but still you use the same 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
    }

}

Note that in this case I removed the OK event since the GetProcessOptions is supposed to block until user closes the main window. If you want a responsive approach in the FromFile case, you may need to work on the async stuff, maybe define GetProcessOptionsAsync instead.

In this case things may get a little bit complicated but I guess it is achievable in this way.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM