为什么ButtonBase在测试"ICommand.CanExecute"之前不检查其可见性?



我遇到了一个问题,这让我大吃一惊。
让我们从ButtonBase看一下这些方法:

private void HookCommand(ICommand command)
{ 
CanExecuteChangedEventManager.AddHandler(command, OnCanExecuteChanged);
UpdateCanExecute();
}
private void OnCanExecuteChanged(object sender, EventArgs e)
{ 
UpdateCanExecute(); 
}
private void UpdateCanExecute()
{
if (Command != null)
{ 
CanExecute = MS.Internal.Commands.CommandHelpers.CanExecuteCommandSource(this);
} 
else 
{
CanExecute = true; 
}
}
HookCommand

在为按钮分配新命令时调用。它通过弱事件管理器订阅CommandManager.RequerySuggested并更新按钮状态(启用/禁用)。

OnCanExecuteChanged只是一个事件处理程序,当您使用与RoutedCommand不同的东西时,UpdateCanExecute最终会调用您的ICommand.CanExecute。当您使用任何 MVVM 框架时,就是这种情况。

现在,问题来了。
我的一个数据模板应用于ContentControl以显示一些数据:

<ContentControl Grid.Row="0" Content="{Binding}" ContentTemplate="{StaticResource TemplateState}"/>

这个模板包含在另一个ContentControl中相当复杂的可视化树中,该托管在ElementHost(这是WinForms MDI应用程序中的WPF组件)。 此模板中有几个按钮,其Command属性绑定到RelayCommands。

当我关闭包含使用此数据模板呈现的视觉对象的 MDI 子项时,按钮会尝试更新其状态并调用OnCanExecuteChanged。这是一个大问题,因为CanExecute称之为已经处理过的一次性物品。

我知道那件事: 1)窗口(WinForms表单)此时关闭,因为在处理Form.Closed事件后调用CanExecute; 2)没有内存泄漏 - 如果我模拟CanExecute,内存分析器显示,我的视图模型(包含命令)由GC收集并且不再存在。

问题。
如果按钮不可见,检查CanExecute的目的是什么? 是否有任何选项可以防止此行为?

附言 我看到的唯一解决方法是在我的视图中模型的某个位置保留一个标志,该标志将显示一次性已丢弃,并从CanExecute返回false。 有什么更好的主意吗?

我会给出四个可能的答案,并猜测为什么它以这种方式实现:

  1. 处理所有东西之前,将正在拆除的窗口的DataContext设置为null。 该按钮在对象上没有引用,因此永远不会引发异常。

  2. 将对
  3. 一次性对象的调用包装在 try/catch 中,该 try/catch 过滤ObjectDisposedException并返回 false。

  4. IsDisposed属性添加到一次性对象,并事先检查。 如果您在非 UI 或终结器线程上执行任何操作,这里似乎可能存在竞争条件。

  5. 如果您正在等待终结器调用Dispose,请按住一次性对象的WeakReference,或者在调用Dispose()后将引用设置为null,并在调用它之前检查它是否null

至于为什么命令即使在不可见时也会被查询,请考虑命令的结果可能会控制可见性。 想象一下以下内容:

<!-- This would probably have to be done in some more complicated way, like
passing IsEnabled to a converter with CanExecute as the parameter, or
by just binding to IsEnabled. -->
<Button Visibility="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=CanExecute}"
Command="{Binding TheCommand" Content="Do it" />

如果未查询按钮的状态处于隐藏状态,则一旦禁用,它将永远不会显示。

最新更新