【发布时间】:2011-11-07 16:09:42
【问题描述】:
我确实有一个必须具有 CanContentScroll==false 的列表框,因为我需要能够顺利滚动它。这可以实现物理滚动。
我也想逐页滚动列表框,但如果我在列表框内部 ScrollViewer 上调用 PageDown 方法,第一行会被剪切,因为列表框高度不是行高的倍数。
我希望第一行始终完全可见,就像使用逻辑滚动时一样。
有人可以给我一个提示吗?
【问题讨论】:
我确实有一个必须具有 CanContentScroll==false 的列表框,因为我需要能够顺利滚动它。这可以实现物理滚动。
我也想逐页滚动列表框,但如果我在列表框内部 ScrollViewer 上调用 PageDown 方法,第一行会被剪切,因为列表框高度不是行高的倍数。
我希望第一行始终完全可见,就像使用逻辑滚动时一样。
有人可以给我一个提示吗?
【问题讨论】:
如果您向下滚动然后用鼠标选择上方可见容器,则非虚拟化ItemsControl (ScrollViewer.CanContentScroll="False") 将获得与虚拟化相同的效果。这也可以在代码中完成。
当 CanContentScroll 设置为 false 时,虚拟化将关闭,因此所有容器都将始终生成。为了获得顶部可见的容器,我们可以从顶部迭代容器,直到到达ScrollViewer 的VerticalOffset。一旦我们得到它,我们可以简单地在它上面调用BringIntoView,它会在顶部很好地对齐,就像使用虚拟化时一样。
例子
<ListBox ItemsSource="{Binding MyCollection}"
ScrollViewer.CanContentScroll="False"
ScrollViewer.ScrollChanged="listBox_ScrollChanged" >
在事件处理程序的顶部可见容器上调用BringIntoView
private void listBox_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
ItemsControl itemsControl = sender as ItemsControl;
ScrollViewer scrollViewer = e.OriginalSource as ScrollViewer;
FrameworkElement lastElement = null;
foreach (object obj in itemsControl.Items)
{
FrameworkElement element = itemsControl.ItemContainerGenerator.ContainerFromItem(obj) as FrameworkElement;
double offset = element.TransformToAncestor(scrollViewer).Transform(new Point(0, 0)).Y + scrollViewer.VerticalOffset;
if (offset > e.VerticalOffset)
{
if (lastElement != null)
lastElement.BringIntoView();
break;
}
lastElement = element;
}
}
要仅在您想调用PageDown 时实现此效果,例如在按钮单击中,您可以为ListBox 创建一个名为LogicalPageDown 的扩展方法。
listBox.LogicalPageDown();
ListBoxExtensions
public static class ListBoxExtensions
{
public static void LogicalPageDown(this ListBox listBox)
{
ScrollViewer scrollViewer = VisualTreeHelpers.GetVisualChild<ScrollViewer>(listBox);
ScrollChangedEventHandler scrollChangedHandler = null;
scrollChangedHandler = (object sender2, ScrollChangedEventArgs e2) =>
{
scrollViewer.ScrollChanged -= scrollChangedHandler;
FrameworkElement lastElement = null;
foreach (object obj in listBox.Items)
{
FrameworkElement element = listBox.ItemContainerGenerator.ContainerFromItem(obj) as FrameworkElement;
double offset = element.TransformToAncestor(scrollViewer).Transform(new Point(0, 0)).Y + scrollViewer.VerticalOffset;
if (offset > scrollViewer.VerticalOffset)
{
if (lastElement != null)
lastElement.BringIntoView();
break;
}
lastElement = element;
}
};
scrollViewer.ScrollChanged += scrollChangedHandler;
scrollViewer.PageDown();
}
}
我在您的问题中注意到您已经获得了ScrollViewer,但如果其他人遇到此问题,我将向GetVisualChild 添加一个实现
public static T GetVisualChild<T>(DependencyObject parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null)
{
child = GetVisualChild<T>(v);
}
if (child != null)
{
break;
}
}
return child;
}
【讨论】:
Margin等在查找容器的相对位置时会涉及到。相应地更新了我的答案
如果您将“新页面”的顶部项目滚动到视图中,这会实现您所寻找的吗? 见ScrollIntoView。
【讨论】: