0

我正在开发一个 WPF .NET 5 应用程序,该应用程序需要使用按钮命令来处理更长的任务。在任务完成之前,应禁用该按钮。我正在使用 Microsoft.Toolkit.Mvvm 的 RelayCommand:
BtnCmd = new RelayCommand(DoSomething, CanDoSomething);
DoSomething 方法所做的第一件事就是将返回值设为CanDoSomethingfalse。这可以防止 DoSomething 再次被执行,但它在 Button 上是不可见的。
在研究时,我发现这确实是https://github.com/CommunityToolkit/MVVM-Samples/issues/41上的情况:

“Sergio0694 于 2021 年 3 月 28 日发表评论”:“没错,默认情况下 RelayCommand 不会自动更新其在 WPF 上的视觉状态......”。

他推荐的解决方案是使用:https ://gist.github.com/Sergio0694/130bc344e552e501563546454bd4e62a和

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

我的 DoSomething 方法如下所示:

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

它将给出所需的视觉效果,但仅在线:只有PropertyCheckedByCanDoSomething = false;PropertyCheckedByCanDoSomething = true;单击应用程序或进行窗口切换后才能看到效果。
我怎样才能解决这个问题?
非常感谢任何支持。

4

1 回答 1

1

在不知道所有绑定等情况的情况下,我可能会通过子类化您的中继命令来接近它,例如

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;
        }

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

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

        public void Execute(object cmdParm)
        {
            if (!_doingWithCallback)
                _execute(cmdParm);
            else
                Execute2(cmdParm);
        }

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

        private bool _isMyTaskRunning = false;
        public bool IsMyTaskRunning
        { get { return _isMyTaskRunning; } }

        private bool _doingWithCallback;
        private readonly Action<object, Action> _executeWithCallback;

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

        public MyRelayCommand(Action<object, Action> executeWithCallback, Func<object, bool> canExecute)
        {
            // new flag, so when the default "Execute" method is called, it can then redirect to
            // calling the Execute2() method that checks to prevent the double-click and then
            // calls your function with the additional parameter of the action method to call upon completion.
            _doingWithCallback = true;
            _executeWithCallback = executeWithCallback;
            _canExecute = canExecute;
        }

        public void Execute2(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

            // NOW, call your execute function that accepts TWO parameters.
            // the first is whatever parameter MAY come from the button click itself.
            // the SECOND parameter will be to accept MY ACTION HERE to reset the flag when finished
            System.Threading.Tasks.Task.Run(() => _executeWithCallback(cmdParm, ButtonTaskIsComplete));
        }

        public void ButtonTaskIsComplete()
        {
            _isMyTaskRunning = false;
            System.Windows.Application.Current.Dispatcher.Invoke(() => { RaiseCanExecuteChanged(); });
        }
    }
}

可能不是一个完美的选择,但可能会为您提供一个可能的包装解决方案。

这是一个示例实现,可在您现有的表单区域中调用它。

private MyRelayCommand _myFormButton;
public MyRelayCommand MyFormButton
{ get { return _myFormButton ?? ( _myFormButton = new MyRelayCommand( YourFormMethod )); } }

public void YourFormMethod(object cmdParm, System.Action doThisWhenFinished)
{
    MessageBox.Show("Something from after the click event of the button");

    // NOW, the callback function so the button re-enables itself once finished.
    doThisWhenFinished();
}
于 2022-02-01T22:15:05.073 回答