为什么ButtonBase在测试`ICommand.CanExecute`之前没有检查它的可见性?

时间:2014-01-16 12:35:37

标签: c# wpf mvvm weak-references

我遇到了一个问题,这让我大吃一惊  我们来看看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只是一个事件处理程序,当您使用与UpdateCanExecute不同的内容时,ICommand.CanExecute最终会调用您的RoutedCommand。当您使用任何MVVM框架时就是这种情况。

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

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

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

当我关闭包含使用此数据模板呈现的视觉效果的MDI子项时,按钮会尝试更新其状态并调用CanExecute。这是一个很大的问题,因为CanExecute调用了一些已经被处置的一次性对象。

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

问题。
如果按钮不可见,检查false的目的是什么? 有没有选择来阻止这种行为?

P.S。 我看到的唯一解决方法是在我的视图模型中的某处保留一个标志,该标志将显示,丢弃了一次性用品,并从CanExecute返回{{1}}。 有更好的想法吗?

1 个答案:

答案 0 :(得分:0)

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

  1. 在丢弃所有内容之前,将窗口的DataContext设置为null。该按钮没有对象的引用,因此异常永远不会被抛出。

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

  3. IsDisposed属性添加到一次性对象中,并事先进行检查。如果您在非UI或终结器线程上做任何事情,似乎可能存在竞争条件。

  4. 如果您正在等待终结器呼叫WeakReference,请将Dispose保留到一次性对象,或在致电null后将引用设置为Dispose()并在调用之前检查它是否为null

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

    <!-- 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" />
    

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