user3235445
user3235445

Reputation: 155

GridViewHeaderRowPresenter scrolls with custom TreeListView control

I have a custom TreeListView - based on the standard TreeView - with the following style:

<Style TargetType="{x:Type c:TreeListView}">
   <Setter Property="Template">
      <Setter.Value>
         <ControlTemplate TargetType="{x:Type: c:TreeListView}">
            <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
               <DockPanel>
                  <GridViewHeaderRowPresenter DockPanel.Dock="Top" Columns="{Binding Path=Columns, RelativeSource={RelativeSource TemplatedParent}}" />
                  <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
               </DockPanel>
            </Border>
         </ControlTemplate>
      </Setter.Value>
   </Setter>
</Style>

As I have no ScrollViewer, the content will not scroll so if the content requires more space than is available, it looks like this:

enter image description here

So I added a ScrollViewer so that the style now Looks like this:

<Style TargetType="{x:Type c:TreeListView}">
   <Setter Property="Template">
      <Setter.Value>
         <ControlTemplate TargetType="{x:Type: c:TreeListView}">
            <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
               <ScrollViewer Focusable="False" Padding="{TemplateBinding Padding">
                  <DockPanel>
                     <GridViewHeaderRowPresenter DockPanel.Dock="Top" Columns="{Binding Path=Columns, RelativeSource={RelativeSource TemplatedParent}}" />
                     <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                  </DockPanel>
               </ScrollViewer>
            </Border>
         </ControlTemplate>
      </Setter.Value>
   </Setter>
</Style>

With the content expanded as in the first Image, the Content is not able to scroll, but the column Header scrools as well, which is not wished for :D

enter image description here

Could anyone please help me so that only the content gets scrolled, and not the columns bound to the GridViewHeaderRowPresenter?

Thank you!

[EDIT]

Thanks to the related links to the right, I found the answer here.

Instead of having the DockPanel within the ScrollViewer I needed only to have the ItemsPresenter inside the ScrollViewer. So with the following Style it now works.

<Style TargetType="{x:Type c:TreeListView}">
   <Setter Property="Template">
      <Setter.Value>
         <ControlTemplate TargetType="{x:Type c:TreeListView}">
            <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
               <DockPanel>
                  <GridViewHeaderRowPresenter DockPanel.Dock="Top" Columns="{Binding Path=Columns, RelativeSource={RelativeSource TemplatedParent}}" />
                  <ScrollViewer Focusable="False" Padding="{TemplateBinding Padding}">
                     <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                  </ScrollViewer>
               </DockPanel>
            </Border>
         </ControlTemplate>
      </Setter.Value>
   </Setter>
</Style>

Many thanks to Nikhil Agrawal for his code!

Upvotes: 2

Views: 1834

Answers (2)

Kirill Osenkov
Kirill Osenkov

Reputation: 8986

The way DataGrid solves it is it customizes the Template of the ScrollViewer to place the GridViewHeaderRowPresenter outside the ScrollViewer ScrollContentPresenter:

https://github.com/dotnet/wpf/blob/059ba38771aef10d2172a3ca0e247dfbfa8cefde/src/Microsoft.DotNet.Wpf/src/Themes/PresentationFramework.Aero2/Themes/Aero2.NormalColor.xaml#L987-L993

This way when the scrollbars scroll, they don't impact the header row.

The ListView that uses GridView does a similar thing here: https://github.com/dotnet/wpf/blob/059ba38771aef10d2172a3ca0e247dfbfa8cefde/src/Microsoft.DotNet.Wpf/src/Themes/PresentationFramework.Aero2/Themes/Aero2.NormalColor.xaml#L2628

At the same time they wrap the header row in another ScrollViewer, and honestly I don't understand why.

Once I find out more I will expand on this answer. I think this approach if it works is cleaner than synchronizing scroll viewers.

Upvotes: 0

luisv
luisv

Reputation: 491

The solution given is incomplete and will only work if you have a fixed width control. By only wrapping the ItemsPresenter the header columns will not scroll horizontally if needed. This results in shifted value columns which then mismatch the headers.

I've tried applying SelectiveScrollingGrid.SelectiveScrollingOrientation="Horizontal" to GridViewHeaderRowPresenter but haven't been successfull with that approach.

Instead, you could add a scroll viewer to the GridViewHeaderRowPresenter and to the ItemsPresenter and then synchronize them. It's not the best solution, but at least it works.

<Style TargetType="{x:Type local:TreeListView}">
<Setter Property="Template">
    <Setter.Value>
        <ControlTemplate TargetType="{x:Type local:TreeListView}">
            <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
                <DockPanel>
                    <ScrollViewer DockPanel.Dock="Top"
                                  HorizontalScrollBarVisibility="Hidden"
                                  Name="scrollViewerHeader"
                                  VerticalScrollBarVisibility="Disabled">
                        <GridViewHeaderRowPresenter Columns="{StaticResource gvcc}"/>
                    </ScrollViewer>
                    <ScrollViewer HorizontalScrollBarVisibility="Auto"
                                  Name="scrollViewerBody"
                                  VerticalScrollBarVisibility="Auto">
                        <ItemsPresenter />
                    </ScrollViewer>
                </DockPanel>
            </Border>
        </ControlTemplate>
    </Setter.Value>
</Setter>

And the code behind:

public class TreeListView : TreeView
{
    private ScrollViewer _scrollViewerHeader;
    private ScrollViewer _scrollViewerBody;

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        _scrollViewerHeader = (ScrollViewer)Template.FindName("scrollViewerHeader", this);
        _scrollViewerBody = (ScrollViewer)Template.FindName("scrollViewerBody", this);

        _scrollViewerBody.ScrollChanged += (sender, e) =>
                                            {
                                                _scrollViewerHeader.Width = e.ViewportWidth;
                                                _scrollViewerHeader.ScrollToHorizontalOffset(e.HorizontalOffset);
                                            };
    }
}

Upvotes: 1

Related Questions