Binding to ReactiveCommand.IsExecuting
Asked Answered
C

2

9

I'm would like to know the recommended way to bind to ReactiveCommand's IsExecuting.

The problem is the initial command execution (started at the end of the constructor) is not updating the WPF control using IsLoading as a binding, although subsequent calls work as expected.

Update 2 Add test binding code

This shows the adorner content when IsLoading is true

<ac:AdornedControl IsAdornerVisible="{Binding IsLoading}">
    <ac:AdornedControl.AdornerContent>
        <controls1:LoadingAdornerContent/>
    </ac:AdornedControl.AdornerContent>
    <fluent:ComboBox
        ItemsSource="{Binding Content, Mode=OneWay}"
        DisplayMemberPath="Name"
        SelectedValuePath="ContentId"
        SelectedValue="{Binding SelectedContentId}"
        IsSynchronizedWithCurrentItem="True"
    />
</ac:AdornedControl>

Update

I found this: https://github.com/reactiveui/rxui-design-guidelines

and figured I should be able to do something like:

this._isLoading = this.WhenAnyValue(x => x.LoadCommand.IsExecuting)
    .ToProperty(this, x => x.IsLoading);

but it gives the compilation error:

The type arguments for method 'ReactiveUI.OAPHCreationHelperMixin.ToProperty< TObj,TRet>(System.IObservable< TRet>, TObj, System.Linq.Expressions.Expression< System.Func< TObj,TRet>>, TRet, System.Reactive.Concurrency.IScheduler)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

I also tried:

this._isLoading = this.WhenAnyValue(x => x.LoadCommand.IsExecuting)
    .ToProperty<TheViewModel, bool>(this, x => x.IsLoading);

but get the compilation error:

'System.IObservable< System.IObservable< bool >>' does not contain a definition for 'ToProperty' and the best extension method overload 'ReactiveUI.OAPHCreationHelperMixin.ToProperty< TObj,TRet>(System.IObservable< TRet>, TObj, System.Linq.Expressions.Expression< System.Func< TObj,TRet>>, TRet, System.Reactive.Concurrency.IScheduler)' has some invalid arguments

and

Instance argument: cannot convert from 'System.IObservable>' to 'System.IObservable'

Original Below

The code listed at the end of my post works for the initial bind by accessing the IsLoading property and it sounds like that kicks off a subscription. But from further reading it seems I should be using WhenAny and I can't seem to figure out what has been put in front of my nose:

ToProperty and BindTo - Get initial value without Subscribing

Adding:

this.WhenAnyValue(x => x.LoadCommand.IsExecuting);

also works, but is there a better way?

I was thinking removing the ObservableAsPropertyHelper as it doesn't seem to be doing much for me and making IsLoading a normal property like:

private bool _isLoading;

public bool IsLoading
{
    get { return _isLoading; }
    set { this.RaiseAndSetIfChanged(ref _isLoading, value); }
}

And doing something like the following, but it doesn't compile because it is trying to assign a IObservable< bool> to a bool:

this.WhenAnyValue(x => x.LoadCommand.IsExecuting)
   .Subscribe(x => IsLoading = x);

Current code:

private readonly ObservableAsPropertyHelper<bool> _isLoading;

public bool IsLoading
{
   get { return _isLoading.Value; }
}

LoadCommand = ReactiveCommand.CreateAsyncTask(async _ =>
{
    //go do command stuff like fetch data from a database
}

LoadCommand.IsExecuting.ToProperty(this, x => x.IsLoading, out _isLoading);

//works if I have this line
var startSubscription = IsLoading;

LoadCommand.ExecuteAsyncTask();
Curricle answered 31/8, 2014 at 8:43 Comment(0)
D
10

and figured I should be able to do something like:

You've got the right idea, but the syntax is a bit off, try:

this.LoadCommand.IsExecuting
    .ToProperty(this, x => x.IsLoading, out _isLoading);

If you were to do this with objects that can change (i.e. you've got a long expression), there's a special method called WhenAnyObservable that you use instead of WhenAnyValue:

this.WhenAnyObservable(x => x.SomeObjectThatMightBeReplaced.IsExecuting)
    .ToProperty(this, x => x.IsLoading, out _isLoading);
Dasheen answered 2/9, 2014 at 19:11 Comment(1)
I think that is what I had minus the this. at the start of the call. It still doesn't seem to show the initial "loading" on my bound control unless I put in var startSubscription = IsLoading; after the ToProperty call. Is that how I should keep it as suggested by jomtois? I check this by adding a 10 second delay to my async call and don't see my "loading" visual on the control unless I add it. The binding is pretty simple, but I will add it to the question.Curricle
P
4

I have run into this before and I think what you are experiencing lies here.

ToProperty / OAPH changes

  • ObservableAsPropertyHelper no longer is itself an IObservable, use WhenAny to observe it.

  • ObservableAsPropertyHelper now lazily Subscribes to the source only when the Value is read for the first time. This significantly improves performance and memory usage, but at the cost of some "Why doesn't my test work??" confusion. If you find that your ToProperty "isn't working", this may be why.

It is lazy, so you must subscribe to it (i.e. request a value from the property if using OAPH) for it to work. That is why you notice that your var startSubscription = IsLoading; 'fixes' the issue.

Knowing that made it easier for me to determine whether or not this was even an issue, or just something to keep in mind during my unit tests, knowing that in my application these properties would be bound to and hence subscribed to, making it moot in practice. You know, the whole "tree falling in the forest with no one there to hear it" idea.

I think you should stick with the ToProperty that you have, that seems the way to go IMHO.

Perform answered 2/9, 2014 at 13:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.