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

4

当我开始编写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;
  }
}

这种方式似乎很有效。大家是否都认同这是自定义控件中连接和删除事件处理程序的更好方法?如果不是,那么为什么呢?

2个回答

3
这种方法是可行的,但您必须理解,在某些时候,您会得到未加载事件,而您可能不想卸载事件处理程序。例如,假设您有一个选项卡控件。当您切换TabItems时,前一个TabItem的内容都会被卸载,然后在重新选择TabItem时重新加载。这对于Button.Click之类的操作是可以接受的,因为您无法在非活动选项卡上执行此类操作,但是任何不需要将项目加载到可视树中的事件仍将被断开连接,即使项目仍然存在。
您为什么觉得需要清理所有事件处理程序?我知道有一些情况下它们可以挂起另一个对象的引用,但这是一个不寻常的情况,通常最好在使用它们的方式时清理它们。关于此,以下是一些更好的细节:内置WPF控件如何管理其附加事件的事件处理程序?

你说得对,我想避免保留引用,因为我在性能调优期间注意到了这种情况。我不确定具体的原因,所以我宁愿使用新技术来保险起见。关于你的TabItem示例,我知道会发生这种情况,但我想不出有什么问题。 - HappyNomad
只有当父对象将其事件解析到子对象上(从而保留对子对象的第二个引用)时,才会发生泄漏。 - Ed Bayiates
如果我理解正确的话,那么在上面的例子中,“父对象”是自定义控件,“子对象”是 addMenu(模板部分)。由于自定义控件将 addMenu 存储在私有字段中,因此旧技术(使用 OnApplyTemplate)可能会导致泄漏。 - HappyNomad
不行。在你的例子中,自定义控件必须执行类似于custom.event += addMenu.Method这样的操作,而不是通常的addMenu.Event += custom.Method。这种做法非常不寻常。 - Ed Bayiates

1
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控件的方式。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接