1

を含む単純な 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);
    }
}

このメソッドでは、 false を返すようにイベントExecute(object)をトリガーします。その呼び出しの後、ボタンがすぐに無効になることを期待していますが、呼び出しと2番目のシミュレートされた計算の間のある時点まで無効になりません。CanExecuteChangedCanExecute(object)RunWorkerAsync()

バックグラウンド ワーカーのRunWorkerCompleted(...)イベント ハンドラーで、再びCanExecuteChangedイベントをトリガーしますが、今回はCanExecuteChanged(object)true を返すようにします。この呼び出しの後、ボタンはすぐに有効になります。

CanExecuteChangedイベント をトリガーしても、ボタンがすぐに無効として表示されないのはなぜですか?

注 #1: 最初のシミュレートされた計算は、メインの GUI スレッドで実行する必要があるコードを表しています。この呼び出しを削除すると、ボタンは期待どおりに機能します。

CommandManager.InvalidateRequerySuggested()注 #2:コードにメソッドを強制的に呼び出すためのusing について読みましたCanExecute(object)。コメントで、これがうまくいかないことを示しました。を呼び出すOnCanExecuteChanged(...)ことを考えると、その提案はとにかく冗長だと思います。

4

1 に答える 1

2

適切な解決策は、既に見つけたものです。最初の長時間実行される操作を UI スレッドから移動します。

ただし、それができない場合、問題は、UI にバインディングを実行して状態を更新する機会を与えていないことです。バックグラウンドワーカーが開始するとすぐに更新される可能性があります(制御が関数から返されるため)。

async/awaitTask.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 スレッドから移動) しない限り、オプションはかなり制限されます。

于 2015-01-05T20:28:59.117 に答える