WPF DataGrid: как остановить автоматическую прокрутку при нажатии на ячейку? - PullRequest
25 голосов
/ 14 января 2010

Проблема:
Если мой DataGrid не полностью виден (отображаются горизонтальные и вертикальные полосы прокрутки), и я щелкаю одну из частично видимых ячеек, сетка автоматически прокручивается, чтобы отобразить эту ячейку. Я не хочу, чтобы это произошло . Я пытался поиграть с RequestBringIntoView, вот так:

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

Но это ничего не значит.

То, что я пробовал:

  • Мои клетки нестандартные UserControls; Я попытался поместить обработчик события для RequestBringIntoView на все UserControls, которые составляют мои ячейки, и попытался обработать событие, думая, что, возможно, я недостаточно делал, просто обрабатывая RequestBringIntoView на самом DataGrid. Это не сработало.
  • Размещает DataGrid внутри ScrollViewer и обрабатывает событие ScrollViewer RequestBringIntoView. Это на самом деле работает и останавливает автоматическую прокрутку, но в моем случае размещение DataGrid внутри ScrollViewer совсем не желательно, поэтому мне нужно найти другое решение.

Я не уверен, как остановить это поведение, есть идеи?

Ответы [ 9 ]

28 голосов
/ 04 апреля 2011

Определите EventSetter в DataGrid.RowStyle для вызова обработчика, который препятствует отображению строки:

XAML

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

Обработчик

private void DataGrid_Documents_RequestBringIntoView(object sender, RequestBringIntoViewEventArgs e)
{
    e.Handled = true;      
}
9 голосов
/ 22 января 2010

Мне потребовалось больше времени, чтобы взглянуть на эту проблему, так как мое первое решение не сработало.

Однако ответ Иоанна почти хороший. Хитрость заключается в том, чтобы перехватить событие RequestBringIntoView ДО того, как оно попадет в ScrollViewer, чтобы отметить, что оно обработано.

Если вам не нужно дорабатывать весь шаблон, вы можете использовать следующий код:

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

Мы используем ScrollContentPresenter, потому что он находится чуть ниже ScrollViewer в визуальном дереве.

Надеюсь, это поможет!

7 голосов
/ 21 января 2010

Вы можете получить доступ к внутреннему ScrollViewer DataGrid, изменив шаблон. Хотя обычно вы не помещаете обработчик событий для кода в шаблоне, если вы объявляете шаблон встроенным, вы можете обрабатывать обработчик событий так же, как и при подключении его к самой DataGrid. Это шаблон по умолчанию, созданный из Blend, включая добавленный обработчик в ScrollViewer для события RequestBringIntoView:

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

4 голосов
/ 02 ноября 2011

У меня была такая же проблема, и ответ Яна помог мне. Единственным недостатком было то, что ScrollContentPresenter будет найден только после наступления события Loaded. Я создал расширенный класс DataGrid, унаследованный от DataGrid, с дополнительным свойством AutoScroll для контроля, хочу ли я, чтобы сетка автоматически прокручивалась или нет.

Вот класс:

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

А вот как вы его используете:

   <local:ExtendedDataGrid AutoScroll="False">
        <!-- your grid definition -->
   </local:ExtendedDataGrid>
3 голосов
/ 08 сентября 2016

У меня была та же проблема, что и у Румита, но я нашел решение / хак.

Я подумал, что если я смогу найти способ различать щелчки мыши и клавиши со стрелками, то я мог бы установить e.Handled соответственно.

После некоторых экспериментов я обнаружил, что e.OriginalSource менялся в зависимости от мыши или клавиши со стрелкой. Для щелчка мыши обработчик для RequestBringIntoView вызывается один раз, и e.OriginalSource имел тип DataGridCell. Для клавиши со стрелкой обработчик вызывается дважды, а e.OriginalSource имеет типы DataGridRow, а затем DataGridCell.

Код моего обработчика:

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

Это похоже на взлом, но прекрасно работает для меня.

1 голос
/ 14 января 2010

Я не уверен, что это работает, но вот идея, основанная на некоторых исследованиях, сделанных в исходном коде DataGrid с использованием Reflector:

1 / создать класс, который наследует DataGridCellsPanel. Это панель, которая используется DataGrid для организации ячеек

2 / переопределить метод BringIndexIntoView пустым методом (без вызова базового метода)

3 / установите свойство ItemsPanelTemplate в вашем XAML:

<tk:DataGrid>
    <tk:DataGrid.ItemsPanel>
        <ItemsPanelTemplate>
            <local:DataGridCellsPanelNoAutoScroll />
        </ItemsPanelTemplate>
    </tk:DataGrid.ItemsPanel>
</tk:DataGrid>

Кажется, что когда происходит событие MouseDown, в какой-то момент вызывается метод панели BringIndexIntoView для выполнения автопрокрутки. Замена на неактивную может помочь.

У меня не было времени протестировать это решение, пожалуйста, сообщите нам, работает ли оно.

0 голосов
/ 30 августа 2018

l33t путь:

static App()
{
    EventManager.RegisterClassHandler(typeof(ScrollContentPresenter), 
        FrameworkElement.RequestBringIntoViewEvent,
        new RoutedEventHandler(OnRequestBringIntoView));
}

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

Обратите внимание, что это может мешать, например, сторонние элементы управления.

0 голосов
/ 20 апреля 2015

Поскольку Dr.WPF ответил на аналогичный вопрос здесь the RequestBringIntoView должен обрабатываться в ItemsPanel.

0 голосов
/ 25 июля 2013

Вот что сработало для меня (попробовав все менее сложные «ответы» на сегодняшний день):

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

ScrollViewer.CanContentScroll = "Ложь" кажется невероятно противоречивой ...

...