4

I am using ReactiveUI with AvaloniaUI and have a ViewModel with several ReactiveCommands namely Scan, Load, and Run.

Scan is invoked when an Observable<string> is updated (when I receive a barcode from a scanner).

Load is triggered from within the Scan command.

Run is triggered from a button on the UI.

Simplified code below:

var canRun = Events.ToObservableChangeSet().AutoRefresh().ToCollection().Select(x => x.Any());
Run = ReactiveCommand.CreateFromTask<bool>(EventSuite.RunAsync, canRun);

var canLoad = Run.IsExecuting.Select(x => x == false);
var Load = ReactiveCommand.CreateFromTask<string, Unit>(async (barcode) =>
    {
        //await - go off and load Events.
    }, canLoad);

var canReceiveScan = Load.IsExecuting.Select(x => x == false)
        .Merge(Run.IsExecuting.Select(x => x == false));
var Scan = ReactiveCommand.CreateFromTask<string, Unit>(async (barcode) => 
    {
        //do some validation stuff
        await Load.Execute(barcode)
    }, canReceiveScan);

Barcode
   .SubscribeOn(RxApp.TaskpoolScheduler)
   .ObserveOn(RxApp.MainThreadScheduler)
   .InvokeCommand(Scan);

Each command can only be executed if no other command is running (including itself). But I can't reference the commands' IsExecuting property before is it declared. So I have been trying to merge the "CanExecute" observable variables like so:

canRun = canRun
   .Merge(Run.IsExecuting.Select(x => x == false))
   .Merge(Load.IsExecuting.Select(x => x == false))
   .Merge(Scan.IsExecuting.Select(x => x == false))
   .ObserveOn(RxApp.MainThreadScheduler);

// same for canLoad and canScan

The issue I'm having is that the ReactiveCommand will continue to execute when another command is executing.

Is there a better/correct way to implement this?

hackintosh
  • 105
  • 8

2 Answers2

4

But I can't reference the commands' IsExecuting property before is it declared.

One option is to use a Subject<T>, pass it as the canExecute: parameter to the command, and later emit new values using OnNext on the Subject<T>.

Another option is to use WhenAnyObservable:

this.WhenAnyObservable(x => x.Run.IsExecuting)
    // Here we get IObservable<bool>,
    // representing the current execution 
    // state of the command.
    .Select(executing => !executing)

Then, you can apply the Merge operator to the observables generated by WhenAnyObservable. To skip initial null values, if any, use either the Where operator or .Skip(1).

Artyom
  • 446
  • 4
  • 7
1

To give an example of the Subject<T> option described in the answer by Artyom, here is something inspired by Kent Boogaart's book p. 82:

var canRun = new BehaviorSubject<bool>(true);

Run = ReactiveCommand.Create...(..., canExecute: canRun);
Load = ReactiveCommand.Create...(..., canExecute: canRun);
Scan = ReactiveCommand.Create...(..., canExecute: canRun);

Observable.Merge(Run.IsExecuting, Load.IsExecuting, Scan.IsExecuting)
    .Select(executing => !executing).Subscribe(canRun);
Fabian
  • 1,100
  • 11
  • 14