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

WPF:如何阻止ScrollViewer在缩放时自动上下移动

WPF ScrollViewer缩放时滚动位置跳回问题解决

问题描述

在ScrollViewer内部放置了一个Grid并设置较长高度,动态向Grid中添加Canvas及两个矩形后,鼠标缩放功能正常,但滚动到矩形底部尝试缩放时,ScrollViewer会回到初始位置。需要保留滚动条显示,同时避免缩放时滚动位置向上移动。

原始代码

MainWindow.xaml

<ScrollViewer VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Visible" Name="sv">
    <Grid Name="myGrid">
        <Grid.RowDefinitions>
            <RowDefinition Height="50" />
            <RowDefinition Height="5000" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="330" />
        </Grid.ColumnDefinitions>
    </Grid>
</ScrollViewer>

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    double currentZoom = 0;
    ScaleTransform scaleTransform;
    TransformGroup transformGroup;

    public MainWindow()
    {
        InitializeComponent();
        scaleTransform = new ScaleTransform();
        transformGroup = new TransformGroup();

        transformGroup.Children.Add(scaleTransform);
        begin();
    }
    private void begin()
    {
        Canvas canvas = new Canvas();
        Rectangle rectangle1 = new Rectangle
        {
            Width = 300,
            Height = 600,
            Fill = new SolidColorBrush(Color.FromRgb(255, 255, 255)),
            StrokeThickness = 2,
            Stroke = Brushes.Black
        };
        Canvas.SetLeft(rectangle1, 100);
        Canvas.SetTop(rectangle1, 100);
        canvas.Children.Add(rectangle1);

        Rectangle rectangle2 = new Rectangle
        {
            Width = 300,
            Height = 600,
            Fill = new SolidColorBrush(Color.FromRgb(255, 255, 255)),
            StrokeThickness = 2,
            Stroke = Brushes.Black
        };
        Canvas.SetLeft(rectangle2, 200);
        Canvas.SetTop(rectangle2, 200);
        canvas.Children.Add(rectangle2);

        myGrid.Children.Add(canvas);
        myGrid.RenderTransform = transformGroup;
        myGrid.MouseWheel += (sender, e) => wheelEvent(sender, e);
    }
    private void wheelEvent(object sender, MouseWheelEventArgs e)
    {
        Point position = e.GetPosition(myGrid);

        scaleTransform.CenterX = position.X;
        scaleTransform.CenterY = position.Y;

        var newZoom = e.Delta / 120 / (double)50;
        scaleTransform.ScaleX = 1 + newZoom + currentZoom;
        scaleTransform.ScaleY = 1 + newZoom + currentZoom;
        currentZoom += newZoom;
    }
}

解决方法

1. 阻止ScrollViewer处理滚轮事件

ScrollViewer会优先响应鼠标滚轮事件并触发滚动,导致缩放时位置跳回。在wheelEvent方法开头添加事件标记,阻止ScrollViewer处理该事件:

private void wheelEvent(object sender, MouseWheelEventArgs e)
{
    e.Handled = true; // 阻止ScrollViewer处理滚轮事件
    // ... 原有代码
}

2. 同步滚动偏移,保持缩放视口位置

仅阻止滚动还不够,缩放时需要调整ScrollViewer的滚动偏移,确保鼠标指向的点始终停留在当前视口中。修改后的wheelEvent方法如下:

private void wheelEvent(object sender, MouseWheelEventArgs e)
{
    e.Handled = true;

    // 获取鼠标相对于ScrollViewer视口的位置
    Point viewportPos = e.GetPosition(sv);
    // 获取鼠标相对于Grid的原始位置
    Point gridPos = e.GetPosition(myGrid);

    // 计算缩放增量与新旧缩放比例
    var newZoom = e.Delta / 120 / (double)50;
    double oldScale = scaleTransform.ScaleX;
    double newScale = oldScale + newZoom;

    // 调整水平滚动偏移
    double newHorizontalOffset = (gridPos.X * newScale - viewportPos.X) / newScale * oldScale - (gridPos.X - viewportPos.X);
    sv.ScrollToHorizontalOffset(newHorizontalOffset);

    // 调整垂直滚动偏移
    double newVerticalOffset = (gridPos.Y * newScale - viewportPos.Y) / newScale * oldScale - (gridPos.Y - viewportPos.Y);
    sv.ScrollToVerticalOffset(newVerticalOffset);

    // 更新缩放参数
    scaleTransform.CenterX = gridPos.X;
    scaleTransform.CenterY = gridPos.Y;
    scaleTransform.ScaleX = newScale;
    scaleTransform.ScaleY = newScale;
    currentZoom = newScale - 1;
}

3. 优化布局(可选)

如果Canvas内容会超出Grid原始尺寸,建议将Grid替换为Canvas作为ScrollViewer的直接子元素,因为Canvas本身支持无限制的坐标布局,能避免不必要的布局计算异常:

<ScrollViewer VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Visible" Name="sv">
    <Canvas Name="myCanvas" Width="330" Height="5050"/>
</ScrollViewer>

对应的代码中直接向myCanvas添加矩形即可,无需额外创建Canvas容器。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 02:57:49