自定义WPF ScrollViewer





5.00/5 (3投票s)
更好地利用可用的 UI 空间。
引言
许多 WPF 控件,如 TreeView、ListView 等,都使用滚动条,有时使用滚动条覆盖的 UI 空间很有用,不仅用于滚动条本身,还用于按钮甚至迷你工具栏。 本技巧使用 ListBox 解释了“如何”部分,但相同的原理应该真正适用于任何使用 ScrollViewer
的控件,因为它最终自定义的是 ScrollViewer
。
背景
我一直在寻找一种解决方案,将 ResizeGrip 放入弹出控件的空白按钮 - 右下角,但我只找到了零星片段,更不用说完整的解决方案了。 因此,我开发了一个解决方案,并在此处记录示例解决方案,希望它对其他人有用。
下面的屏幕截图显示了附加示例应用程序中的 ListBox。 注意,ResizeGrip 位于您通常有一个空白区域的位置。
使用代码
要理解此解决方案,重要的是要理解 WPF 中 ControlTemplate 的概念。 ControlTemplate
是一种非常灵活的方式,可以完全重新定义控件各个部分的顺序和布局方式。 为此,我们可以简单地下载一个示例 ControlTemplate
,根据需要修改它,然后使用 Template
依赖属性将其插入 XAML 中。
要更改 ListBox 的滚动条的布局,我们需要首先重新模板化 ListBox,然后在该 ListBox 中更改 ScrollViewer 的 ControlTemplate
。 因此,这是带有 Template
依赖属性的 ListBox,位于附加示例应用程序中
<ListBox Template="{StaticResource defaulttemplate}"
ItemsSource="{Binding MyStrings,RelativeSource={RelativeSource AncestorType={x:Type Window},Mode=FindAncestor}}"
/>
我们可以看到 Template
依赖属性指向一个名为 defaulttemplate 的静态 ControlTemplate
资源
<ControlTemplate x:Key="defaulttemplate" TargetType="{x:Type ListBox}">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
Padding="1"
SnapsToDevicePixels="True">
<ScrollViewer Focusable="False"
Padding="{TemplateBinding Padding}"
Template="{StaticResource ScrollViewTemplate}"
>
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</ScrollViewer>
</Border>
</ControlTemplate>
ListBox 的 defaulttemplate 又包含我们即将自定义的 scrollview。 要对其进行自定义,它包含对另一个名为 ScrollViewTemplate 的资源的 Template
引用
<ControlTemplate TargetType="{x:Type ScrollViewer}" x:Key="ScrollViewTemplate">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}">
<Grid Background="{TemplateBinding Background}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Display listbox content here -->
<ScrollContentPresenter Grid.Column="0" Grid.ColumnSpan="2"
Grid.Row="0" Grid.RowSpan="2"
KeyboardNavigation.DirectionalNavigation="Local"
CanContentScroll="True"
CanHorizontallyScroll="True"
CanVerticallyScroll="True"
/>
<!-- Display Vertical Scrollbar to the right -->
<ScrollBar Name="PART_VerticalScrollBar"
Grid.Column="1" Grid.Row="0"
Padding="0,0,0,3"
Value="{TemplateBinding VerticalOffset}"
Maximum="{TemplateBinding ScrollableHeight}"
ViewportSize="{TemplateBinding ViewportHeight}"
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"/>
<ScrollBar Name="PART_HorizontalScrollBar"
Grid.Column="0" Grid.Row="1"
Orientation="Horizontal"
Padding="0,0,6,0"
Value="{TemplateBinding HorizontalOffset}"
Maximum="{TemplateBinding ScrollableWidth}"
ViewportSize="{TemplateBinding ViewportWidth}"
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"/>
<DockPanel Grid.Column="1" Grid.Row="1"
LastChildFill="false"
Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
<ResizeGrip HorizontalAlignment="Right" VerticalAlignment="Stretch"
Grid.Column="1" Grid.Row="1"
Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
/>
</Grid>
</Border>
</ControlTemplate>
ScrollViewer 的 ControlTemplate
稍微复杂一些,但本质上是使用具有 2 行 2 列的 Grid
的布局。 底行包含水平滚动条和右下角的 ResizeGrip
。 右列包含垂直滚动条,底行包含相同的 ResizeGrip
。
我们只需要应用这一点,将两个 ControlTemplate
放在应用程序的资源部分,并让 ListBox 的 Template
依赖属性指向 ListBox 模板即可。 显然,可以使用相同的技术将按钮或多个按钮放置在列表框的左下侧。
就是这样,这就是我想在这个技巧中分享的内容。 如果您还有问题,请务必下载示例应用程序。 让我知道这是否对您有用,以及您是否有类似的自定义示例。