WPF 多个 ScrollViewer 滚动同步

WPF 多个 ScrollViewer 滚动同步

独立观察员 2023 年 5 月 31 日


0、前言

在进行 WPF 程序开发时,有时界面内容分别放在不同的滚动区域中,也就是在不同的 ScrollViewer 控件中,默认情况下,它们各自的内容滚动是互不影响的,这也符合大部分的场景。但是偏偏就有这么一些场景,需要让它们虽然身处不同区域,但是其中一个区域内容滚动后,另外的一个或几个滚动区域也要同步滚动,以达到 具有关联性的内容同时出现或消失在视野中 的效果。那么如何实现呢?本文将分两个方面介绍,二者相辅相成,共同达成目标,和本文的主题有异曲同工之妙。


1、ScrollChanged 事件控制同步滚动

可以看到界面上有 3 个滚动区域,分别是左侧滚动区 leftScrollViewer(垂直滚动条隐藏,水平滚动条禁用)、顶部滚动区 topScrollViewer(垂直滚动条禁用,水平滚动条隐藏)、以及中部滚动区 centerScrollViewer(水平和垂直滚动条都设为自动),最主要的是,三者都各自设置了 ScrollChanged 事件处理方法:


三者的滚动事件处理方法如下,简而言之就是:左侧的垂直滚动区带动中部滚动区垂直滚动,顶部水平滚动区带动中部滚动区水平滚动,中部滚动区带动左侧和顶部滚动区相应地滚动,使用的参数都是各自方向的偏移量。


可以看到,滚动后,三个区域的内容是相互对齐的:


但是当中部滚动区在垂直或水平方向上分别滚到底时就露馅了,与左侧和顶部内容产生了错位:


可以通过一个动图来更直观地展示:


那么这是什么情况呢?其实就是滚动条导致的。因为既然要求三个区域内容对齐,那么无论是在行还是列上,单个内容的数目和尺寸都是一样的,那么在一个方向上内容的总体尺寸(宽度或高度)也是一致的;此时一方有滚动条,一方没有,就导致了总体尺寸的不一致(猜测是有滚动条的一方会给内部内容加上滚动条的 宽度 / 高度 这样一个冗余尺寸,不然不就被挡住了嘛);最终就在有滚动条的一方滚动到最远端时,没有滚动条的一方无法满足相应的偏移量(按照之前的猜测,就是由于没被加上冗余尺寸),产生了错位。


2、一方隐藏滚动条后错位的解决方法

既然猜测是由于没有(应该说是被隐藏了)滚动条的一方未被加上冗余尺寸(滚动条的 宽度 / 高度)导致的错位,那我们就对症下药,分别在对应的区域末尾加上这么一个冗余尺寸即可。

将滚动区域原先的内容放在 StackPanel 中,再在 StackPanel 内部末尾添加一个 Border 元素,宽度或高度设为显示了滚动条一方的滚动条的 宽度 (垂直滚动条) 或 高度 (水平滚动条):


滚动条的高度或宽度获取方式如下(使用了 CalcBinding):

xmlns:calc="clr-namespace:CalcBinding;assembly=CalcBinding"
<Border Width="{calc:Binding 'ActualWidth-ViewportWidth > 0 ? ActualWidth-ViewportWidth : 0', ElementName=centerScrollViewer}"/><Border Height="{calc:Binding 'ActualHeight-ViewportHeight > 0 ? ActualHeight-ViewportHeight : 0', ElementName=centerScrollViewer}"/>


解释(以宽度为例):滚动区域的总体宽度(包括了垂直滚动条的宽度)就是 ActualWidth,内容区域宽度就是 ViewportWidth,二者相减即为垂直滚动条的宽度。一开始只绑定了 'ActualWidth-ViewportWidth',观察发现在改变窗口尺寸时(应该是滚动条刚出现时),会有负数的情况,导致出现绑定错误,虽然不会使程序出错,不过还是处理掉比较好,所以加了个三元表达式过滤掉负数。

吐槽:框架竟然没有提供直接能获取滚动条 宽度 / 高度 的属性!(当然也有可能是我没找到)


总之,通过这种方式加上冗余尺寸后,问题就解决了,效果如下:


为了看得更清楚,可以给该区域加上红色背景:


大家可以自己运行一下试试,代码地址:

https://gitee.com/dlgcy/DLGCY_WPFPractice/tree/Blog20230531


全文完,感谢阅读。


原创文章,转载请注明: 转载自 独立观察员 (dlgcy.com)

本文链接地址:[WPF 多个 ScrollViewer 滚动同步](http://dlgcy.com/wpf-multi-scrollvierer-scroll-at-same-position/)

关注微信公众号 独立观察员博客(DLGCY_BLOG) 第一时间获取最新文章




WPF

C# 或 WPF 中如何判断两个颜色是否近似

WPF 路由事件和附加事件简明教程

WPF 消息传递简明教程

WPF 属性变动后的业务处理及恢复原始值的方法

我向 ChatGPT 讨教了一下 WPF 中的行为 Behavior

使用通用附加属性来减少 WPF 元素自定义样式的多余代码

几十款 WPF 控件 - UI 库,总有一款适合你

WPF 用户控件分享之边上带输入框的圆圈

分享一个 WPF 气泡弹框

WPF 表单验证之 INotifyDataErrorlnfo 接口的使用示例

[翻译] WPF 中用户控件 DataContext/Binding 和依赖属性的问题

OxyPlot 导出图片及 WPF 元素导出为图片的方法

让 WPF 的 RadioButton 支持再次点击取消选中的功能

WPF DataGrid 如何将被选中行带到视野中

WPF 触屏事件后触发鼠标事件的问题及 DataGrid 误触问题

WPF DataGrid 通过自定义表头模拟首行固定

WPF ComboBox 使用 ResourceBinding 动态绑定资源键并支持语言切换

【翻译】WPF 中附加行为的介绍 Introduction to Attached Behaviors in WPF

WPF 使用 Expression Design 画图导出及使用 Path 画图

WPF MVVM 弹框之等待框

解决 WPF 绑定集合后数据变动界面却不更新的问题(使用 ObservableCollection)

WPF 消息框 TextBox 绑定新数据时让光标和滚动条跳到最下面

真・WPF 按钮拖动和调整大小

WPF MVVM 模式下的弹窗

WPF 让一组 Button 实现 RadioButton 的当前样式效果

WPF 原生绑定和命令功能使用指南

WPF 用户控件自定义依赖属性MVVM 模式下的使用备忘

在WPF的MVVM模式中使用OCX组件


第三方库使用

WPF 依赖注入之 Microsoft.Extensions.DependencyInjection

WPF 表格控件 ReoGrid 的简单使用

OxyPlot.WPF 公共属性一览

OxyPlot.Wpf 图表控件使用备忘


举报
评论 0