WPF DataGrid:如何在单击单元格时停止自动滚动?

问题描述 投票:25回答:9

问题: 如果我的DataGrid不完全可见(水平和垂直滚动条显示),我点击我的一个部分可见的单元格,网格自动滚动以将该单元格带入视图。我不希望这种情况发生。我试过玩RequestBringIntoView,像这样:

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

但这没有任何作用。

我试过的事情:

  • 我的细胞是定制的UserControls;我尝试在构成我的细胞的所有RequestBringIntoView上放置一个UserControls的事件处理程序,并尝试处理该事件,认为我可能只是在RequestBringIntoView本身处理DataGrid做得不够。这没用。
  • DataGrid内部主持ScrollViewer,并处理了ScrollViewerRequestBringIntoView事件。这实际上是有效的,并且停止了自动滚动行为,但在我的情况下,在DataGrid内部托管ScrollViewer根本不可取,所以我需要提出一个不同的解决方案。

我不知道如何阻止这种行为,任何想法?

wpf datagrid scroll wpfdatagrid
9个回答
7
投票

您可以通过修改模板来访问DataGrid的内部ScrollViewer。虽然通常您不会将事件处理程序放在模板后面的代码中,但如果您将模板内联声明,则可以将事件处理程序与将其附加到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>


28
投票

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

由于我的第一个解决方案无效,我花了更多时间来研究这个问题。

然而约翰的答案几乎是好的。诀窍是在它到达ScrollViewer之前捕获RequestBringIntoView事件,以便标记它已被处理。

如果您不必优化整个模板,则可以使用以下代码:

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

我们使用ScrollContentPresenter,因为它位于可视化树中的ScrollViewer下方。

希望这可以帮助 !


4
投票

我有同样的问题,Jan的回答帮助了我。唯一缺少的是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
投票

我和Rumit有同样的问题,但找到了解决方案/黑客。

我想如果我能找到区分鼠标点击和箭头键的方法,那么我可以相应地设置e.Handled。

经过一些实验,我发现e.OriginalSource根据鼠标或箭头键改变了。对于鼠标单击,RequestBringIntoView的处理程序调用一次,e.OriginalSource类型为DataGridCell。对于箭头键,处理程序被调用两次,e.OriginalSource的类型为DataGridRow,然后是DataGridCell。

我的处理程序的代码是:

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

这看起来有点像黑客,但对我来说很有用。


1
投票

我不确定这是否有效但是这是一个基于一些调查的想法是使用Reflector在DataGrid的源代码中进行的:

1 /创建一个继承DataGridCellsPanel的类。这是DataGrid在内部使用的Panel,用于排列单元格

2 /通过空方法覆盖BringIndexIntoView方法(不调用基方法)

3 /在XAML中设置ItemsPanelTemplate属性:

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

似乎当MouseDown事件发生时,在某些时候调用面板的BringIndexIntoView方法来执行自动滚动。用no-op替换它可能会成功。

我没时间测试这个解决方案,请告诉我们它是否正常工作。


0
投票

这对我有用(在尝试了迄今为止所有不那么复杂的“答案”之后):

    <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”似乎令人难以置信的反直觉......


0
投票

l33t方式:

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

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

请注意,这可能会干扰例如第三方控件。


-1
投票

由于Dr.WPF回答了类似的问题here,RequestBringIntoView应该在ItemsPanel中处理。

© www.soinside.com 2019 - 2024. All rights reserved.