我想要一个带有页脚行的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如何处理滚动?
答案 0 :(得分:2)
您必须将页脚行ScrollViewer.HorizontalOffset
与PART_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);
}
}