65.9K
CodeProject 正在变化。 阅读更多。
Home

自定义WPF ScrollViewer

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2018 年 12 月 7 日

CPOL

2分钟阅读

viewsIcon

19474

downloadIcon

314

更好地利用可用的 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 模板即可。 显然,可以使用相同的技术将按钮或多个按钮放置在列表框的左下侧。
 

就是这样,这就是我想在这个技巧中分享的内容。 如果您还有问题,请务必下载示例应用程序。 让我知道这是否对您有用,以及您是否有类似的自定义示例。

© . All rights reserved.