简体   繁体   中英

CanExecute works only partial

I am working on a WPF .NET 5 application that needs to handle a longer task using a button command. Until the task is done, the button should be disabled. I am using the RelayCommand from Microsoft.Toolkit.Mvvm:
BtnCmd = new RelayCommand(DoSomething, CanDoSomething);
The first thing the DoSomething method does is make the return value of CanDoSomething false. This prevents DoSomething from being executed again, but it is not visually visible on the Button.
While researching, I came across that this is indeed the case on https://github.com/CommunityToolkit/MVVM-Samples/issues/41 :

"Sergio0694 commented on Mar 28, 2021" : "That is correct, by default RelayCommand will not automatically update its visual state on WPF...".

The solution he recommends is using: https://gist.github.com/Sergio0694/130bc344e552e501563546454bd4e62a and

<button xmlns:input="using:Microsoft.Toolkit.Mvvm.Wpf.Input"    
        Command="{Binding Command}"
        input:RelayCommandExtensions.IsCommandUpdateEnabled="True"/> 

My DoSomething Mehod looks like this:

private async void DoSomething()
{
    PropertyCheckedByCanDoSomething = false;
    await Task.Delay(1000);
    PropertyCheckedByCanDoSomething = true;
}

It will give the desired visual effect, but only on the line: PropertyCheckedByCanDoSomething = false; With PropertyCheckedByCanDoSomething = true; the effect is only visible after clicking into the application or doing a window switch.
How can I fix this?
Thanks a lot for any support.

Without knowing how all your bindings and such are going, with I might approach it by subclassing your relay command, something like

using System;
using System.Windows.Input;
namespace MyTestApp
{
    public class MyRelayCommand : ICommand
    {
        private readonly Action<object> _execute;
        private readonly Func<object, bool> _canExecute;

        public MyRelayCommand(Action<object> execute) : this(execute, CanAlwaysExecute)
        { }

        public MyRelayCommand(Action<object> execute, Func<object, bool> canExecute)
        {
            // Lamda expression to execute each respectively
            _execute = execute;
            _canExecute = canExecute;
        }

        // The CanExecuteChanged event handler is required from the ICommand interface
        public event EventHandler CanExecuteChanged;
        public void RaiseCanExecuteChanged()
        {
            if (CanExecuteChanged != null)
                CanExecuteChanged(this, new EventArgs());
        }

        public bool CanExecute(object cmdParm)
        { return _isMyTaskRunning && _canExecute(cmdParm); }

        public static bool CanAlwaysExecute(object cmdParm)
        { return true; }

        public void Execute(object cmdParm)
        {
            // prevent double action if running vs not
            if (_isMyTaskRunning)
                return;

            // flag it to prevent double action
            _isMyTaskRunning = true;

            // trigger raising the CanExecute changed which will update the user interface
            RaiseCanExecuteChanged();
    
            // turn off when done, but if being done from a "task()" process, 
            // you probably want to have a return function be called when the 
            // TASK is finished to re-enable the button... maybe like
            System.Threading.Tasks.Task.Run(() => _execute)
                                    .ContinueWith(task => { ButtonTaskIsComplete(); } );
        }

        private bool _isMyTaskRunning = false;
        public void ButtonTaskIsComplete()
        {
            _isMyTaskRunning = false;
            RaiseCanExecuteChanged();
        }


    }
}

May not be a perfect fit, but might offer a possible wrapper solution for you.

I had a similar problem with that RelayCommandExtension you refered to.

There exists a Github issue regarding this. The following solution was posted there:

In RelayCommandExtensions.cs add the following line:

notifiers.Add(obj, notifier);

to the OnIsCommandUpdateEnabledChanged(...) method so that it looks like that:

// Ensure a notifier is enabled so that we can monitor for changes to the command
// property in the future. This is also needed in case this attached property is
// set before the binding engine has actually resolved the target command instance.
if (!notifiers.TryGetValue(obj, out _))
{
    PropertyChangeNotifier notifier = new(obj, nameof(ICommandSource.Command));

    notifier.PropertyChanged += (_, _) => ToggleIsCommandUpdateEnabled(obj);
    
    notifiers.Add(obj, notifier);
}

This is doing the trick for me.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM