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

如何实现不同窗口中两个ScrollViewer的滚动同步?

嘿,我懂你想要同步两个不同窗口里的ScrollViewer,而且已经有了用按钮控制滚动的附加属性,还尝试改编了UWP/WPF的同步代码对吧?那咱们来把这个问题捋顺,给出一个靠谱的解决方案。

跨窗口ScrollViewer滚动同步的完整实现方案

核心思路

要实现跨窗口的滚动同步,关键要解决两个问题:

  • 避免循环触发(A滚动触发B,B滚动又反过来触发A,导致死循环)
  • 兼容你已有的按钮滚动附加属性,确保按钮触发的滚动也能同步到另一个窗口

咱们可以通过给Behavior添加同步标记位来避免循环,同时扩展Behavior的方法来兼容按钮滚动的场景。

修改后的AutoScrollBehavior代码

下面是调整后的完整代码,注释已经写得很清楚了:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity; // WPF用这个,UWP用Microsoft.Xaml.Behaviors.Uwp.Managed

public class AutoScrollBehavior : Behavior<ScrollViewer>
{
    // 标记是否正在同步滚动,防止循环触发
    private bool _isSyncing;

    // 要同步的目标ScrollViewer,通过依赖属性暴露,方便绑定
    public ScrollViewer TargetScrollViewer
    {
        get => (ScrollViewer)GetValue(TargetScrollViewerProperty);
        set => SetValue(TargetScrollViewerProperty, value);
    }

    public static readonly DependencyProperty TargetScrollViewerProperty =
        DependencyProperty.Register(
            nameof(TargetScrollViewer),
            typeof(ScrollViewer),
            typeof(AutoScrollBehavior),
            new PropertyMetadata(null, OnTargetScrollViewerChanged));

    // 目标ScrollViewer变更时,更新事件监听
    private static void OnTargetScrollViewerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is AutoScrollBehavior behavior)
        {
            // 移除旧目标的滚动事件
            if (e.OldValue is ScrollViewer oldTarget)
            {
                oldTarget.ScrollChanged -= behavior.OnTargetScrollChanged;
            }
            // 添加新目标的滚动事件
            if (e.NewValue is ScrollViewer newTarget)
            {
                newTarget.ScrollChanged += behavior.OnTargetScrollChanged;
            }
        }
    }

    protected override void OnAttached()
    {
        base.OnAttached();
        // 监听当前ScrollViewer的滚动变化
        AssociatedObject.ScrollChanged += OnSelfScrollChanged;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        // 清理事件监听,防止内存泄漏
        AssociatedObject.ScrollChanged -= OnSelfScrollChanged;
        if (TargetScrollViewer != null)
        {
            TargetScrollViewer.ScrollChanged -= OnTargetScrollChanged;
        }
    }

    // 当前ScrollViewer滚动时,同步到目标
    private void OnSelfScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        if (_isSyncing) return; // 正在同步就跳过,避免循环
        _isSyncing = true;

        // 同步垂直和水平滚动位置
        TargetScrollViewer?.ScrollToVerticalOffset(AssociatedObject.VerticalOffset);
        TargetScrollViewer?.ScrollToHorizontalOffset(AssociatedObject.HorizontalOffset);

        _isSyncing = false;
    }

    // 目标ScrollViewer滚动时,同步到当前
    private void OnTargetScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        if (_isSyncing) return;
        _isSyncing = true;

        AssociatedObject.ScrollToVerticalOffset(TargetScrollViewer.VerticalOffset);
        AssociatedObject.ScrollToHorizontalOffset(TargetScrollViewer.HorizontalOffset);

        _isSyncing = false;
    }

    // 兼容你已有的按钮滚动附加属性的方法
    // 按钮触发滚动时调用这个方法,会自动同步到目标ScrollViewer
    public void ScrollBy(double verticalDelta, double horizontalDelta)
    {
        _isSyncing = true;
        // 调整当前ScrollViewer的滚动位置
        AssociatedObject.ScrollToVerticalOffset(AssociatedObject.VerticalOffset + verticalDelta);
        AssociatedObject.ScrollToHorizontalOffset(AssociatedObject.HorizontalOffset + horizontalDelta);
        _isSyncing = false;

        // 同步到目标ScrollViewer
        TargetScrollViewer?.ScrollToVerticalOffset(AssociatedObject.VerticalOffset);
        TargetScrollViewer?.ScrollToHorizontalOffset(AssociatedObject.HorizontalOffset);
    }
}

使用方法

1. 引用行为库

  • WPF项目:添加System.Windows.Interactivity NuGet包(或者手动引用程序集)
  • UWP项目:添加Microsoft.Xaml.Behaviors.Uwp.Managed NuGet包

2. 在XAML中附加Behavior

如果两个ScrollViewer在同一个窗口,直接用ElementName绑定就行;如果是跨窗口,建议用静态类或者ViewModel来传递目标ScrollViewer的引用(因为跨窗口ElementName绑定无效)。

比如窗口1的XAML:

<Window x:Class="YourApp.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:local="clr-namespace:YourApp">
    <ScrollViewer x:Name="Window1Scroll">
        <i:Interaction.Behaviors>
            <!-- 绑定到窗口2的ScrollViewer,这里用静态类示例 -->
            <local:AutoScrollBehavior TargetScrollViewer="{x:Static local:ScrollSyncManager.Window2Scroll}" />
        </i:Interaction.Behaviors>
        <!-- 你的内容 -->
    </ScrollViewer>
</Window>

窗口2的XAML:

<Window x:Class="YourApp.Window2"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:local="clr-namespace:YourApp">
    <ScrollViewer x:Name="Window2Scroll">
        <i:Interaction.Behaviors>
            <local:AutoScrollBehavior TargetScrollViewer="{x:Static local:ScrollSyncManager.Window1Scroll}" />
        </i:Interaction.Behaviors>
        <!-- 你的内容 -->
    </ScrollViewer>
</Window>

3. 静态同步管理器(跨窗口用)

创建一个静态类来存储两个窗口的ScrollViewer引用:

public static class ScrollSyncManager
{
    public static ScrollViewer Window1Scroll { get; set; }
    public static ScrollViewer Window2Scroll { get; set; }
}

然后在窗口的Loaded事件里赋值:

// Window1的Loaded事件
private void Window_Loaded(object sender, RoutedEventArgs e)
{
    ScrollSyncManager.Window1Scroll = Window1Scroll;
}

// Window2的Loaded事件
private void Window_Loaded(object sender, RoutedEventArgs e)
{
    ScrollSyncManager.Window2Scroll = Window2Scroll;
}

4. 按钮滚动调用

你的附加属性触发滚动时,直接调用Behavior的ScrollBy方法即可,比如:

// 获取ScrollViewer上的AutoScrollBehavior
var behavior = Interaction.GetBehaviors(yourScrollViewer)
    .OfType<AutoScrollBehavior>()
    .FirstOrDefault();
behavior?.ScrollBy(50, 0); // 垂直滚动50单位

注意事项

  • 一定要添加同步标记位_isSyncing,否则会出现滚动抖动或者死循环
  • 跨窗口绑定的时候,ElementName是无效的,必须用静态引用或者ViewModel传递
  • 记得在Behavior的OnDetaching方法里清理事件监听,防止内存泄漏
  • 如果是UWP项目,代码里的命名空间需要换成UWP对应的行为库

内容的提问来源于stack exchange,提问作者User9995555

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 08:52:30