Giter Club home page Giter Club logo

Comments (7)

tyrgin avatar tyrgin commented on August 16, 2024 1

@brminnick thank you very much for your help with code and explanation! :)
I really appreciate it!)

from asyncawaitbestpractices.

brminnick avatar brminnick commented on August 16, 2024

Hi @tyrgin!

Can you provide a reproduction sample where CanExecuteChanged does not fire?

It does not automatically fire when the value of canExecute changes; you must call RaiseCanExecuteChanged after changing the value canExecute to fire CanExecuteChanged.

Here is a sample from my Unit Tests demonstrating it:

[Test]
public void AsyncCommand_CanExecuteChanged_Test()
{
//Arrange
bool canCommandExecute = false;
bool didCanExecuteChangeFire = false;
AsyncCommand command = new AsyncCommand(NoParameterTask, commandCanExecute);
command.CanExecuteChanged += handleCanExecuteChanged;
void handleCanExecuteChanged(object? sender, EventArgs e) => didCanExecuteChangeFire = true;
bool commandCanExecute(object? parameter) => canCommandExecute;
Assert.False(command.CanExecute(null));
//Act
canCommandExecute = true;
//Assert
Assert.True(command.CanExecute(null));
Assert.False(didCanExecuteChangeFire);
//Act
command.RaiseCanExecuteChanged();
//Assert
Assert.True(didCanExecuteChangeFire);
Assert.True(command.CanExecute(null));
}

from asyncawaitbestpractices.

tyrgin avatar tyrgin commented on August 16, 2024

Hi @brminnick
Thank you for your answer!
Basically what I have is:

public class ViewModelCalculate : INotifyPropertyChanged
    {
        private bool isBusy;
        private ObservableCollection<BusSection> busSections;
        public ViewModelCalculate(ObservableCollection<BusSection> busSections)
        {
            this.busSections = busSections;          
        }
        #region Command
        private IAsyncCommand calculateAsync;
        public IAsyncCommand CalculateAsyncCommand => calculateAsync ??
             new AsyncCommand(ExecuteCalculateAsync, CanExecuteCalculate);
        private async Task ExecuteCalculateAsync( )   { ... }
           
        private bool CanExecuteCalculate( ) => busSections.Count() > 0 && !IsBusy;
        #endregion

        #region INotifyPropertyChanged
        public bool IsBusy { get => isBusy; set { isBusy = value; OnPropertyChanged("IsBusy"); } }
        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged([CallerMemberName] string prop = "")
        {   PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));    }
        #endregion
    }

And in other ViewModel I update the busSections collection.
As I said I tried to subscribe CalculateAsync.RaiseCanExecuteChanged() on busSections.CollectionChanged in ViewModelCalculate ctor, but that also gave nothing - RaiseCanExecuteChanged() executed but it the event CanExecuteChanged was always null in this part

public void RaiseCanExecuteChanged()
{
    CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}

Where I should call RaiseCanExecuteChanged()?
Actually I've found other hacky way:

///the VM where i update BusSections collection
public class OtherViewModel
{
    private ObservableCollection<BusSection> busSections;
    public ObservableCollection<BusSection> BusSections { get=>busSections; set { busSections = 
        value; OnPropertyChanged(); }}
    public ViewModelCalculate VmCalculate {get;set;}
    void UpdateBusSection ()
    {
        ///add item to BusSections
        ///with next line works good
        OnPropertyChanged("VmCalculate");
    }
}

Sorry for messy explanation, i've tried a few options, eventually came to OnPropertyChanged("VmCalculate") without calling RaiseCanExecuteChanged(), but i believe there is should be right way.

from asyncawaitbestpractices.

tyrgin avatar tyrgin commented on August 16, 2024

@brminnick it has occurred to me today that I RaiseCanExecuteChanged always for a new instance of Command...
so I changed

public ViewModelCalculate(ObservableCollection<BusSection> busSections)
{
    busSections.CollectionChanged += (sender,e)=> RaiseChange();
}
private IAsyncCommand<string> calculateAsync;
public IAsyncCommand<string> CalculateAsyncCommand => calculateAsync ??
             new AsyncCommand(x=>ExecuteCalculateAsync(x), x=>CanExecuteCalculate(x));

to this

private bool busSectionAny;
private IAsyncCommand<string> calculateAsync;
public ViewModelCalculate(ObservableCollection<BusSection> busSections)
{
    busSections.CollectionChanged += (sender,e)=> RaiseChange();
}
void RaiseChange()
{
    if(busSections.Any() ^ busSectionAny)
    {
        busSectionAny = busSectionAny?false:true;
        ((AsyncCommand<string>)CalculateAsyncCommand).RaiseCanExecuteChanged();
    }
}
public IAsyncCommand<string> CalculateAsyncCommand 
{
    get
    {
        calculateAsync ??= new AsyncCommand<string>(x => ExecuteCalculateAsync(x), x => CanExecuteCalculate(x));
        return calculateAsync;                
    }
}

And now it works without hacky OnPropertyChanged :)
Is this resolve ok? Or I should change something?

from asyncawaitbestpractices.

tyrgin avatar tyrgin commented on August 16, 2024

How do you think, is it ok to invoke RaiseCanExecuteChanged from different ViewModel?

from asyncawaitbestpractices.

brminnick avatar brminnick commented on August 16, 2024

@tyrgin

For your example, you need to call CalculateAsyncCommand.RaiseCanExecuteChanged() every time the value of bool CanExecuteCalculate() changes.

Because bool CanExecuteCalculate() references both busSections.Count() and IsBusy, you need to call CalculateAsyncCommand.RaiseCanExecuteChanged() every time the value of busSections.Count() and IsBusy changes.

I also fixed a bug with your initialization of CalculateAsyncCommand, and I changed OnPropertyChanged("IsBusy") to OnPropertyChanged().

When calling OnPropertyChanged(), you don't need to pass in the property name because OnPropertyChanged uses [CallerMemberName] which will automatically pass in "IsBusy" because IsBusy is the "CallerMemberName", ie the name of the Property calling OnPropertyChanged is "IsBusy" and thus the value of string prop will automatically be "IsBusy".

Here's the updated code:

public class ViewModelCalculate : INotifyPropertyChanged
{
        private bool isBusy;
        private IAsyncCommand calculateAsync;
        private ObservableCollection<BusSection> busSections;

        public ViewModelCalculate(ObservableCollection<BusSection> busSections)
        {
            this.busSections = busSections;          
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public IAsyncCommand CalculateAsyncCommand => calculateAsync ??
             (calculateAsync = new AsyncCommand(ExecuteCalculateAsync, CanExecuteCalculate));
        
        public bool IsBusy 
        { 
            get => isBusy; 
            set
            {
                isBusy = value; 
                OnPropertyChanged();
                CalculateAsyncCommand.RaiseCanExecuteChanged();
            }
        }

        private async Task ExecuteCalculateAsync( )  
        {
            // Whenever you modify `busSections`, call `CalculateAsyncCommand.RaiseCanExecuteChanged()`
            // Here's an example:
            //     busSections.Clear();
            //     CalculateAsyncCommand.RaiseCanExecuteChanged();
            // Here's another example:
            //     busSections.Add(new BusStation());
            //     CalculateAsyncCommand.RaiseCanExecuteChanged();
        }
           
        private bool CanExecuteCalculate( ) => busSections.Count() > 0 && !IsBusy;

        public void OnPropertyChanged([CallerMemberName] string prop = "") =>
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}

from asyncawaitbestpractices.

brminnick avatar brminnick commented on August 16, 2024

And thanks so much for the feedback!

I've updated the docs to include an example of CanExecuteChanged:

https://github.com/brminnick/AsyncAwaitBestPractices/blob/master/README.md#asyncawaitbestpracticesmvvm-2

from asyncawaitbestpractices.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.