为什么我的具有 ICommand 绑定的按钮在单击时不会立即显示为禁用状态?
Why does my button with an ICommand binding not immediately appear disabled when clicked?
我有一个带有 ICommand
的简单 WPF 程序。我发现该按钮并不像我预期的那样 enable/disable。我可以用一个人为的代码示例来最好地说明这一点:
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)
变为 return 错误。在那次通话之后,我希望该按钮立即被禁用,但直到调用 RunWorkerAsync()
和第二次模拟计算之间的某个时间点才会被禁用。
在后台工作人员的 RunWorkerCompleted(...)
事件处理程序中,我再次触发了 CanExecuteChanged
事件,但这次触发的方式会导致 CanExecuteChanged(object)
变为 return true。此调用后,该按钮立即启用。
为什么当我触发 CanExecuteChanged
事件时按钮没有立即显示为禁用状态?
注意#1:第一个模拟计算代表我拥有的应该在主 GUI 线程上运行的代码。如果我删除此调用,该按钮将按我预期的方式运行。
注#2:我读过有关使用 CommandManager.InvalidateRequerySuggested()
强制代码调用 CanExecute(object)
方法的内容。我在评论中表明这对我不起作用。考虑到我调用 OnCanExecuteChanged(...)
,我认为这个建议无论如何都是多余的。
正确的解决方案是您已经找到的解决方案,将第一个 long-运行ning 操作从 UI线程.
但是,如果您不能这样做,问题是您没有给 UI 一个机会来 运行 它的绑定和更新状态。它可能会在后台工作人员启动后立即更新(因为控制权是从您的函数返回的)。
您可以利用 async/await
and 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 允许您异步执行一个操作,并等待它完成,同时允许当前线程继续执行(在当前方法调用之外)。要解释所有的技术细节并不容易,请参阅 link 了解更多信息。
我会等待至少 20 毫秒,可能是 50 毫秒。显然像这样延迟不是 cleanest 解决方案,但是如果不删除 Sleep
(或将它代表的代码移出 UI 线程),您的选择很不错有限
我有一个带有 ICommand
的简单 WPF 程序。我发现该按钮并不像我预期的那样 enable/disable。我可以用一个人为的代码示例来最好地说明这一点:
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)
变为 return 错误。在那次通话之后,我希望该按钮立即被禁用,但直到调用 RunWorkerAsync()
和第二次模拟计算之间的某个时间点才会被禁用。
在后台工作人员的 RunWorkerCompleted(...)
事件处理程序中,我再次触发了 CanExecuteChanged
事件,但这次触发的方式会导致 CanExecuteChanged(object)
变为 return true。此调用后,该按钮立即启用。
为什么当我触发 CanExecuteChanged
事件时按钮没有立即显示为禁用状态?
注意#1:第一个模拟计算代表我拥有的应该在主 GUI 线程上运行的代码。如果我删除此调用,该按钮将按我预期的方式运行。
注#2:我读过有关使用 CommandManager.InvalidateRequerySuggested()
强制代码调用 CanExecute(object)
方法的内容。我在评论中表明这对我不起作用。考虑到我调用 OnCanExecuteChanged(...)
,我认为这个建议无论如何都是多余的。
正确的解决方案是您已经找到的解决方案,将第一个 long-运行ning 操作从 UI线程.
但是,如果您不能这样做,问题是您没有给 UI 一个机会来 运行 它的绑定和更新状态。它可能会在后台工作人员启动后立即更新(因为控制权是从您的函数返回的)。
您可以利用 async/await
and 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 允许您异步执行一个操作,并等待它完成,同时允许当前线程继续执行(在当前方法调用之外)。要解释所有的技术细节并不容易,请参阅 link 了解更多信息。
我会等待至少 20 毫秒,可能是 50 毫秒。显然像这样延迟不是 cleanest 解决方案,但是如果不删除 Sleep
(或将它代表的代码移出 UI 线程),您的选择很不错有限