为什么我的带有 ICommand 绑定的按钮在单击时不会立即显示为禁用

本文关键字:显示 单击 我的 ICommand 绑定 按钮 为什么 | 更新日期: 2023-09-27 18:36:14

我有一个简单的WPF程序,带有ICommand。 我发现该按钮没有像我预期的那样启用/禁用。 我可以用一个人为的代码示例来最好地说明这一点:

class Reload : ICommand
{
    private readonly BackgroundWorker _bworker = new BackgroundWorker();
    public Reload()
    {
        this._isExecuting = false;
        this._bworker.DoWork += this._bworker_DoWork;
        this._bworker.RunWorkerCompleted += this._bworker_RunWorkerCompleted;
    }
    public event EventHandler CanExecuteChanged;
    private void OnCanExecuteChanged()
    {
        if (this.CanExecuteChanged != null)
            this.CanExecuteChanged(this, EventArgs.Empty);
    }
    private bool _isExecuting;
    private void SetIsExecuting(bool isExecuting)
    {
        this._isExecuting = isExecuting;
        this.OnCanExecuteChanged();
    }
    public bool CanExecute(object parameter)
    {
        return !this._isExecuting;
    }
    public void Execute(object parameter)
    {
        //this does not update the GUI immediately
        this.SetIsExecuting(true);
        //This line doesn't fix my problem
        CommandManager.InvalidateRequerySuggested();
        //during this wait, button appears "clicked"
        Thread.Sleep(TimeSpan.FromSeconds(2)); //simulate first calculation
        this._bworker.RunWorkerAsync();
    }
    private void _bworker_DoWork(object sender, DoWorkEventArgs e)
    {
        //during this wait, button appears disabled
        Thread.Sleep(TimeSpan.FromSeconds(2)); //simulate second calculation
    }
    private void _bworker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        //this updates immediately
        this.SetIsExecuting(false);
    }
}

Execute(object) 方法中,我触发 CanExecuteChanged 事件的方式将导致CanExecute(object)返回 false。 在该调用之后,我希望该按钮立即被禁用,但它直到调用RunWorkerAsync()和第二次模拟计算之间的某个时间点才会被禁用。

在后台工作线程的 RunWorkerCompleted(...) 事件处理程序中,我再次触发 CanExecuteChanged 事件,但这次的方式将导致CanExecuteChanged(object)返回 true。 此调用后,该按钮将立即启用。

为什么当我触发CanExecuteChanged事件时,按钮没有立即显示为禁用?

注意 #1:第一个模拟计算表示我拥有的应该在主 GUI 线程上运行的代码。 如果我删除此调用,该按钮将按预期运行。

注意 #2:我已经阅读了有关使用 CommandManager.InvalidateRequerySuggested() 强制代码调用 CanExecute(object) 方法的信息。 我已经在我的评论中表明这对我不起作用。 考虑到我称OnCanExecuteChanged(...),我认为这个建议无论如何都是多余的。

为什么我的带有 ICommand 绑定的按钮在单击时不会立即显示为禁用

正确的解决方案是你已经找到的解决方案,将第一个长时间运行的操作移出 UI 线程。

但是,如果不能执行此操作,则问题在于没有给 UI 运行其绑定和更新状态的机会。它可能会在后台工作线程启动后立即更新(因为控件是从函数返回的)。

您可以利用 async/await 和 Task.Delay 来腾出一些时间来更新 UI:

public async void Execute(object parameter)
{
    //this does not update the GUI immediately
    this.SetIsExecuting(true);
    //Delays this function executing, gives the UI a chance to pick up the changes
    await Task.Delay(500);
    //during this wait, button appears "clicked"
    Thread.Sleep(TimeSpan.FromSeconds(2)); //simulate first calculation
    this._bworker.RunWorkerAsync();
}

Async/Await 允许您异步执行操作,并等待它完成,同时允许当前线程继续执行(在当前方法调用之外)。解释所有技术细节并不是很容易,请参阅链接以获取更多信息。

我会等待至少 20 毫秒,可能 50 毫秒。 显然,像这样延迟并不是最干净的解决方案,但是如果不删除Sleep(或将其表示的代码移出UI线程),您的选择非常有限。