[英]Why does my button with an ICommand binding not immediately appear disabled when clicked?
I have a simple WPF program with an ICommand
. 我有一个带ICommand
的简单WPF程序。 I am finding that the button doesn't enable/disable as I would expect. 我发现按钮没有启用/禁用,正如我期望的那样。 I can illustrate this best with a contrived code example: 我可以用一个人为的代码示例来最好地说明这一点:
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);
}
}
In the Execute(object)
method, I trigger the CanExecuteChanged
event in a way that will cause CanExecute(object)
to return false. 在Execute(object)
方法中,我以导致CanExecute(object)
返回false的方式触发CanExecuteChanged
事件。 After that call, I expect the button to become disabled immediately, but it doesn't become disabled until some point between the call to RunWorkerAsync()
and the second simulated calculation. 在该调用之后,我希望该按钮会立即被禁用,但是直到调用RunWorkerAsync()
与第二个模拟计算之间的某个时刻它才会被禁用。
In the background worker's RunWorkerCompleted(...)
event handler, I again trigger the CanExecuteChanged
event, but this time in a way that will cause CanExecuteChanged(object)
to return true. 在后台工作者的RunWorkerCompleted(...)
事件处理程序中,我再次触发CanExecuteChanged
事件,但这一次将导致CanExecuteChanged(object)
返回true。 After this call, the button immediately becomes enabled. 通话后,该按钮将立即启用。
Why does the button not immediately appear as disabled when I trigger the CanExecuteChanged
event? 为什么触发CanExecuteChanged
事件时按钮没有立即显示为禁用状态?
Note #1: that the first simulated calculation represents code that I have that should run on the main GUI thread. 注意#1:第一个模拟计算代表我拥有的应在主GUI线程上运行的代码。 If I remove this call, the button acts as I would expect. 如果我删除此呼叫,该按钮将按我期望的那样工作。
Note #2: I've read about using CommandManager.InvalidateRequerySuggested()
to force the code to call the CanExecute(object)
method. 注意#2:我已经阅读了有关使用CommandManager.InvalidateRequerySuggested()
强制代码调用CanExecute(object)
方法的信息。 I've shown in my comments that this isn't working for me. 我在评论中显示这对我不起作用。 Considering that I call OnCanExecuteChanged(...)
, I think that that suggestion is redundant anyway. 考虑到我调用OnCanExecuteChanged(...)
,我认为该建议是多余的。
The right solution is the one you've already found, move the first long-running operation off of the UI thread. 正确的解决方案是您已经找到的解决方案,将第一个长时间运行的操作移出UI线程。
However, if you can't do that, the problem is that you aren't giving the UI a chance to run its binding and update the state. 但是,如果您不能这样做,那么问题就在于您没有给UI提供运行其绑定和更新状态的机会。 It probably updates as soon as the background worker starts (because control is returned from your function). 后台工作程序启动后,它可能会立即更新(因为从函数中返回了控件)。
You could take advantage of async/await
and Task.Delay to give up some time for the UI to update: 您可以利用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 allows you to execute an operation asynchronously, and wait for it to finish, while allowing the current thread to continue executing (outside of the current method call). 异步/等待允许您异步执行操作,并等待操作完成,同时允许当前线程继续执行(当前方法调用之外)。 Its not super easy to explain all the technical details, see the link for more info. 解释所有技术细节并非易事,请参阅链接以获取更多信息。
I would wait at least 20ms, and probably 50ms. 我至少要等待20毫秒,可能要等50毫秒。 Obviously delaying like this isn't the cleanest solution, but without removing the Sleep
(or moving the code it represents off the UI thread) your options are pretty limited. 显然,这样的延迟并不是最干净的解决方案,但是如果不删除Sleep
(或将其表示的代码移出UI线程),则您的选择将受到很大限制。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.