【问题标题】:Why ButtonBase doesn't check its Visibility before testing `ICommand.CanExecute`?为什么 ButtonBase 在测试 ICommand.CanExecute 之前不检查其可见性?
【发布时间】:2014-02-05 08:52:28
【问题描述】:

我遇到了一个问题,让我大吃一惊。
我们从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窗体)关闭,因为CanExecute是在处理完Form.Closed事件后调用的; 2) 没有内存泄漏 - 如果我模拟 CanExecute,内存分析器显示,我的包含命令的视图模型被 GC 收集并且不再存在。

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

附: 我看到的唯一解决方法是在我的视图模型中的某处保留一个标志,这将显示一次性已处理,并从CanExecute 返回false。 有更好的想法吗?

【问题讨论】:

  • 想知道为什么一些 Microsoft 开发人员以他们的方式开发东西有什么意义……他们只是出于某种原因这样做。在尝试使用 disposableObject 之前先使用旧的 if (disposableObject != null) 怎么样?
  • @Sheridan:这种行为可能是有原因的,我做错了什么。 disposableObject 不是空的,它只是被处理掉了,检查一些标志是我建议的解决方法。
  • “它订阅了 CommandManager.RequerySuggested”,我猜这就是问题所在,因为你的所有命令都与这个事件挂钩,当你调用 InvalidateRequerySuggested() 时,它们都会被重新评估,当你关闭你的窗口。
  • @Dtex:没错。但是当按钮不可见时,我想不出一个理由来检查影响视觉状态的东西。
  • 我的建议与您的解决方法基本相同,如果您的视图模型被处置,则返回 false。

标签: c# wpf mvvm weak-references


【解决方案1】:

我会给出四个可能的答案,并猜测为什么它是这样实现的:

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

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

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

  4. 如果您正在等待终结器调用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" />

如果没有查询按钮的隐藏状态,一旦禁用就永远不会显示。

【讨论】:

    猜你喜欢
    • 2021-10-21
    • 1970-01-01
    • 1970-01-01
    • 2011-04-23
    • 2014-04-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多