为两个WPF DataGrid同步滚动位置

6

我正在尝试同步两个WPF DataGrid控件的水平滚动位置。

我订阅了第一个DataGrid的ScrollChanged事件:

<toolkit:DataGrid x:Name="SourceGrid" ScrollViewer.ScrollChanged="SourceGrid_ScrollChanged">

我有一个第二个数据表格:

<toolkit:DataGrid x:Name="TargetGrid">

在事件处理程序中,我试图使用 IScrollInfo.SetHorizontalOffset,但是可惜的是,DataGrid没有暴露出 IScrollInfo
private void SourceGrid_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
    ((IScrollInfo)TargetGrid).SetHorizontalOffset(e.HorizontalOffset);
    // cast to IScrollInfo fails
}

有没有其他方法可以完成这个任务?或者TargetGrid上是否有另一个元素可以公开必要的IScrollInfo以实现滚动位置的同步?

顺便说一下,我正在使用冻结列,因此无法在两个DataGrid控件周围包装ScrollViewers。

5个回答

3

2

是的。我以前也是用同样的方法做过同样的事情,但这似乎不应该是我们必须要通过可视化树来解决的问题。这只是 WPF 在某些方面还有待改进的又一例证。 - PeterAllenWebb
2
当用户更改视觉主题时,请小心 - 控件将获得新的模板(新的可视树),您将持有错误滚动视图器的引用。您应该在OnApplyTemplate中做出反应,并每次调用它时查找实际的ScrollViewer。请参见http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.onapplytemplate.aspx。 - Tomáš Kafka

1

当我们使用Infragistics表格时,我们遇到了相同的问题,因为它不支持冻结列(现在仍然不支持)。所以我们将两个网格并排放置,使其看起来像一个。左边的网格不会水平滚动,但右边的网格可以。这是一种简易版的冻结列。

总之,我们最终通过访问可视树并自己提取ScrollViewer解决了问题。毕竟,我们知道它在那里——只是对象模型没有暴露出来。如果WPF表格未公开ScrollViewer,则可以使用类似的方法。或者您可以对网格进行子类化,并添加所需的功能以使其工作。

有兴趣听听您为什么需要这样做。


我从Codeplex获得了WPF Toolkit DataGrid的源代码,因此我可能能够找到它并公开它(这不是我首选的方法)。我正在堆叠2个网格以实现冻结窗格效果(类似于Excel)。 - Philipp Schmid

1
您可以通过在用户控件初始化期间调用innerGridControl_ScrollChanged()处理程序来欺骗datagrid,以公开每个网格的ScrollViewer属性。 要公开它,您可以在xaml View文件中创建网格,然后在另一个xaml View中组成两个网格。 以下代码示例在innerGrid.xaml.cs中:
    public ScrollViewer Scroller { get; set; } // exposed ScrollViewer from the grid
    private bool _isFirstTimeLoaded = true; 

    private void innerGridControl_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        if (_isFirstTimeLoaded) // just to save the code from casting and assignment after 1st time loaded
        {
            var scroller = (e.OriginalSource) as ScrollViewer;
            Scroller = scroller;
            _isFirstTimeLoaded = false;
        }
    }

在OuterGridView.xaml中添加一个附加事件处理程序定义:
<Views:innerGridView Grid.Row="1" Margin="2,0,2,2" DataContext="{Binding someCollection}" 
                                      x:Name="grid1Control"
                                      ScrollViewer.ScrollChanged="Grid1Attached_ScrollChanged"
                                      ></Views:innerGridView>

<Views:innerGridView Grid.Row="3" Margin="2,0,2,2" DataContext="{Binding someCollection}" 
                                      x:Name="grid2Control"
                                      ScrollViewer.ScrollChanged="Grid2Attached_ScrollChanged"
                                      ></Views:innerGridView>

当发生另一个滚动事件时,访问公共的ScrollViewer.SetHorizontalOffset(e.HorizontalOffset)方法。 以下代码在OuterGridView.xaml.cs中的一个处理程序定义中:

private void Grid1Attached_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        if (e != null && !e.Handled)
        {
            if (e.HorizontalChange != 0.0)
            {
                grid2Control.Scroller.ScrollToHorizontalOffset(e.HorizontalOffset);
            }
            e.Handled = true;
        }
    }
private void Grid2Attached_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        if (e != null && !e.Handled)
        {
            if (e.HorizontalChange != 0.0)
            {
                grid1Control.Scroller.ScrollToHorizontalOffset(e.HorizontalOffset);
            }
            e.Handled = true;
        }
    }

还要确保内部网格中的任何其他scroll_changed事件(如果有的话,例如如果您在列数据模板中定义了一个带有默认滚动条的TextBox)都将其e.Handled设置为true,以防止外部网格处理它(这是由于路由事件的默认冒泡行为导致的)。或者,您可以在e.OriginalSource或e.Source上添加额外的if检查来过滤您打算处理的滚动事件。

1

这是一个很棒的解决方案。在WPF中对我非常有效。

http://www.codeproject.com/Articles/39244/Scroll-Synchronization

我刚刚提到了 ScrollSynchronizer dll, 并添加了一个 xml 导入:
xmlns:scroll="clr-namespace:ScrollSynchronizer"
然后只需将此添加到我的两个 datagrids 中,就完成了。
<DataGrid.Resources>
   <Style TargetType="ScrollViewer">
     <Setter Property="scroll:ScrollSynchronizer.ScrollGroup" Value="Group1" />
   </Style>
</DataGrid.Resources>

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