ScrollViewer的视口高度与实际高度

9

这两个术语都很通用,但我想知道除了使用虚拟化的情况外,这些高度何时会有所不同?

还有一个问题:

如果CanContentScroll为true,则ExtentHeight、ScrollableHeight、ViewportHeight和VerticalOffset属性的值是项目数。如果CanContentScroll为false,则这些属性的值为设备独立像素。

然而,我在Viewport Height方面遇到了问题:我在应用程序中有2个列表框:
1.启用了虚拟化且CanContentScroll = True。
2.没有虚拟化且CanContentScroll = True。

在ListBox 1中,拖放时Viewport Height变为4/5(当前可见元素数)。但是在ListBox 2中,我得到的Viewport Height等于Listbox的实际高度。

为什么会有这种差异?

还有一些发现:
1. Scrollable Height是滚动查看器中不可见项的数量
2. Viewport Height是滚动查看器中可见项的数量。
因此Viewport Height + ScrollableHeight = Extent Height

有人能解释一下两个列表框之间的区别吗?我需要在Listbox 1中获取ViewPort高度

3个回答

12

ActualHeight 是指 ScrollViewer 的实际高度。Viewport 是从 ScrollViewers Content 中可见的。所以回答你的问题:ViewportHeight 会根据水平滚动条是否可见而与 ActualHeight 不同,其差值为滚动条的 Height

因此,总结一下:

ActualHeight = ViewportHeight + HorizontalScrollbarHeight

1
请问可以解释一下水平滚动条的可见性与视口高度之间的关系吗? - Rohit
1
@Rohit,我不是已经做了吗?这里有一个具体的例子:ActualHeight为200px。水平滚动条的高度为12px。如果水平滚动条被显示,视口高度为188px;如果不显示,则视口高度为200px。而滚动条是否显示取决于属性“HorizontalScrollBarVisibility”。 - Markus Hütter
谢谢回复。我理解了上面的内容,但我很好奇这些东西如何相互关联。据我所知,视口高度应该考虑实际高度和垂直滚动条的可见性。 - Rohit
@Rohit 我真的不知道还有什么需要解释的了。Viewport的大小是ScrollViewer减去滚动条区域的大小。你只能__获取__Viewport的尺寸(不能设置它)。它的计算取决于ActualHeight(本身是只读的,也会被计算(通过Height属性和控件上的限制))和滚动条。它存在的目的是让你可以绑定它,而不必使用值转换器来计算ActualHeight - (IsHorizontalScrollBarVisible ? HorizontalScrollbarHeight : 0) - Markus Hütter
这个答案仅适用于 CanContentScrollfalse 的情况。例如,在 ListView 中,ScrollViewer 的视口高度将始终是 ScrollViewer 可以显示的项目数。 - bitbonk
@bitbonk 这个答案回答了原始形式的问题。直到 OP 编辑后,才显而易见他实际上是在谈论一个 ListView - Markus Hütter

9

最终,这是根本原因:

引用自https://dev59.com/jnA75IYBdhLWcg3w3NHf#3062692

你正在遇到物理滚动和逻辑滚动之间的差异。正如你已经发现的,每种方式都有其权衡利弊。
物理滚动(CanContentScroll=false)仅按像素进行滚动,因此:
- 视口始终代表滚动范围的完全相同部分,提供平滑的滚动体验; - 但是,数据网格的所有内容必须应用所有模板并测量和排列以确定滚动条的大小,导致加载期间出现长时间延迟和高内存使用率,并且它实际上不会滚动项目,因此无法很好地理解 ScrollIntoView。
逻辑滚动(CanContentScroll=true)通过项目而不是像素计算其滚动视口和范围,因此:
- 视口可能在不同的时间显示不同数量的项目,这意味着视口中的项目数与范围中的项目数相比会有所变化,从而导致滚动条长度改变; - 滚动从一个项目移动到下一个项目,而不是在两个项目之间移动,导致“抖动”滚动; - 但是,只要在幕后使用 VirtualizingStackPanel,它就只需要应用模板并测量和排列当前实际可见的项目,而且 ScrollIntoView 更简单,因为它只需要将正确的项目索引置于视图中。
这些是 WPF 提供的唯一两种滚动方式。您必须根据上述权衡来选择它们。通常,逻辑滚动最适合中等到大型数据集,而物理滚动最适合小型数据集。
加快物理滚动加载的技巧是将您的项目包装在一个自定义装饰器中,该装饰器具有固定大小,并在其不可见时将其子项的 Visibility 设置为 Hidden。这可以防止 ApplyTemplate、Measure 和 Arrange 在该项目的后代控件上发生,直到您准备好进行操作为止。
使物理滚动的 ScrollIntoView 更可靠的技巧是调用它两次:一次立即调用,一次在 DispatcherPriority.ApplicationIdle 的调度程序回调中调用。
使逻辑滚动滚动条更稳定
如果您的所有项目高度相同,则任何时候在视口中可见的项目数将保持不变,从而导致滚动拇指大小保持不变(因为与总项目数的比率不会改变)。
还可以修改 ScrollBar 自身的行为,以便始终计算拇指的固定大小。要在没有任何 hacky 代码的情况下执行此操作:
- 子类化 Track,以在 MeasureOverride 中使用自己的 Thumb 位置和大小替换其计算。 - 将用于逻辑滚动 ScrollBar 的 ScrollBar 模板更改为使用您的子类化 Track 而不是常规模板。 - 将 ScrollViewer 模板更改为在逻辑滚动 ScrollBar 上显式设置您的自定义 ScrollBar 模板(而不是使用默认模板)。 - 将 ListBox 模板更改为在它创建的 ScrollViewer 上显式设置您的自定义 ScrollViewer 模板。
这意味着复制大量内置 WPF 模板代码,因此这不是非常优雅的解决方案。但是,这样做的替代方法是使用 hacky 代码,在等待所有模板扩展之后,找到 ScrollBar 并将 ScrollBar 模板替换为使用您的自定义 Track 的模板。这段代码可以节省两个大型模板(ListBox、ScrollViewer),但代价是一些非常棘

2
下次发问题时,请从一开始就发布所有相关信息。例如,您可以声明您正在讨论一个位于ItemsControl内部的ScrollViewer,这是与通常的仅允许您滚动某些内容的ScrollViewer完全不同的情况。 - Markus Hütter

1

它们可以在渲染过程中的任何时间点与指定的Height不同。

来自MSDN

HeightWidth属性与ActualHeightActualWidth有所不同。例如,ActualHeight属性是基于其他高度输入和布局系统计算出的值。该值由布局系统本身设置,基于实际的渲染过程,并且可能略微滞后于作为输入更改基础的属性(如Height)的设置值。

因为ActualHeight是一个计算出的值,所以您应该知道,由于布局系统的各种操作,它可能会有多个或增量报告的更改。布局系统可能正在计算子元素所需的测量空间、父元素的约束等。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接