You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.01 23:57:28