【问题标题】:How can I update a command's "CanExecute" value from a different thread?如何从不同的线程更新命令的“CanExecute”值?
【发布时间】:2013-02-25 20:09:42
【问题描述】:

在我的应用程序中,我有一个命令,我只希望用户能够在它尚未运行时触发它。有问题的命令绑定到 WPF 按钮,这意味着如果 CanExecute 为 false,它会自动禁用该按钮。到目前为止一切顺利。

不幸的是,命令执行的操作是一个长时间运行的操作,所以它需要发生在不同的线程上。我不认为这会是一个问题......但它似乎是。

我提取了一个可以显示问题的最小样本。如果绑定到一个按钮(通过 LocalCommands.Problem 静态引用),该按钮将根据需要被禁用。当工作线程尝试更新 CanExecute 时,将从 System.Windows.Controls.Primitives.ButtonBase 内部抛出 InvalidOperationException。

解决这个问题最合适的方法是什么?

下面的示例命令代码:

using System;
using System.Threading;
using System.Windows.Input;

namespace InvalidOperationDemo
{
    static class LocalCommands
    {
        public static ProblemCommand Problem = new ProblemCommand();
    }

    class ProblemCommand : ICommand
    {
        private bool currentlyRunning = false;
        private AutoResetEvent synchronize = new AutoResetEvent(false);

        public bool CanExecute(object parameter)
        {
            return !CurrentlyRunning;
        }

        public void Execute(object parameter)
        {
            CurrentlyRunning = true;

            ThreadPool.QueueUserWorkItem(ShowProblem);
        }

        private void ShowProblem(object state)
        {
            // Do some work here. When we're done, set CurrentlyRunning back to false.
            // To simulate the problem, wait on the never-set synchronization object.
            synchronize.WaitOne(500);

            CurrentlyRunning = false;
        }

        public bool CurrentlyRunning
        {
            get { return currentlyRunning; }
            private set
            {
                if (currentlyRunning == value) return;

                currentlyRunning = value;

                var onCanExecuteChanged = CanExecuteChanged;
                if (onCanExecuteChanged != null)
                {
                    try
                    {
                        onCanExecuteChanged(this, EventArgs.Empty);
                    }
                    catch (Exception e)
                    {
                        System.Windows.MessageBox.Show(e.Message, "Exception in event handling.");
                    }
                }
            }
        }

        public event EventHandler CanExecuteChanged;
    }
}

【问题讨论】:

    标签: c# wpf threadpool


    【解决方案1】:

    变化:

    onCanExecuteChanged(this, EventArgs.Empty);

    到:

    Application.Current.Dispatcher.BeginInvoke((Action)(onCanExecuteChanged(this, EventArgs.Empty)));
    

    编辑:

    原因是 WPF 正在侦听这些事件并尝试在 UI 元素中执行操作(即在 Button 中切换 IsEnabled),因此必须在 UI 线程中引发这些事件。

    【讨论】:

    • 虽然这无疑是可行的,但您能否解释一下为什么它是必要的?我(显然不正确)的假设是,与 UI 绑定相关的任何事件的任何处理程序都应该/已经在检查它们是否正在尝试在正确的线程上执行 UI 更新。
    • 是的,它检查了它是否在正确的线程上并抛出一个异常让你知道。
    • 我想我用错了@sixlettervariables 的措辞(感谢@HighCore 的澄清) - 我知道这个例外让我知道这个问题。我不明白为什么这与我认为将“启用”绑定到视图模型的属性的几乎相同的场景不同。我没有遇到任何类似的问题,它不是 CanExecuteChanged,而是导致更改的 OnPropertyChanged 事件。我担心的是到目前为止我很幸运,如果在绑定上下文中使用 OnPropertyChange 事件也只需要在 UI 线程上发生。
    • 绑定发生在调度程序的线程上,属性更改通知也是如此。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-15
    • 2010-10-18
    • 1970-01-01
    • 1970-01-01
    • 2011-02-28
    相关资源
    最近更新 更多