WinUI 3自定义控件事件处置:如何正确取消事件订阅?
解决WinUI 3自定义控件Popup事件订阅/取消订阅问题
问题根源
WinUI 3的Unloaded事件并非控件销毁的标志——当控件被临时移出视觉树(比如布局调整、父控件切换可视状态)时就会触发,所以在这个事件里取消订阅会导致控件后续正常使用时事件失效。
正确解决方案
1. 每次模板应用时先取消旧订阅,再订阅新事件
OnApplyTemplate可能会被多次调用(比如模板刷新、控件样式变更),所以每次获取新的Popup实例前,必须先取消旧实例的事件订阅,避免重复订阅和内存泄漏:
private Popup? _popup; private Button? toggleButton; private ListView? listView; private Grid? popupGrid; private TextBlock? textBlock; protected override void OnApplyTemplate() { base.OnApplyTemplate(); // 先取消旧Popup的事件订阅 if (_popup != null) { _popup.Closed -= Popup_Closed; _popup.Opened -= Popup_Opened; } // 获取模板中的控件实例 toggleButton = GetTemplateChild("PART_ToggleButton") as Button; _popup = GetTemplateChild("PART_Popup") as Popup; listView = GetTemplateChild("PART_ListView") as ListView; popupGrid = GetTemplateChild("PART_PopupGrid") as Grid; textBlock = GetTemplateChild("PART_ContentTextBlock") as TextBlock; // 订阅新Popup的事件 if (_popup != null) { _popup.Closed += Popup_Closed; _popup.Opened += Popup_Opened; } }
2. 在控件销毁时取消订阅
通过实现IDisposable接口,在控件真正被销毁时清理事件订阅,确保资源释放:
public sealed class YourCustomControl : Control, IDisposable { // ... 上述成员变量和OnApplyTemplate代码 ... public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } ~YourCustomControl() { Dispose(false); } private void Dispose(bool disposing) { if (disposing) { // 清理托管资源:取消Popup事件订阅 if (_popup != null) { _popup.Closed -= Popup_Closed; _popup.Opened -= Popup_Opened; _popup = null; } // 可在此处清理其他托管资源 } // 若有非托管资源,在此处清理 } }
备选方案:使用WeakEventManager避免内存泄漏
如果担心忘记手动取消订阅,可使用WeakEventManager来订阅事件,它不会因未取消订阅导致内存泄漏:
// 订阅事件 if (_popup != null) { WeakEventManager<Popup, object>.AddHandler(_popup, nameof(Popup.Opened), Popup_Opened); WeakEventManager<Popup, object>.AddHandler(_popup, nameof(Popup.Closed), Popup_Closed); } // 取消订阅 if (_popup != null) { WeakEventManager<Popup, object>.RemoveHandler(_popup, nameof(Popup.Opened), Popup_Opened); WeakEventManager<Popup, object>.RemoveHandler(_popup, nameof(Popup.Closed), Popup_Closed); }
内容的提问来源于stack exchange,提问作者Bevara Satyasairam
相关产品推荐
相关产品推荐

