Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to modify the placement of DataGrid vertical scroll bar in WPF?

SOLVED: Please see my own answer below with the XAML code, a screenshot and an explanation.

It was a little tough to title this one, so let me explain what my issue is. I have a datagrid that has a defined height, so the scrollbar appears. I would like to contain the vertical scrollbar to the area that excludes the header. While it only scrolls the data rows and not the header, visually it covers the entire datagrid area to the right. The problem with that is that two boxes appear (one above and below) at the scrollbar area. I'm not sure how to get rid of them or how to contain the scrollbar to the body of the datagrid.

enter image description here

The only thing I've been able to figure out (and I don't like the look of it) is to set the Background of the DataGrid to Transparent. Here's the outcome:

enter image description here

As you see, the scrollbar is sticking out annoyingly. Then, there's also the problem of a gap between the horizontal scrollbar and the last row if the background is transparent:

enter image description here

There's also a solution that gets rid of the background color of the datagrid for those two boxes, making them stand out less:

http://social.msdn.microsoft.com/Forums/vstudio/en-US/9fc4252b-38b1-4369-8d76-b6c5ae1e4df5/how-to-remove-the-blank-space-above-the-verticalscroolbar-of-datagrid-in-wpf?forum=wpf

Similar solution can be found here: Annoying Square Where Scrollbars Meet

However, it doesn't get rid of the problem that the scrollbar is awkwardly sticking out on the side.

Something I tested was to separate header from the body and place the body into a vertical ScrollViewer, then place the header and the body into a horizontal ScrollViewer so that they can be both scrolled horizontally. But, as you imagine, this doesn't work out well, because you have to scroll to the right to see the verticall scrollbar. I'm sure there's a way to make it so that it stays frozen on the right, but I haven't figured it out. Another issue is the fact the header width has to match the largest possible width of any cell in that column, or everything will be off. Here's the outcome shown when scrolled all the way to the right:

enter image description here

I'm very new to control templates, so I can't figure out if this could work, because I can't find the right components:

If I give the vertical scrollbar a negative margin to the left (say -6,0,0,0) and a similarly sized padding to the right of the cell block (0,0,6,0), the vertical scrollbar technically should move to the left. I will continue experimenting and try to figure this out, unless someone has the answer for me (which would be awesome).

EDIT #1:

Well, I made some progress and was able to set the margin of the scrollbar to (-17,0,0,0). 17 appears to be the width of the scrollbar. There appears to be a specific key for its width:

http://msdn.microsoft.com/en-us/library/system.windows.systemparameters.verticalscrollbarwidthkey(v=vs.110).aspx

but I can't figured out a way to insert that as the negative offset value in the margin within XAML. It wouldn't be hard to do it in code-behind.... but I'd rather keep it all XAML. Anyways, here's the screenshot of the progress and the XAML code portion:

enter image description here

Here's XAML code portion:

    <Style TargetType="{x:Type DataGrid}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type DataGrid}">
                    <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="True">
                        <ScrollViewer x:Name="DG_ScrollViewer" Focusable="false">
                            <ScrollViewer.Template>
                                <ControlTemplate TargetType="{x:Type ScrollViewer}">
                                    <Grid>
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="Auto"/>
                                            <ColumnDefinition Width="*"/>
                                            <ColumnDefinition Width="Auto"/>
                                        </Grid.ColumnDefinitions>
                                        <Grid.RowDefinitions>
                                            <RowDefinition Height="Auto"/>
                                            <RowDefinition Height="*"/>
                                            <RowDefinition Height="Auto"/>
                                        </Grid.RowDefinitions>
                                        <Button Command="{x:Static DataGrid.SelectAllCommand}" Focusable="false" Style="{DynamicResource {ComponentResourceKey ResourceId=DataGridSelectAllButtonStyle, TypeInTargetAssembly={x:Type DataGrid}}}" Visibility="{Binding HeadersVisibility, ConverterParameter={x:Static DataGridHeadersVisibility.All}, Converter={x:Static DataGrid.HeadersVisibilityConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" Width="{Binding CellsPanelHorizontalOffset, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/>
                                        <DataGridColumnHeadersPresenter x:Name="PART_ColumnHeadersPresenter" Grid.Column="1" Visibility="{Binding HeadersVisibility, ConverterParameter={x:Static DataGridHeadersVisibility.Column}, Converter={x:Static DataGrid.HeadersVisibilityConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/>
                                        <ScrollContentPresenter Margin="0,0,17,0" x:Name="PART_ScrollContentPresenter" CanContentScroll="{TemplateBinding CanContentScroll}" Grid.ColumnSpan="2" Grid.Row="1"/>
                                        <ScrollBar Margin="-17,0,0,0" x:Name="PART_VerticalScrollBar" Grid.Column="2" Maximum="{TemplateBinding ScrollableHeight}" Orientation="Vertical" Grid.Row="1" Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" Value="{Binding VerticalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" ViewportSize="{TemplateBinding ViewportHeight}"/>
                                        <Grid Grid.Column="1" Grid.Row="2">
                                            <Grid.ColumnDefinitions>
                                                <ColumnDefinition Width="{Binding NonFrozenColumnsViewportHorizontalOffset, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/>
                                                <ColumnDefinition Width="*"/>
                                            </Grid.ColumnDefinitions>
                                            <ScrollBar x:Name="PART_HorizontalScrollBar" Grid.Column="1" Maximum="{TemplateBinding ScrollableWidth}" Orientation="Horizontal" Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" Value="{Binding HorizontalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" ViewportSize="{TemplateBinding ViewportWidth}"/>
                                        </Grid>
                                    </Grid>
                                </ControlTemplate>
                            </ScrollViewer.Template>
                            <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                        </ScrollViewer>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

The important part that makes the change possible is halfway through that code snippet where I set Margin="-17,0,0,0" for ScrollBar.

My current problem is that I can't figure out which component to offset to the left by 17 (by adding either margin or padding). I've been messing with all of them and no luck thus far. I'll update as soon as I get it, unless someone figures it out before me. As it stands now, scrollbar will overlay anything in that last column until I fix the offset.

EDIT #2:

Please refer to the updated XAML code above. What I did was add Margin="0,0,17,0" to ScrollContentPresenter.

One side effect effect, which I can completely live with, is the fact that offsets the header as well, but you don't see that until you scroll all the way to the right. It effects only the header of the last column, because the ScrollContentPresenter offsets it as well... strangely, since there is a DataGridColumnHeadersPresenter, but it works independently... so, I'll keep on working on it. ScrollContentPresenter, unfortunately, doesn't have padding, which would work like a charm. So, now I have to figure out how I can pad it, rather than setting a margin, or figure out a different method.

A similar method is to set the margin of the horizontal ScrollBar (that's the one in the second grid) to 0,0,-17,0. It'll move it 17 units to the right. Then, set the margin of DataGridColumnHeaderPresenter to 0,0,-17,0. It'll move it 17 units to the right.

EDIT #3:

Here's another method for anyone that's interested:

enter image description here

Set the margin of vertical ScrollBar to 0,-22,0,-17. -22 is the height of my headers, so adjust it to whatever yours are. This method stretches the scrollbar to cover the two white boxes.

EDIT #4:

I figured out the solution. Please see my answer for the XAML code, a screenshot and an explanation.

like image 899
B.K. Avatar asked Feb 13 '23 21:02

B.K.


2 Answers

So, above I've played around with several methods, but none of them achieved that perfect look and fill that I wanted. So, here's the solution that I'm very satisfied with. I hope that it helps others. I used Blend to go through every line of the template code and this is the best combination of things for my purpose.

Final result:

enter image description here

Pardon the messy headers, I covered them up on purpose. As you can see, the vertical scroll bar lines up nicely between the headers and the horizontal scroll bar located at the bottom. This was accomplished through setting margin of vertical ScrollBar (located in the first grid of the template) to -17 on the left side. Margin="-17,0,0,0". I then set the margin of ScrollContentProperty, located right above the vertical scroll bar within the template code, to 17 on the right side, Margin="0,0,17,0, to ensure that the vertical scroll bar doesn't cover any content. This leaves us with the header having 17 units of empty space to the right when we scroll to the extreme right, and that's due to the fact that our 17 margin on ScrollContentPresenter effects the header as well. To counter that, I created a style for DataGridColumnHeader and set its margin to -17 on the right side, Margin="0,0,-17,0", ensuring that it bleeds over into the empty space. To ensure that the last column header text is not hovering over the vertical scroll bar, making it visually unappealing, I added a padding of 17 to the right side, Padding="0,0,17,0".

Here's my XAML code for the DataGrid and DataGridColumnHeader that made this possible:

DataGrid:

    <Style TargetType="{x:Type DataGrid}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type DataGrid}">
                    <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="True">
                        <ScrollViewer x:Name="DG_ScrollViewer" Focusable="false">
                            <ScrollViewer.Template>
                                <ControlTemplate TargetType="{x:Type ScrollViewer}">
                                    <Grid>
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="Auto"/>
                                            <ColumnDefinition Width="*"/>
                                            <ColumnDefinition Width="Auto"/>
                                        </Grid.ColumnDefinitions>
                                        <Grid.RowDefinitions>
                                            <RowDefinition Height="Auto"/>
                                            <RowDefinition Height="*"/>
                                            <RowDefinition Height="Auto"/>
                                        </Grid.RowDefinitions>
                                        <Button Command="{x:Static DataGrid.SelectAllCommand}" Focusable="false" Style="{DynamicResource {ComponentResourceKey ResourceId=DataGridSelectAllButtonStyle, TypeInTargetAssembly={x:Type DataGrid}}}" Visibility="{Binding HeadersVisibility, ConverterParameter={x:Static DataGridHeadersVisibility.All}, Converter={x:Static DataGrid.HeadersVisibilityConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" Width="{Binding CellsPanelHorizontalOffset, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/>
                                        <DataGridColumnHeadersPresenter x:Name="PART_ColumnHeadersPresenter" Grid.Column="1" Visibility="{Binding HeadersVisibility, ConverterParameter={x:Static DataGridHeadersVisibility.Column}, Converter={x:Static DataGrid.HeadersVisibilityConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/>
                                        <!--This is the scroll content presenter that gets shifted to the left 17 units so that scrollbar doesn't cover it-->
                                        <ScrollContentPresenter Margin="0,0,17,0" x:Name="PART_ScrollContentPresenter" CanContentScroll="{TemplateBinding CanContentScroll}" Grid.ColumnSpan="2" Grid.Row="1"/>
                                        <!--This is the vertical scrollbar. Margin is used to shift it to the left 17 units over the content-->
                                        <ScrollBar Margin="-17,0,0,0" x:Name="PART_VerticalScrollBar" Grid.Column="2" Maximum="{TemplateBinding ScrollableHeight}" Orientation="Vertical" Grid.Row="1" Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" Value="{Binding VerticalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" ViewportSize="{TemplateBinding ViewportHeight}"/>
                                        <Grid Grid.Column="1" Grid.Row="2">
                                            <Grid.ColumnDefinitions>
                                                <ColumnDefinition Width="{Binding NonFrozenColumnsViewportHorizontalOffset, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/>
                                                <ColumnDefinition Width="*"/>
                                            </Grid.ColumnDefinitions>
                                            <ScrollBar x:Name="PART_HorizontalScrollBar" Grid.Column="1" Maximum="{TemplateBinding ScrollableWidth}" Orientation="Horizontal" Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" Value="{Binding HorizontalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" ViewportSize="{TemplateBinding ViewportWidth}"/>
                                        </Grid>
                                    </Grid>
                                </ControlTemplate>
                            </ScrollViewer.Template>
                            <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                        </ScrollViewer>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

DataGridColumnHeader:

    <Style TargetType="{x:Type DataGridColumnHeader}">
        <Setter Property="Height" Value="22"/>
        <!--Padding to shift header text of the last column to the left 17 units-->
        <Setter Property="Padding" Value="0,0,17,0"/>
        <!--Margin to shift the entire header to the right 17 units to fill the void-->
        <Setter Property="Margin" Value="0,0,-17,0"/>
    </Style>

A similar method would be to set margin of the horizontal scroll bar to -17, Margin="0,0,-17,0", set padding of DataGridColumnHeader to 17, Padding="0,0,17,0", and its margin to -17, Margin="0,0,-17,0". This achieves the same result.

like image 148
B.K. Avatar answered Feb 16 '23 11:02

B.K.


Better solution is set up Grid.Row="0" Grid.RowSpan="2" for Name="PART_VerticalScrollBar"

like image 25
Maxim Mikhisor Avatar answered Feb 16 '23 11:02

Maxim Mikhisor