Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you update the CanExecute value after the ReactiveCommand has been declared

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?

like image 667
hackintosh Avatar asked Oct 25 '25 15:10

hackintosh


2 Answers

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).

like image 51
Artyom Avatar answered Oct 28 '25 04:10

Artyom


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);
like image 24
Fabian Avatar answered Oct 28 '25 02:10

Fabian