Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Custom WPF ScrollViewer

0.00/5 (No votes)
7 Dec 2018 2  
Making better use of the available UI space.

Introduction

Many WPF controls like the TreeView, ListView, and so forth, use a scrollbar and it is sometimes useful to use the UI space that a scrollbar covers, not just for the bar itself, but also for a button or even a mini tool bar. This tip explains the "how to" bit using a ListBox, but the same principal should really be applicable to any other control that uses a ScrollViewer since it is the ScrollViewer that it customized in the end.

Background

I was looking for a solution to put a ResizeGrip into the otherwise blank button - right corner of a pop-up control, but I found only bits and pieces, nevermind a complete solution. So, I developed a solution and am going to document the sample solution here in the hope that it can be useful to others.

The screenshots below shows a ListBox in the attached sample application. Notice that the ResizeGrip is positioned where you normally have a blank area.

Using the code

To understand this solution it is important to understand the concept of a ControlTemplate in WPF. A ControlTemplate is a very flexible way to completely re-define the order and way in which the parts of a control are layed out. To do this, we can simply download a sample ControlTemplate modify it as we see fit, and insert it into XAML using the Template dependency property.

To change the layout of the scrollbars of a ListBox, we are required to first re-template the ListBox, and then change the ScrollViewer's ControlTemplate inside that ListBox. So, here is the ListBox with the Template dependency property in the attached sample application:

<ListBox Template="{StaticResource defaulttemplate}"

         ItemsSource="{Binding MyStrings,RelativeSource={RelativeSource AncestorType={x:Type Window},Mode=FindAncestor}}"

/> 

We can see that the Template dependency property points to a static ControlTemplate resource called defaulttemplate:

<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>

The defaulttemplate of the ListBox in turn contains the scrollview we are about to customize. To customize it, it contains a Template reverance to another resource called ScrollViewTemplate:

 <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>

The ControlTemplate of the ScrollViewer is a little bit more complicated but is essantially a layout using a Grid with 2 rows and 2 columns. The bottom row contains the horizontal scrollbar and the ResizeGrip in the far right corner. The right column contains the vertical scrollbar with the same ResizeGrip in the bottom row.

All we need to apply this, is to have the two ControlTemplates in the Resource section of the application, and have the Template dependency property of the ListBox point at the ListBox template. The same technique can obviously be used to position a button or a number of buttons on the bottom left side of the listbox.
 

Thats it, all I wanted to share in this tip. Be sure to download the sample application if you still have questions. Let me know how this works for you and whether you have similar examples of customization.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here