问题
I have made a ScrollViewer style which is designed to show scrollbars overlaying the scrollable content. I produced it based on other stackoverflow posts such as this.
The difference between a lot of other scenarios and MY scenario, is that I am applying my scrollviewer style to a ListView which includes a header. Generally, users would expect a scrollviewer to not overlay the headers as in my image below. (I've highlighted the bar in orange to make it more obvious)
(The bar should stop before the header starts. It's important to mention that the first element(s) of the scrollable content are NOT hidden underneath the header. In the image I have scrolled the bar down ~15%.)
I have tried anchoring the scrollbars in my scrollviewer to the bottom and setting their height to "ViewportHeight" and also tried placing various elements in the second row (row #1) of the grid (see code below) to no avail.
How can I get the scrollviewer to stop before the base of the headers?
<Style x:Key="StandardScrollViewer" TargetType="{x:Type ScrollViewer}">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="{DynamicResource BackgroundGreyLevel2}" />
<Setter Property="Background" Value="{DynamicResource CollectionControlBackgroundGradient}" />
<Setter Property="VerticalScrollBarVisibility" Value="Hidden"></Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ScrollViewer}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<DockPanel Margin="{TemplateBinding Padding}" Grid.ColumnSpan="2" Grid.RowSpan="1">
<ScrollViewer DockPanel.Dock="Top" Grid.ColumnSpan="2" Grid.RowSpan="2" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden" Focusable="False">
<GridViewHeaderRowPresenter x:Name="GridViewHeaderRowPresenter" 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="{TemplateBinding CanContentScroll}" Grid.ColumnSpan="2" Grid.RowSpan="2"/>
</DockPanel>
<ScrollBar Name="PART_VerticalScrollBar"
HorizontalAlignment="Right"
Grid.Column="1"
Grid.Row="0"
VerticalAlignment="Stretch"
Value="{TemplateBinding VerticalOffset}"
Maximum="{TemplateBinding ScrollableHeight}"
ViewportSize="{TemplateBinding ViewportHeight}"
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" />
<ScrollBar Name="PART_HorizontalScrollBar"
VerticalAlignment="Bottom"
Orientation="Horizontal"
Grid.Row="1"
Value="{TemplateBinding HorizontalOffset}"
Maximum="{TemplateBinding ScrollableWidth}"
ViewportSize="{TemplateBinding ViewportWidth}"
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
回答1:
This was a lot easier to fix than I imagined, thanks in part to the fact that (contrary to my belief) WPF is not precious about the source WPF ordering/positioning of the elements which render the scroll content and the scroll bars (everything within the dockpanel and the two scrollbars above).
As such, it was as simple as adding an additional (3rd) row to the view, into which I can place my headers. Then I simply changed the assigned row of the content and vertical scroll bar to +1 what it was before.
The result is a scrollbar which is contained within the same row as the scroll content, which therefore doesn't leak over onto the headers.
<ControlTemplate TargetType="{x:Type ScrollViewer}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<GridViewHeaderRowPresenter x:Name="GridViewHeaderRowPresenter" Margin="2,0,2,0" Grid.Row="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}"/>
<DockPanel Margin="{TemplateBinding Padding}" Grid.Row="1" Grid.ColumnSpan="2" Grid.RowSpan="1">
<ScrollViewer DockPanel.Dock="Top" Grid.Row="1" Grid.ColumnSpan="2" Grid.RowSpan="2" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden" Focusable="False">
</ScrollViewer>
<ScrollContentPresenter Name="PART_ScrollContentPresenter" KeyboardNavigation.DirectionalNavigation="Local" CanContentScroll="{TemplateBinding CanContentScroll}" Grid.ColumnSpan="2" Grid.RowSpan="2"/>
</DockPanel>
<ScrollBar Name="PART_VerticalScrollBar"
HorizontalAlignment="Right"
Grid.Column="1"
Grid.Row="1"
VerticalAlignment="Stretch"
Value="{TemplateBinding VerticalOffset}"
Maximum="{TemplateBinding ScrollableHeight}"
ViewportSize="{TemplateBinding ViewportHeight}"
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" />
<ScrollBar Name="PART_HorizontalScrollBar"
VerticalAlignment="Bottom"
Orientation="Horizontal"
Grid.Row="1"
Value="{TemplateBinding HorizontalOffset}"
Maximum="{TemplateBinding ScrollableWidth}"
ViewportSize="{TemplateBinding ViewportWidth}"
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"/>
</Grid>
</ControlTemplate>
The above is the only section of code which I altered.
Also, for reference, this article is very helpful.
来源:https://stackoverflow.com/questions/52725066/wpf-how-to-prevent-scrollbar-from-overlaying-headers-in-list-type-views