WPF DataGrid: how do I stop auto scrolling when a cell is clicked?

16,289

Solution 1

You can access the DataGrid's internal ScrollViewer by modifying the template. Although normally you wouldn't put an event handler to code behind in a template, if you declare the template inline you can treat the event handler the same way you are when you attach it to the DataGrid itself. This is the default template as generated from Blend including an added handler on the ScrollViewer for the RequestBringIntoView event:

<ControlTemplate TargetType="{x:Type Controls:DataGrid}">
<Border SnapsToDevicePixels="True" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}">
    <ScrollViewer x:Name="DG_ScrollViewer" Focusable="False" RequestBringIntoView="DG_ScrollViewer_RequestBringIntoView">
        <ScrollViewer.Template>
            <ControlTemplate TargetType="{x:Type ScrollViewer}">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="*"/>
                        <RowDefinition Height="Auto"/>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="Auto"/>
                    </Grid.ColumnDefinitions>
                    <Button Width="{Binding CellsPanelHorizontalOffset, RelativeSource={RelativeSource FindAncestor, AncestorLevel=1, AncestorType={x:Type Controls:DataGrid}}}" Focusable="False">
                        <Button.Visibility>
                            <Binding Path="HeadersVisibility" RelativeSource="{RelativeSource FindAncestor, AncestorLevel=1, AncestorType={x:Type Controls:DataGrid}}">
                                <Binding.ConverterParameter>
                                    <Controls:DataGridHeadersVisibility>All</Controls:DataGridHeadersVisibility>
                                </Binding.ConverterParameter>
                            </Binding>
                        </Button.Visibility>
                        <Button.Template>
                            <ControlTemplate TargetType="{x:Type Button}">
                                <Grid>
                                    <Rectangle x:Name="Border" Fill="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" SnapsToDevicePixels="True"/>
                                    <Polygon x:Name="Arrow" Fill="Black" Stretch="Uniform" HorizontalAlignment="Right" Margin="8,8,3,3" VerticalAlignment="Bottom" Opacity="0.15" Points="0,10 10,10 10,0"/>
                                </Grid>
                                <ControlTemplate.Triggers>
                                    <Trigger Property="IsMouseOver" Value="True">
                                        <Setter Property="Stroke" TargetName="Border" Value="{DynamicResource {x:Static SystemColors.ControlDarkBrushKey}}"/>
                                    </Trigger>
                                    <Trigger Property="IsPressed" Value="True">
                                        <Setter Property="Fill" TargetName="Border" Value="{DynamicResource {x:Static SystemColors.ControlDarkBrushKey}}"/>
                                    </Trigger>
                                    <Trigger Property="IsEnabled" Value="False">
                                        <Setter Property="Visibility" TargetName="Arrow" Value="Collapsed"/>
                                    </Trigger>
                                </ControlTemplate.Triggers>
                            </ControlTemplate>
                        </Button.Template>
                        <Button.Command>
                            <RoutedCommand/>
                        </Button.Command>
                    </Button>
                    <Custom:DataGridColumnHeadersPresenter x:Name="PART_ColumnHeadersPresenter" Grid.Column="1">
                        <Custom:DataGridColumnHeadersPresenter.Visibility>
                            <Binding Path="HeadersVisibility" RelativeSource="{RelativeSource FindAncestor, AncestorLevel=1, AncestorType={x:Type Controls:DataGrid}}">
                                <Binding.ConverterParameter>
                                    <Controls:DataGridHeadersVisibility>Column</Controls:DataGridHeadersVisibility>
                                </Binding.ConverterParameter>
                            </Binding>
                        </Custom:DataGridColumnHeadersPresenter.Visibility>
                    </Custom:DataGridColumnHeadersPresenter>
                    <ScrollContentPresenter x:Name="PART_ScrollContentPresenter" Grid.ColumnSpan="2" Grid.Row="1" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}" ContentTemplate="{TemplateBinding ContentTemplate}" CanContentScroll="{TemplateBinding CanContentScroll}" CanHorizontallyScroll="False" CanVerticallyScroll="False"/>
                    <ScrollBar x:Name="PART_VerticalScrollBar" Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" Grid.Column="2" Grid.Row="1" Maximum="{TemplateBinding ScrollableHeight}" Value="{Binding VerticalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" Orientation="Vertical" ViewportSize="{TemplateBinding ViewportHeight}"/>
                    <Grid Grid.Column="1" Grid.Row="2">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="{Binding NonFrozenColumnsViewportHorizontalOffset, RelativeSource={RelativeSource FindAncestor, AncestorLevel=1, AncestorType={x:Type Controls:DataGrid}}}"/>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>
                        <ScrollBar x:Name="PART_HorizontalScrollBar" Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" Grid.Column="1" Maximum="{TemplateBinding ScrollableWidth}" Value="{Binding HorizontalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" Orientation="Horizontal" ViewportSize="{TemplateBinding ViewportWidth}"/>
                    </Grid>
                </Grid>
            </ControlTemplate>
        </ScrollViewer.Template>
        <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
    </ScrollViewer>
</Border>

Solution 2

Define an EventSetter in the DataGrid.RowStyle to call a handler that prevents the row from being brought into view:

XAML

<DataGrid>
    <DataGrid.RowStyle>
        <Style TargetType="{x:Type DataGridRow}">  
            <EventSetter Event="Control.RequestBringIntoView" Handler="DataGrid_Documents_RequestBringIntoView"  />
        </Style>
    </DataGrid.RowStyle>
</DataGrid>

Handler

private void DataGrid_Documents_RequestBringIntoView(object sender, RequestBringIntoViewEventArgs e)
{
    e.Handled = true;      
}

Solution 3

I took more time to have a look at this problem as my first solution wasn't working.

However the answer of John is almost the good one. The trick is to catch the RequestBringIntoView event BEFORE it gets to the ScrollViewer in order to mark it has handled.

If you don't have to refine the whole template, you can use the following code:

var scp = TreeHelper.FindVisualChild<ScrollContentPresenter>(this.datagrid);
scp.RequestBringIntoView += (s, e) => e.Handled = true;

We use the ScrollContentPresenter because it's just below the ScrollViewer in the visual tree.

Hope this helps !

Solution 4

I had the same problem as Rumit, but found a solution/hack.

I thought if I could find a way to differentiate between mouse clicks and arrow keys, then I could set e.Handled accordingly.

After some experimentation I found that e.OriginalSource changed depending on mouse or arrow key. For a mouse click, the handler for RequestBringIntoView is called once and e.OriginalSource was of type DataGridCell. For an arrow key, the handler is called twice and e.OriginalSource is of types DataGridRow and then DataGridCell.

The code for my handler is:

e.Handled = (e.OriginalSource is DataGridCell);

This seems like a bit of a hack, but works great for me.

Solution 5

I had the same problem and Jan's answer helped me. The only missing thing was that ScrollContentPresenter will be found only after Loaded event occurs. I created an extended DataGrid class inherited from DataGrid with additional property AutoScroll to control if I want the grid to scroll automatically or not.

Here's the class:

using System.Windows;
using System.Windows.Controls;
using Microsoft.Windows.Controls;

namespace Bartosz.Wojtowicz.Wpf
{
    public class ExtendedDataGrid : DataGrid
    {
        public bool AutoScroll { get; set; }

        public ExtendedDataGrid()
        {
            AutoScroll = true;
            Loaded += OnLoaded;
        }

        private void OnLoaded(object sender, RoutedEventArgs eventArgs)
        {
            if (!AutoScroll)
            {
                ScrollContentPresenter scp = DataGridHelper.GetVisualChild<ScrollContentPresenter>(this);
                if (scp != null) scp.RequestBringIntoView += OnRequestBringIntoView;
            }
        }

        private static void OnRequestBringIntoView(object sender, RequestBringIntoViewEventArgs e)
        {
            e.Handled = true;
        }
    }
}

And here's how you use it:

   <local:ExtendedDataGrid AutoScroll="False">
        <!-- your grid definition -->
   </local:ExtendedDataGrid>
Share:
16,289
Rob
Author by

Rob

Software architect specializing in machine learning/AI. Enjoys Python/TensorFlow/PyTorch/CNTK.

Updated on June 17, 2022

Comments

  • Rob
    Rob almost 2 years

    Problem:
    If my DataGrid is not entirely visible (horizontal & vertical scrollbars are showing) and I click on one of my cells that is partially visible, the grid auto-scrolls to bring that cell into view. I don't want this to happen. I've tried playing around with RequestBringIntoView, like this:

    private void DataGrid_RequestBringIntoView(object sender, RequestBringIntoViewEventArgs e)
    {
        e.Handled = true;
    }
    

    But that does nothing.

    Things I've tried:

    • My cells are custom UserControls; I tried putting an event handler for RequestBringIntoView on all UserControls that make up my cells, and tried handling the event, thinking that maybe I wasn't doing enough by just handling RequestBringIntoView on the DataGrid itself. This did not work.
    • Hosted the DataGrid inside of a ScrollViewer, and handled the ScrollViewer's RequestBringIntoView event. This actually works, and stops the auto-scrolling behavior, but in my case hosting a DataGrid inside of a ScrollViewer is not at all desirable, so I need to come up with a different solution.

    I'm not sure how to stop this behavior, any ideas?