GridViewHeaderRowPresenter

GridViewHeaderRowPresenter

我想拥有一个带有页脚行的ListView,该页脚行不会与其余项目垂直滚动,它应该一直可见。我已经使用以下模板完成了此操作:

<Style x:Key="FrozenRowListView" TargetType="ListView">
    <Setter Property="SnapsToDevicePixels" Value="true" />
    <Setter Property="OverridesDefaultStyle" Value="true" />
    <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto" />
    <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
    <Setter Property="ScrollViewer.CanContentScroll" Value="true" />
    <Setter Property="VerticalContentAlignment" Value="Center" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ListView">
                <Border Name="Border" BorderThickness="1">
                    <ScrollViewer Style="{StaticResource FrozenRowScrollViewer}">
                        <ItemsPresenter />
                    </ScrollViewer>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<Style x:Key="FrozenRowScrollViewer" TargetType="ScrollViewer">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ScrollViewer">
                <Grid Background="{TemplateBinding Background}">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*" />
                        <ColumnDefinition Width="Auto" />
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*" />
                        <RowDefinition Height="Auto" />
                    </Grid.RowDefinitions>

                    <DockPanel Margin="{TemplateBinding Padding}">
                        <ScrollViewer DockPanel.Dock="Bottom"
                                        HorizontalScrollBarVisibility="Hidden"
                                          VerticalScrollBarVisibility="Hidden"
                                          Focusable="false">
                            <GridViewRowPresenter
                                Margin="2,0,2,0"
                                Content="{Binding Path=TemplatedParent.ItemsSource, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource summaryConverter}}"
                                Columns="{Binding Path=TemplatedParent.View.Columns, RelativeSource={RelativeSource TemplatedParent}}"
                                SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                        </ScrollViewer>
                        <ScrollViewer DockPanel.Dock="Top"
                                        HorizontalScrollBarVisibility="Hidden"
                                        VerticalScrollBarVisibility="Hidden"
                                        Focusable="false">
                            <GridViewHeaderRowPresenter DockPanel.Dock="Top"
                                    Margin="2,0,2,0"
                                    Columns="{Binding Path=TemplatedParent.View.Columns, RelativeSource={RelativeSource TemplatedParent}}"
                                    ColumnHeaderContainerStyle="{Binding Path=TemplatedParent.View.ColumnHeaderContainerStyle, RelativeSource={RelativeSource TemplatedParent}}"
                                    ColumnHeaderTemplate="{Binding Path=TemplatedParent.View.ColumnHeaderTemplate, RelativeSource={RelativeSource TemplatedParent}}"
                                    ColumnHeaderTemplateSelector="{Binding Path=TemplatedParent.View.ColumnHeaderTemplateSelector, RelativeSource={RelativeSource TemplatedParent}}"
                                    AllowsColumnReorder="{Binding Path=TemplatedParent.View.AllowsColumnReorder, RelativeSource={RelativeSource TemplatedParent}}"
                                    ColumnHeaderContextMenu="{Binding Path=TemplatedParent.View.ColumnHeaderContextMenu, RelativeSource={RelativeSource TemplatedParent}}"
                                    ColumnHeaderToolTip="{Binding Path=TemplatedParent.View.ColumnHeaderToolTip, RelativeSource={RelativeSource TemplatedParent}}"
                                    SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                        </ScrollViewer>

                        <ScrollContentPresenter Name="PART_ScrollContentPresenter"
                                                KeyboardNavigation.DirectionalNavigation="Local"
                                                CanContentScroll="True"
                                                CanHorizontallyScroll="False"
                                                CanVerticallyScroll="False" />
                    </DockPanel>

                    <ScrollBar Name="PART_HorizontalScrollBar"
                               Orientation="Horizontal"
                               Grid.Row="1"
                               Maximum="{TemplateBinding ScrollableWidth}"
                               ViewportSize="{TemplateBinding ViewportWidth}"
                               Value="{TemplateBinding HorizontalOffset}"
                               Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" />

                    <ScrollBar Name="PART_VerticalScrollBar"
                               Grid.Column="1"
                               Maximum="{TemplateBinding ScrollableHeight}"
                               ViewportSize="{TemplateBinding ViewportHeight}"
                               Value="{TemplateBinding VerticalOffset}"
                               Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" />

                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>


这可以正常工作,但是有一个问题。页脚不会像列标题和其他项目一样水平滚动。我已经将额外的GridViewRowPresenter放入ScrollViewer中,就像GridViewHeaderRowPresenter一样。似乎标题行必须具有某种挂钩才能跟随滚动偏移量。

如何使这项工作? GridViewHeaderRowPresenter如何处理滚动?

最佳答案

您必须将页脚行ScrollViewer.HorizontalOffsetPART_HorizontalScrollBar.Value同步。但是这里的this之类的解决方案将行不通。
我建议使用附加属性。看起来很复杂,但事实并非如此。

public static class ScrollViewerBinding
{
    public static double GetHorizontalOffset(DependencyObject depObj)
    {
        return (double)depObj.GetValue(HorizontalOffsetProperty);
    }

    public static void SetHorizontalOffset(DependencyObject depObj, double value)
    {
        depObj.SetValue(HorizontalOffsetProperty, value);
    }

    public static readonly DependencyProperty HorizontalOffsetProperty =
        DependencyProperty.RegisterAttached("HorizontalOffset",
                                            typeof(double),
                                            typeof(ScrollViewerBinding),
                                            new PropertyMetadata(OnHorizontalOffsetPropertyChanged));

    private static void OnHorizontalOffsetPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ScrollViewer sv = d as ScrollViewer;
        if (sv != null)
        {
            sv.ScrollToHorizontalOffset((double)e.NewValue);
        }
    }
}


然后你可以像这样绑定

<ScrollViewer DockPanel.Dock="Bottom"
              local:ScrollViewerBinding.HorizontalOffset="{Binding Value, ElementName=PART_HorizontalScrollBar}"




现在,您要回答第二个问题:GridViewHeaderRowPresenter是如何执行的?

您可以在GridViewHeaderRowPresenter.csRenewEvents()方法中找到魔术(从946行开始)。

// find needed elements and hook up events
private void RenewEvents()
{
    ScrollViewer oldHeaderSV = _headerSV;
    _headerSV = Parent as ScrollViewer;
    if (oldHeaderSV != _headerSV)
    {
        if (oldHeaderSV != null)
        {
            oldHeaderSV.ScrollChanged -= new ScrollChangedEventHandler(OnHeaderScrollChanged);
        }
        if (_headerSV != null)
        {
            _headerSV.ScrollChanged += new ScrollChangedEventHandler(OnHeaderScrollChanged);
        }
    }

    ScrollViewer oldSV = _mainSV; // backup the old value
    _mainSV = TemplatedParent as ScrollViewer;

    if (oldSV != _mainSV)
    {
        if (oldSV != null)
        {
            oldSV.ScrollChanged -= new ScrollChangedEventHandler(OnMasterScrollChanged);
        }

        if (_mainSV != null)
        {
            _mainSV.ScrollChanged += new ScrollChangedEventHandler(OnMasterScrollChanged);
        }
    }
    ...


如您所见,GridViewHeaderRowPresenter关联ScrollChanged(HeaderScrollViewer)和Parent(MainScrollViewer)的TemplatedParent事件,然后也使用ScrollToHorizontalOffset方法处理该事件(从第1034行开始)。

// The following two scroll changed methods will not be called recursively and lead to dead loop.
// When scrolling _masterSV, OnMasterScrollChanged will be called, so _headerSV also scrolled
// to the same offset. Then, OnHeaderScrollChanged be called, and try to scroll _masterSV, but
// it's already scrolled to that offset, so OnMasterScrollChanged will not be called.

// When master scroll viewer changed its offset, change header scroll viewer accordingly
private void OnMasterScrollChanged(object sender, ScrollChangedEventArgs e)
{
    if (_headerSV != null && _mainSV == e.OriginalSource)
    {
        _headerSV.ScrollToHorizontalOffset(e.HorizontalOffset);
    }
}

// When header scroll viewer changed its offset, change master scroll viewer accordingly
private void OnHeaderScrollChanged(object sender, ScrollChangedEventArgs e)
{
    if (_mainSV != null && _headerSV == e.OriginalSource)
    {
        _mainSV.ScrollToHorizontalOffset(e.HorizontalOffset);
    }
}

08-16 15:38