CanExecute() returns true and button is still disa

2019-05-14 23:31发布

问题:

I have a BottomAppBar.AppBarButton in a Windows Phone specific page, that is bound to a relay command. The code, binding and viewmodel implementation have all been used in basically the same way on other pages in the project and works exactly as expected there.

The issue in this particular scenario is that the button remains disabled even after raising the .RaiseCanExecuteChanged() method, and the CanExecute() returns true.

I originally thought that it might be due to excess calls to manually raising the notification with property changes, so have tightened that part of my code so that the method is only raised as needed, and when it is needed to change the button's status. Even still, the button remains disabled despite CanExecute() returning true. If I comment out all the checks in CanExecute() and default to true, the button is enabled as expected, and when tapped fires the expected Execute() function, so it appears that the initialization of the RelayCommand is ok. If I then let the checks back in, and run step through each time CanExecute() is fired, when it returns true, the button doesn't become enabled.

Any ideas? For what its worth, I've added code below, but I don't think that is the cause.

RelayCommand class is the standard class that comes with the HubApp in VS, so I will omit that code.

last line of the viewmodel constructor is the RelayCommand;

AddStrikeTeamCommand = new RelayCommand(async() => await AddStrikeTeam(), CanAddStrikeTeam);

Can Add is;

private bool CanAddStrikeTeam()
{
    //if (NameWorking == string.Empty) return false;
    //if (FactionWorking == string.Empty) return false;
    //if (PointsLimitWorking < 1) return false;
    //if (!IsValidTeamWorking) return false;
    return true;
}

And finally, the button binding

<AppBarButton x:Name="accept" Icon="Accept" Label="accept"
              Command="{Binding AddStrikeTeamCommand}"/>

回答1:

I know this is a late answer, but this post is being linked in another question so I feel like I should post a better code sample.

Jerry's answer is most likely correct that the problem is RaiseCanExecuteChanged is not raised automatically in that implementation of ICommand, however the code sample provided re-introduces the exact same problem that caused it to be taken out in the first place - it raises CanExecuteChanged whenever any property changes, resulting in CanExecute being called far more than necessary.

The PropertyChanged event handler should include a check and only raise CanExecuteChanged if the property changed is one that is used in CanExecute.

Since your CanExecute is

private bool CanAddStrikeTeam()
{
    if (NameWorking == string.Empty) return false;
    if (FactionWorking == string.Empty) return false;
    if (PointsLimitWorking < 1) return false;
    if (!IsValidTeamWorking) return false;
    return true;
}

then the event handler needs to only raise CanExecuteChanged if one of those for properties changes

this.PropertyChanged += (s, e) => 
{
    switch (e.PropertyName)
    {
        case "NameWorking":
        case "FactionWorking":
        case "PointsLimitWorking":
        case "IsValidTeamWorking":
            AddStrikeTeamCommand.RaiseCanExecuteChanged();
            break;
    }
}


回答2:

I'd probably bet your problem has to do with RaiseCanExecuteChanged(). This is especially true if you are used to WPF and how it automatically refreshes CanExe4cute for you. Check out this Delegate Command implementation:

http://codepaste.net/ho9s5a

The ICommand interface defines the event CanExecuteChanged which instructs the button (or UI Element) to refresh its Enabled status. In WPF, this was raised constantly by the static, command manager. This does not exist in WinRT. In WPF, because it was raised so frequently, WPF developers had to be careful that CanExecute() was not an expensive operation. WinRT provides for expensive tests, but consequently requires the developer to raise the event manually. I hope this makes sense.

One way I handle this is:

DelegateCommand _SaveCommand = null;
public DelegateCommand SaveCommand
{
    get
    {
        if (_SaveCommand != null)
            return _SaveCommand;
        _SaveCommand = new DelegateCommand
        (
            () =>
            {
                // TODO
            }, 
            () => true
        );
        this.PropertyChanged += (s, e) => _SaveCommand.RaiseCanExecuteChanged();
        return _SaveCommand;
    }
}

This basically refreshes the CanExecute based on the change of any property in (usually in my View Model). This is not sufficient if you have potential changes in models that you have in an ObservableCollection, but it's a nice start to the whole thing.

There's a possibility that you don't have this problem at all. And that you are calling to raise the event, it is returning true, and is still not working. If that is what is happening, it just has to be your code because Commands are working for thousands of apps. But, if you want to send me your code, I'll take a look.

Best of luck!



回答3:

If you are using Mvvm Light, make sure you are including the GalaSoft.MvvmLight.CommandWpf namespace instead of the GalaSoft.MvvmLight.Command namespace. (See the second answer on MVVM RelayCommand CanExecute)