CommunityToolkit / dotnet

.NET Community Toolkit is a collection of helpers and APIs that work for all .NET developers and are agnostic of any specific UI platform. The toolkit is maintained and published by Microsoft, and part of the .NET Foundation.
https://docs.microsoft.com/dotnet/communitytoolkit/?WT.mc_id=dotnet-0000-bramin
Other
2.99k stars 294 forks source link

Allow `NotifyCanExecuteChanged` on a background thread #536

Open adamhewitt627 opened 1 year ago

adamhewitt627 commented 1 year ago

Overview

This might be a better request for WPF, but (as I don't see that changing) I thought I would suggest it here.

Problem: as a developer, I want to use async code and be able to change properties and update command states without thinking about a Dispatcher.

  1. WPF Binding handles property changes regardless of the thread, so that's fine.
  2. However, the handler of CanExecuteChanged throws if you raise the event on a background thread.

This requires coupling your ViewModel to a dispatcher (or an IDispatcher) and limits the value of source generators like [NotifyCanExecuteChangedFor(nameof(MyCommand))]. This becomes even more complex on platforms (e.g. UWP) where each window might have separate dispatchers.

On a previous project, I ended up implementing my own ICommand that would capture the SynchronizationContext during event registration and post back to it when notifying. (x:Bind on UWP has a similar failure for INotifyPropertyChanged.)

I must have done the ICommand inside that closed-source project rather than my library, but the approach I took is illustrated here: https://github.com/adamhewitt627/SynchronizedEvents

API breakdown

No new API is proposed here, I would like it to just be a behavior change.

Usage example

The usage would be the same: MyCommand.NotifyCanExecuteChanged.

Breaking change?

I'm not sure

Alternatives

The developer can inject a dispatcher into a view model and marshal to the UI thread on their own.

Additional context

No response

Help us help you

No, just wanted to propose this

mikechristiansenvae commented 1 year ago

Suggestion...

Perhaps if there was an interface:

public interface ISynchronizationContextProvider
{
    SynchronizationContext SynchronizationContext { get; }
}

Then a constructor could be added to RelayCommand that would accept an instance of ISynchronizationContextProvider.