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

前端 未结 9 1099
旧巷少年郎
旧巷少年郎 2020-12-08 07:31

Problem:
If my DataGrid is not entirely visible (horizontal & vertical scrollbars are showing) and I click on one of my cells that is p

相关标签:
9条回答
  • 2020-12-08 07:44

    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>
    

    0 讨论(0)
  • 2020-12-08 07:44

    The l33t way:

    static App()
    {
        EventManager.RegisterClassHandler(typeof(ScrollContentPresenter), 
            FrameworkElement.RequestBringIntoViewEvent,
            new RoutedEventHandler(OnRequestBringIntoView));
    }
    
    private static void OnRequestBringIntoView(object sender, RoutedEventArgs e)
    {
        e.Handled = true;
    }
    

    Note that this could interfere with e.g. third-party controls.

    0 讨论(0)
  • 2020-12-08 07:48

    Here's what worked for me (after trying all of the less complex "answers" to date):

        <DataGrid Grid.Column="0" Grid.Row="1"
                  Name="ListItemContainerDataGrid"
                  ScrollViewer.VerticalScrollBarVisibility="Visible"
                  ScrollViewer.CanContentScroll="False"
                  And.Others
                  ItemsSource="{Binding Path=ListItemModels}"
                  >
        </DataGrid>
    

    ScrollViewer.CanContentScroll="False" seems mindbogglingly counter intuitive...

    0 讨论(0)
  • 2020-12-08 07:51

    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.

    0 讨论(0)
  • 2020-12-08 07:52

    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 !

    0 讨论(0)
  • 2020-12-08 07:54

    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>
    
    0 讨论(0)
提交回复
热议问题