Andreas Duering
Andreas Duering

Reputation: 1758

Command in ViewModel: Signal operation is finished and then access result?

I'm trying to use Commands for the application I'm writing so I can simply have the conditions under which the Buttons on the UI are enabled inside the CanExecute condition in the ViewModel instead of a MultiBinding inside the UI XAML (or even worse, managing the IsEnabled states in code-behind).

Now I want to start a operation which sends and fetches some data via a SerialPort, which are written to a file in the end. This takes several minutes.

[Model]
 + DoLongOperation(string port) : void // Starts a timer to gather data, takes several minutes
 + LongOperationDone : event // fires when operation finished
 + Result: LongOperationResults // set when operation finished (successfully)
 + LongOperationRunning: bool // INPC
 + AbortOperation(): void
 - port: SerialPort

Here's what my non-MVVM approach would be:

[UI]
<ComboBox x:Name"port" />
 <Button Click="ButtonClick">
   <Button.IsEnabled>
     <!-- bind to model directly -->
     <MultiBinding Converter="{StaticResource AllTrueConverter}">
      <!-- multiple conditions: port selected, operation not running... -->
     </MultiBinding>
   </Button.IsEnabled>
   Do the long operation
 </Button>

[UI Code-Behind]
private void ButtonClick(object sender, RoutedEventArgs e)
{
  // unsubscribe first, to avoid multiple execution
  // logic inside UI! "not good", as far as I know
  longOp.LongOperationDone += (s, e2) => {var result = longOp.Result; var filename = result.WriteToFile(); Process.Start(filename); }
  longOp.DoLongOperation((string)port.SelectedItem);
}

Trying to use Commands:

[UI]
<ComboBox x:Name"port" SelectedItem="{Binding PortName}"/>
 <Button Command="{Binding LongOperationCommand}">Do the long operation</Button>

[ViewModel]
 + LongOperationCommand : RelayCommand // Execute: LongOperation.DoLongOperation(PortName); CanExecute: !String.isNullOrEmpty(PortName) && !LongOperation.LongOperationRunning
 + PortName: String // INPC
 # Model: LongOperation

However, using Commands, I'm not sure how I can do the same as in the code-behind example.

So, actually multiple questions:

  1. Is a command the correct way to execute a long-running operation in background "returning" a result (via a property, in this case)?
  2. (general) Should a ViewModel fire events (beside INotifyPropertyChanged; "refire" LongOperationDone in my example) (edit: How would the UI handle them?)
  3. (regarding my problem) Should I, for example, set another Propery IsFinished: bool inside the ViewModel, which gets set when the LongOperationDone is fired in the model, revealing a bound Button in the UI which opens the file with the results? -> Are there better alternatives?

Upvotes: 0

Views: 134

Answers (1)

S.L.
S.L.

Reputation: 1076

I try to answer:

  1. The command is generally the right way, because you should avoid Events in this pattern. But this has nothing to do with longrunning or short running processes. For Long running process you should think about creating a Task that runs in Background

  2. Generally the ViewModel should not fire Events because the View does not handle them, beside NotifyChanged. You should set states in ViewModel with bool Properties like LongOperationRunning:bool

  3. You should set something like LongOperationRunning to tell the View that a Long running process is running and the view should Show a progressbar or whatever you Need in your view. Generally you have to tell the view about the state. And the view has to react depending on the ViewModel state.

Hope tis helps a bit

References:

Task Class

Asynchronous Programming with Async and Await (C# and Visual Basic)

Upvotes: 1

Related Questions