控制和查看 Silverlight DataGrid 的滚动条位置






4.80/5 (9投票s)
如果您查看 Silverlight DataGrid,您会发现默认情况下您无法控制滚动。 如果您使用数据驱动的应用程序,这可能是一个障碍。 如果您想在重新加载 DataGrid 后保留滚动位置,该怎么办?
引言
如果您查看 Silverlight DataGrid
,您会发现默认情况下您无法控制滚动。 如果您使用数据驱动的应用程序,这可能是一个障碍。 如果您想在重新加载 DataGrid
后保留滚动位置,该怎么办?
在本文中,我们将对滚动执行 2 件事
- 创建扩展方法来控制
DataGrid
滚动 - 创建一个自定义的
DataGrid
用于高级场景
DataGrid 上的滚动条
在我们开始之前,先快速了解一下带有滚动条的 DataGrid
。 每个 DataGrid
最多可以有 2 个滚动条(1 个水平和 1 个垂直)。 您可以拖动的按钮称为拇指 (来自 System.Windows.Controls.Primitives.Thumb
)。

创建 DataGridScrollExtensions 类
我们创建的类应该提供以下功能
- 允许我们访问水平和垂直滚动条
- 通知我们滚动条拇指的位置(和变化)
- 移动滚动条拇指
这就是类的样子
查找滚动条
首先要做的是确保我们可以访问滚动条。 这就是我们将使用递归方法 GetScrollbars
的地方。 使用 VisualTreeHelper
,我们可以深入研究一个 DependencyObject
(在我们的例子中是 DataGrid
),直到找到 Scrollbar
。 您可以用来查找正确的滚动条的名称是 VerticalScrollbar
和 HorizontalScrollbar
(区分大小写!)。
private static ScrollBar GetScrollbar(this DependencyObject dep, string name)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(dep); i++)
{
var child = VisualTreeHelper.GetChild(dep, i);
if (child != null && child is ScrollBar && ((ScrollBar)child).Name == name)
return child as ScrollBar;
else
{
ScrollBar sub = GetScrollbar(child, name);
if (sub != null)
return sub;
}
}
return null;
}
现在我们可以访问滚动条了,我们可以获取我们需要的所有信息。 您可能需要查看 Maximum
和 Value
属性以及 Scroll
事件。
移动滚动条
移动滚动条稍微复杂一些。 我敢打赌您想的和我一样。 只需更改滚动条的 Value
属性。 我建议您尝试一下。 您会看到 Thumb
将移动到滚动条的新值,但数据不会跟随。 让我们使用 Reflector 看看 DataGrid
public override void OnApplyTemplate()
{
...
this._hScrollBar = base.GetTemplateChild("HorizontalScrollbar") as ScrollBar;
if (this._hScrollBar != null)
{
this._hScrollBar.IsTabStop = false;
this._hScrollBar.Maximum = 0.0;
this._hScrollBar.Orientation = Orientation.Horizontal;
this._hScrollBar.Visibility = Visibility.Collapsed;
this._hScrollBar.Scroll +=
new ScrollEventHandler(this.HorizontalScrollBar_Scroll);
}
...
this._vScrollBar = base.GetTemplateChild("VerticalScrollbar") as ScrollBar;
if (this._vScrollBar != null)
{
this._vScrollBar.IsTabStop = false;
this._vScrollBar.Maximum = 0.0;
this._vScrollBar.Orientation = Orientation.Vertical;
this._vScrollBar.Visibility = Visibility.Collapsed;
this._vScrollBar.Scroll += new ScrollEventHandler(this.VerticalScrollBar_Scroll);
}
...
}
如您所见,DataGrid
订阅每个 ScrollBar
的 Scroll
事件。 当此事件被触发时,DataGrid
将移动数据。 但是如果您只是更改 ScrollBar
的 Value
,则不会触发 Scroll
事件。
我们需要模拟用户移动 ScrollBar
。 也许您知道这一点,但 Silverlight(和 .NET)提供了一个为此目的而设计的概念,即 Microsoft UI Automation。 它的一个功能是模拟用户交互。 您可以在 这里 和 这里 阅读更多信息。 以下代码获取 DataGrid
的 AutomationPeer
,并且此对象可以为我们提供 IScrollProvider
。
private static IScrollProvider GetScrollProvider(DataGrid grid)
{
var p = FrameworkElementAutomationPeer.FromElement(grid)
?? FrameworkElementAutomationPeer.CreatePeerForElement(grid);
return p.GetPattern(PatternInterface.Scroll) as IScrollProvider;
}
最后,使用此 IScrollProvider
,您可以模拟滚动交互
switch (mode)
{
case ScrollMode.Vertical:
scrollProvider.SetScrollPercent(
System.Windows.Automation.ScrollPatternIdentifiers.NoScroll, percent);
break;
case ScrollMode.Horizontal:
scrollProvider.SetScrollPercent(percent,
System.Windows.Automation.ScrollPatternIdentifiers.NoScroll);
break;
}
代码
这是包含所有必要的扩展方法的代码
public static class DataGridScrollExtensions
{
// Scroll to the start of the ScrollBar.
public static void ScrollToStart(this DataGrid grid, ScrollMode mode)
{
switch (mode)
{
case ScrollMode.Vertical:
grid.ScrollToPercent(ScrollMode.Vertical, 0);
break;
case ScrollMode.Horizontal:
grid.ScrollToPercent(ScrollMode.Horizontal, 0);
break;
}
}
// Scroll to the end of the ScrollBar.
public static void ScrollToEnd(this DataGrid grid, ScrollMode mode)
{
switch (mode)
{
case ScrollMode.Vertical:
grid.ScrollToPercent(ScrollMode.Vertical, 100);
break;
case ScrollMode.Horizontal:
grid.ScrollToPercent(ScrollMode.Horizontal, 100);
break;
}
}
// Scroll to a percentage of the scrollbar (50% = half).
public static void ScrollToPercent
(this DataGrid grid, ScrollMode mode, double percent)
{
// Fix the percentage.
if (percent < 0)
percent = 0;
else if (percent > 100)
percent = 100;
// Get the scroll provider.
var scrollProvider = GetScrollProvider(grid);
// Scroll.
switch (mode)
{
case ScrollMode.Vertical:
scrollProvider.SetScrollPercent
(System.Windows.Automation.ScrollPatternIdentifiers.NoScroll, percent);
break;
case ScrollMode.Horizontal:
scrollProvider.SetScrollPercent
(percent, System.Windows.Automation.ScrollPatternIdentifiers.NoScroll);
break;
}
}
// Get the current position of the scrollbar.
public static double GetScrollPosition(this DataGrid grid, ScrollMode mode)
{
var scrollBar = grid.GetScrollbar(mode);
return scrollBar.Value;
}
// Get the maximum position of a scrollbar.
public static double GetScrollMaximum(this DataGrid grid, ScrollMode mode)
{
var scrollBar = grid.GetScrollbar(mode);
return scrollBar.Maximum;
}
// Scroll to a position of the scrollbar.
public static void Scroll(this DataGrid grid, ScrollMode mode, double position)
{
// Get the scrollbar and convert the position to percent.
var scrollBar = grid.GetScrollbar(mode);
double positionPct = ((position / scrollBar.Maximum) * 100);
// Scroll to a specific percentage of the scrollbar.
grid.ScrollToPercent(mode, positionPct);
}
// Get a scroll provider for the grid.
private static IScrollProvider GetScrollProvider(DataGrid grid)
{
var p = FrameworkElementAutomationPeer.FromElement(grid) ??
FrameworkElementAutomationPeer.CreatePeerForElement(grid);
return p.GetPattern(PatternInterface.Scroll) as IScrollProvider;
}
// Get one of the grid's scrollbars.
public static ScrollBar GetScrollbar(this DataGrid grid, ScrollMode mode)
{
if (mode == ScrollMode.Vertical)
return grid.GetScrollbar("VerticalScrollbar");
else
return grid.GetScrollbar("HorizontalScrollbar");
}
// Find the scrollbar for our datagrid.
private static ScrollBar GetScrollbar(this DependencyObject dep, string name)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(dep); i++)
{
var child = VisualTreeHelper.GetChild(dep, i);
if (child != null && child is ScrollBar && ((ScrollBar)child).Name == name)
return child as ScrollBar;
else
{
ScrollBar sub = GetScrollbar(child, name);
if (sub != null)
return sub;
}
}
return null;
}
}
创建 ScrollDataGrid 控件
现在我们可以控制滚动,我们可以更进一步。 创建一个 DataGrid
,您可以在其中订阅两个 Scroll
事件甚至存储当前的滚动位置怎么样? 当我们重新加载项目并且想要保留滚动位置时,这可能很有用。

最重要的事情是注意 SaveScrollPosition
/ ReloadScrollPosition
方法以及 HorizontalScroll
/ VerticallScroll
事件。 这里是 ScrollDataGrid
的一个小演示。
方法 SaveScrollPosition
将当前位置保存在内部字段中,而 ReloadScrollPosition
将该字段的值应用回 ScrollBar
。 为了找到 ScrollBar
,我们正在应用另一种技术,即使用 GetTemplateChild
。 其余代码调用我们的扩展方法
public class ScrollDataGrid : DataGrid
{
// The vertical scrollbar.
private ScrollBar verticalScrollBar;
// The horizontal scrollbar.
private ScrollBar horizontalScrollBar;
// Position of the vertical scrollbar we saved.
private double savedVerticalScrollPosition;
// Position of the horizontal scrollbar we saved.
private double savedHorizontalScrollPosition;
// Event for each vertical scroll.
public event EventHandler<scrolleventargs> VerticalScroll;
// Event for each horizontal scroll.
public event EventHandler<scrolleventargs> HorizontalScroll;
// Load the scrollbars after the template gets loaded.
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.LoadScrollBars();
}
// Get both scrollbars.
private void LoadScrollBars()
{
verticalScrollBar = this.GetTemplateChild("VerticalScrollbar") as ScrollBar;
if (verticalScrollBar != null)
verticalScrollBar.Scroll += new ScrollEventHandler(OnVerticalScroll);
horizontalScrollBar = this.GetTemplateChild("HorizontalScrollbar") as ScrollBar;
if (horizontalScrollBar != null)
horizontalScrollBar.Scroll += new ScrollEventHandler(OnHorizontalScroll);
}
// Notify that we are scrolling vertically.
private void OnVerticalScroll(object sender, ScrollEventArgs e)
{
if (VerticalScroll != null)
VerticalScroll(sender, e);
}
// Notify that we are scrolling horizontally.
private void OnHorizontalScroll(object sender, ScrollEventArgs e)
{
if (HorizontalScroll != null)
HorizontalScroll(sender, e);
}
// Save the current scroll position.
public void SaveScrollPosition(ScrollMode mode)
{
switch (mode)
{
case ScrollMode.Vertical:
this.savedVerticalScrollPosition = verticalScrollBar.Value;
break;
case ScrollMode.Horizontal:
this.savedHorizontalScrollPosition = horizontalScrollBar.Value;
break;
default:
break;
}
}
// Reload the scroll position that was saved before.
public void ReloadScrollPosition(ScrollMode mode)
{
switch (mode)
{
case ScrollMode.Vertical:
this.Scroll(ScrollMode.Vertical, savedVerticalScrollPosition);
break;
case ScrollMode.Horizontal:
this.Scroll(ScrollMode.Horizontal, savedHorizontalScrollPosition);
break;
}
}
}
滚动愉快。
历史
- 2010 年 9 月 14 日:初始帖子