ListView页脚行 - 水平滚动

时间:2013-08-03 09:38:51

标签: c# wpf listview scroll footer

我想要一个带有页脚行的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如何处理滚动?

1 个答案:

答案 0 :(得分:2)

您必须将页脚行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.cs中的RenewEvents()方法中找到魔法(从第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事件Parent HeaderScrollViewer )和TemplatedParent MainScrollViewer < / em>)然后使用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);
    }
}