Почему моя кнопка с привязкой ICommand сразу не отображается отключенной при нажатии?

У меня есть простая программа 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) вернуть ложь. После этого звонка я ожидаю, что кнопка будет немедленно отключена, но она не станет отключенной до некоторой точки между звонком RunWorkerAsync() и второй смоделированный расчет.

На заднем плане рабочий RunWorkerCompleted(...) обработчик событий, я снова вызываю CanExecuteChanged событие, но на этот раз таким образом, что приведет к CanExecuteChanged(object) вернуть истину. После этого звонка кнопка сразу становится активной.

Почему кнопка не отображается сразу как отключенная, когда я запускаю CanExecuteChanged событие?

Примечание #1: что первый смоделированный расчет представляет мой код, который должен быть запущен в главном потоке графического интерфейса. Если я уберу этот вызов, кнопка будет работать так, как я ожидал.

Примечание № 2: я читал об использовании CommandManager.InvalidateRequerySuggested() заставить код вызвать CanExecute(object) метод. Я показал в моих комментариях, что это не работает для меня. Учитывая, что я звоню OnCanExecuteChanged(...) Я думаю, что это предложение в любом случае является излишним.

1 ответ

Решение

Правильное решение - это то, что вы уже нашли, уберите первую длительную операцию из потока пользовательского интерфейса.

Однако, если вы не можете этого сделать, проблема в том, что вы не предоставляете интерфейсу пользователя возможность запускать привязку и обновлять состояние. Вероятно, он обновляется, как только запускается фоновый работник (потому что управление возвращается из вашей функции).

Вы могли бы воспользоваться async/await и Task.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 (или перемещая код, который он представляет, из потока пользовательского интерфейса) ваши возможности довольно ограничены.

Другие вопросы по тегам