单击时,为什么带有ICommand绑定的按钮不会立即显示为禁用状态?

时间:2015-01-05 20:23:44

标签: c# wpf icommand canexecute

我有一个简单的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返回false的方式触发CanExecute(object)事件。在那次通话之后,我希望按钮立即被禁用,但是在调用RunWorkerAsync()和第二次模拟计算之间的某个点之前它不会被禁用。

在后台工作程序的RunWorkerCompleted(...)事件处理程序中,我再次触发CanExecuteChanged事件,但这次会导致CanExecuteChanged(object)返回true。通话结束后,该按钮立即启用。

为什么在触发CanExecuteChanged事件时按钮不会立即显示为已禁用?

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

注意#2:我读过有关使用CommandManager.InvalidateRequerySuggested()强制代码调用CanExecute(object)方法的内容。我在评论中表明这对我不起作用。考虑到我打电话给OnCanExecuteChanged(...),我认为这个建议无论如何都是多余的。

1 个答案:

答案 0 :(得分:2)

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

但是,如果你不能这样做,问题是你没有给UI一个机会来运行它的绑定并更新状态。它可能会在后台工作程序启动时立即更新(因为控制是从您的函数返回的)。

您可以利用async/awaitTask.Delay放弃更新用户界面的时间:

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线程的代码),你的选择就非常有限。