从自定义控件的模板部分中删除事件处理程序

Posted

技术标签:

【中文标题】从自定义控件的模板部分中删除事件处理程序【英文标题】:Removing event handlers from custom control's template parts 【发布时间】:2011-06-14 20:27:29 【问题描述】:

当我第一次开始编写 WPF 自定义控件时,如果我想添加一个事件处理程序,我会在获取模板部分后在控件的 OnApplyTemplate 覆盖中这样做:

public void override OnApplyTemplate() 
  if ( addMenu != null ) 
    addMenu.Click -= addMenu_Click;
    addMenu = null;
  
  addMenu = (MenuItem)Template.FindName("PART_AddMenu", this); 
  addMenu.Click += addMenu_Click;

但是有一天我注意到 OnApplyTemplate() 并不总是在我期望的时候被调用,即当控件与可视化树断开连接时。也就是说,使用上述技术,事件处理程序不会总是被删除。所以我想出了一个不同的方法:

public MyCustomControl()

  Loaded += this_Loaded;


void this_Loaded(object sender, RoutedEventArgs e)

  Unloaded += this_Unloaded;

  addMenu = (MenuItem)Template.FindName("PART_AddMenu", this);
  addMenu.Click += addMenu_Click;


void this_Unloaded(object sender, RoutedEventArgs e)

  Unloaded -= this_Unloaded;

  if (addMenu != null)
  
    addMenu.Click -= addMenu_Click;
    addMenu = null;
  

这种方式似乎可以解决问题。每个人都同意这是在自定义控件中连接和删除事件处理程序的更好方法吗?如果不是,那为什么?

【问题讨论】:

【参考方案1】:

这个方法很好,但你必须明白你有时会得到 unloaded 事件,你可能不希望事件处理程序脱钩。例如,假设您有一个选项卡控件。当您切换 TabItems 时,前一个 TabItem 的内容将全部卸载,然后在 TabItem 再次被选中时重新加载。这对于 Button.Click 之类的东西很好,因为您无法在非活动选项卡上执行此类操作,但任何不需要将项目加载到可视树中的事件都将断开连接,即使项目仍然存在。

为什么你觉得你需要清理所有的事件处理程序?我意识到在某些情况下它们可以挂在另一个对象的引用上,但这是一种不寻常的情况,通常最好在以这种方式使用时通过清理它们来处理。这里有一些更好的细节:How built-in WPF controls manage their event handlers to an attached event?

【讨论】:

你说得对,我想防止挂在引用上,因为我注意到它在性能调整期间发生。我不确定具体的原因,所以我宁愿通过使用新技术来保证安全。关于您的 TabItem 示例,我知道会发生这种情况,但我想不出有问题的情况。 只有当父对象将其事件解析到子对象上时才会发生泄漏(从而保持对子对象的第二个引用)。 如果我没听错的话,那么在上面的例子中,“父对象”是自定义控件,“子对象”是 addMenu(模板部分)。由于自定义控件将 addMenu 存储在私有字段中,因此旧技术(使用 OnApplyTemplate)可能会发生泄漏。 没有。在您的示例中,自定义控件必须执行类似 custom.event += addMenu.Method 的操作,而不是通常的 addMenu.Event += custom.Method。这是非常不寻常的做法。【参考方案2】:

WPF 控件(例如 ComboBox)使用OnTemplateChangedInternal() 方法取消注册在OnApplyTemplate() 中注册的事件。您可以重写该方法,因为它是 PresentationFramework dll 的内部方法,但您可以重写受保护的 OnTemplateChanged() 方法来执行相同的操作 - 它由 Control 基类中的 OnTemplateChangedInternal() 调用。

下面是可以进入您的自定义控件的示例代码:

        public override void OnApplyTemplate()
        
            base.OnApplyTemplate();
            EditableTextBoxSite = GetTemplateChild("PART_EditableTextBox") as TextBox;
            EditableTextBoxSite.TextChanged += new TextChangedEventHandler(this.OnEditableTextBoxTextChanged);
            this.EditableTextBoxSite.PreviewTextInput -= new TextCompositionEventHandler(this.OnEditableTextBoxPreviewTextInput);
        

        protected override void OnTemplateChanged(ControlTemplate oldTemplate, ControlTemplate newTemplate)
        
            base.OnTemplateChanged(oldTemplate, newTemplate);
            if (this.EditableTextBoxSite == null)
                return;
            this.EditableTextBoxSite.TextChanged -= new TextChangedEventHandler(this.OnEditableTextBoxTextChanged);
            this.EditableTextBoxSite.PreviewTextInput -= new TextCompositionEventHandler(this.OnEditableTextBoxPreviewTextInput);
        

我不确定这样做的所有含义,但它似乎是模拟 WPF 控件所做的最接近的方法。

【讨论】:

以上是关于从自定义控件的模板部分中删除事件处理程序的主要内容,如果未能解决你的问题,请参考以下文章

从自定义控件引用子项

如何从自定义用户控件 WPF、C# 中的枚举自定义属性中获取值?

XAML: 自定义控件中事件处理的最佳实践

XAML: 自定义控件中事件处理的最佳实践

自定义控件点击事件

WPF自定义控件,自定义控件中由多个button组成,如何给每个button添加不同事件处理?