Popup控件中Behavior的OnAttached与OnDetaching调用异常求助
问题描述
我有一个包含ScrollViewer的Popup控件,该ScrollViewer关联了自定义行为DragScrollBehavior。
当Popup首次创建时,会调用该行为的OnAttached()方法;但当Popup关闭(即IsOpen设为false)时,OnDetaching()方法会被触发。
问题在于,再次打开Popup(IsOpen设为true)时,OnAttached()不会被调用——我原本预期二者是一对相反操作。这导致每次IsSelectorVisible设为false时,事件处理程序会被移除,但仅在界面首次创建时才会添加,后续打开Popup时事件处理完全失效。
我尝试过处理AssociatedObject.Unloaded事件(它也会在每次Popup关闭时触发),还尝试处理多级父元素的Unloaded事件,但此时AssociatedObject已为null,会抛出异常,且依赖控件层级,十分脆弱。
示例代码
XAML代码
<Popup IsOpen="{Binding IsSelectorVisible}"> <Grid> <ScrollViewer> <i:Interaction.Behaviors> <mybehaviors:DragScrollBehavior /> </i:Interaction.Behaviors> </ScrollViewer> </Grid> </Popup>
C#代码(DragScrollBehavior.cs)
protected override void OnAttached() { base.OnAttached(); AssociatedObject.PreviewMouseLeftButtonDown += ScrollViewer_PreviewMouseLeftButtonDown; } protected override void OnDetaching() { AssociatedObject.PreviewMouseLeftButtonDown -= ScrollViewer_PreviewMouseLeftButtonDown; base.OnDetaching(); }
解决方案
核心问题是Popup关闭时,内部的ScrollViewer会从可视化树中卸载,触发行为的OnDetaching;但再次打开Popup时,ScrollViewer重新加载到可视化树,却不会再次触发OnAttached(因为行为已经和控件完成过首次关联)。
正确的做法是把事件的绑定/解绑逻辑转移到ScrollViewer的Loaded/Unloaded事件中——这两个事件会在控件每次进入/离开可视化树时可靠触发:
public class DragScrollBehavior : Behavior<ScrollViewer> { protected override void OnAttached() { base.OnAttached(); // 监听控件的加载/卸载事件,替代直接绑定目标事件 AssociatedObject.Loaded += OnScrollViewerLoaded; AssociatedObject.Unloaded += OnScrollViewerUnloaded; } protected override void OnDetaching() { // 先移除加载/卸载事件的监听,避免内存泄漏 AssociatedObject.Loaded -= OnScrollViewerLoaded; AssociatedObject.Unloaded -= OnScrollViewerUnloaded; // 清理目标事件的监听,防止残留引用 AssociatedObject.PreviewMouseLeftButtonDown -= ScrollViewer_PreviewMouseLeftButtonDown; base.OnDetaching(); } private void OnScrollViewerLoaded(object sender, RoutedEventArgs e) { // 控件每次加载到可视化树时,绑定事件处理程序 AssociatedObject.PreviewMouseLeftButtonDown += ScrollViewer_PreviewMouseLeftButtonDown; } private void OnScrollViewerUnloaded(object sender, RoutedEventArgs e) { // 控件每次从可视化树卸载时,解绑事件处理程序 AssociatedObject.PreviewMouseLeftButtonDown -= ScrollViewer_PreviewMouseLeftButtonDown; } private void ScrollViewer_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { // 这里放你原来的拖拽滚动逻辑 } }
方案说明
Loaded事件:每次Popup打开,ScrollViewer被添加到可视化树时都会触发,保证事件处理程序被重新绑定,不会因为之前的解绑失效。Unloaded事件:每次Popup关闭,ScrollViewer从可视化树移除时触发,及时解绑事件,避免内存泄漏。- 不再依赖
OnAttached/OnDetaching的单次触发特性,完全适配Popup内容的可视化树生命周期,也不依赖控件层级,稳定性更高。
内容的提问来源于stack exchange,提问作者komodosp
相关产品推荐
相关产品推荐

