当非虚拟化 DataGrid 中有大量行时,WPF 应用程序 DataGrid 控制窗口切换滞后

发布于 2024-11-16 13:43:02 字数 5914 浏览 2 评论 0原文

我们有一个小型 .NET 4.0 DataGrid WPF 演示,其代码如下。它由一个具有 30 列和 3000 行的非虚拟化 DataGrid 组成。它是非虚拟化的,因为我们需要分组功能,而分组功能不支持虚拟化。

当您运行此应用程序并在其与其他窗口之间切换时,会出现明显的延迟(大约 1 秒)。仅当窗口重新激活时才会发生这种情况 - 一旦激活,在内部单击就没有相关的延迟。

我们使用性能分析器分析了窗口重新激活时发生的延迟,并发现当窗口重新获得焦点时会触发许多依赖属性通知。我们不知道为什么会发生这种情况,而且似乎没有必要。

我们发现此延迟与 DataGrid 中的行数成正比。有谁知道我们如何消除或减少这种滞后?

更新:即使停留在应用程序内部但专注于另一个控件(例如网格外的文本框),似乎也会出现焦点滞后。因此我们现在知道这不是窗口切换问题,而是由焦点变化引起的问题,但仍不确定确切原因。

(MainWindow.xaml)

<Window x:Class="WpfApplication20.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525">
    <Grid>
        <DataGrid Name="dataGrid" VirtualizingStackPanel.IsVirtualizing="False" AutoGenerateColumns="True"/>
    </Grid>
</Window>

(MainWindow.xaml.cs)

using System.Collections.Generic;
using System.Windows;

namespace WpfApplication20
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            List<Row> rows = new List<Row>();

            for(int i = 0; i < 3000; i++)
            {
                Row row = new Row(i);
                rows.Add(row);
            }

            dataGrid.ItemsSource = rows;
        }
    }

    public class Row
    {
        public double Double1 { get; set; }
        public double Double2 { get; set; }
        public double Double3 { get; set; }
        public double Double4 { get; set; }
        public double Double5 { get; set; }
        public double Double6 { get; set; }
        public double Double7 { get; set; }
        public double Double8 { get; set; }
        public double Double9 { get; set; }
        public double Double10 { get; set; }
        public double Double11 { get; set; }
        public double Double12 { get; set; }
        public double Double13 { get; set; }
        public double Double14 { get; set; }
        public double Double15 { get; set; }
        public double Double16 { get; set; }
        public double Double17{ get; set; }
        public double Double18 { get; set; }
        public double Double19 { get; set; }
        public double Double20 { get; set; }
        public double Double21 { get; set; }
        public double Double22 { get; set; }
        public double Double23 { get; set; }
        public double Double24 { get; set; }
        public double Double25 { get; set; }
        public double Double26 { get; set; }
        public double Double27 { get; set; }
        public double Double28 { get; set; }
        public double Double29 { get; set; }
        public double Double30 { get; set; }

        public Row(double d)
        {
            Double1 = d;
            Double2 = d + 1;
            Double3 = d + 2;
            Double4  = d + 3;
            Double5  = d + 4;
            Double6  = d + 5;
            Double7  = d + 6;
            Double8  = d + 7;
            Double9  = d + 8;
            Double10 = d + 9;
            Double11 = d + 10;
            Double12 = d + 11;
            Double13 = d + 12;
            Double14 = d + 13;
            Double15 = d + 14;
            Double16 = d + 15;
            Double17 = d + 16;
            Double18 = d + 17;
            Double19 = d + 18;
            Double20 = d + 19;
            Double21 = d + 20;
            Double22 = d + 21;
            Double23 = d + 22;
            Double24 = d + 23;
            Double25 = d + 24;
            Double26 = d + 25;
            Double27 = d + 26;
            Double28 = d + 27;
            Double29 = d + 28;
            Double30 = d + 29;
        }             
    }                 
}

(组样式 - 通过放入 DataGrid XML 可选择启用):

<!--<DataGrid.GroupStyle>
                <GroupStyle>
                    <GroupStyle.ContainerStyle>
                        <Style TargetType="{x:Type GroupItem}">
                            <Setter Property="Template">
                                <Setter.Value>
                                    <ControlTemplate TargetType="{x:Type GroupItem}">
                                        <Border BorderBrush="DarkGray" BorderThickness="1" Padding="4,0" >
                                            <Expander VerticalContentAlignment="Center" IsExpanded="True">
                                                <Expander.Header>
                                                    <Canvas>
                                                        <StackPanel Orientation="Horizontal" Canvas.Top="-11" Canvas.Left="4">
                                                            <Label Content="{Binding Name}" Visibility="{Binding DataContext.ShowGroupHeaderVisibility, RelativeSource={RelativeSource AncestorType=DataGrid}}"/>
                                                            <Label Content="{Binding ItemCount}" Visibility="{Binding DataContext.ShowGroupCountVisibility, RelativeSource={RelativeSource AncestorType=DataGrid}}"/>
                                                        </StackPanel>
                                                    </Canvas>
                                                </Expander.Header>
                                                <ItemsPresenter/>
                                            </Expander>
                                        </Border>
                                    </ControlTemplate>
                                </Setter.Value>
                            </Setter>
                        </Style>
                    </GroupStyle.ContainerStyle>
                </GroupStyle>
            </DataGrid.GroupStyle>-->

We have a small .NET 4.0 DataGrid WPF demo with the code posted below. It consists of a non-virtualized DataGrid with 30 columns and 3000 rows. It is non-virtualized, due to us requiring grouping capabilities, which do not support virtualization.

When you run this app, and switch between it and other windows, there is a noticeable lag (of about 1 second). This only occurs when the window is reactivated - once activated, clicking around inside has no associated delay.

We have profiled this lag that occurs on window reactivation using the performance analyzer and have found that there are a lot of dependency property notifications being triggered when the window is brought back into focus. We do not know why this happens, and it seems unnecessary.

We find this delay is proportional to the number of rows in the DataGrid. Does anyone know how we can eliminate or reduce this lag?

UPDATE: It seems like the focus lag occurs even when staying inside the application but focusing on another control such as a textbox outside the grid. Therefore we now know it's not a Window switching problem, but one caused by change in focus, but are still unsure of the exact cause.

(MainWindow.xaml)

<Window x:Class="WpfApplication20.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525">
    <Grid>
        <DataGrid Name="dataGrid" VirtualizingStackPanel.IsVirtualizing="False" AutoGenerateColumns="True"/>
    </Grid>
</Window>

(MainWindow.xaml.cs)

using System.Collections.Generic;
using System.Windows;

namespace WpfApplication20
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            List<Row> rows = new List<Row>();

            for(int i = 0; i < 3000; i++)
            {
                Row row = new Row(i);
                rows.Add(row);
            }

            dataGrid.ItemsSource = rows;
        }
    }

    public class Row
    {
        public double Double1 { get; set; }
        public double Double2 { get; set; }
        public double Double3 { get; set; }
        public double Double4 { get; set; }
        public double Double5 { get; set; }
        public double Double6 { get; set; }
        public double Double7 { get; set; }
        public double Double8 { get; set; }
        public double Double9 { get; set; }
        public double Double10 { get; set; }
        public double Double11 { get; set; }
        public double Double12 { get; set; }
        public double Double13 { get; set; }
        public double Double14 { get; set; }
        public double Double15 { get; set; }
        public double Double16 { get; set; }
        public double Double17{ get; set; }
        public double Double18 { get; set; }
        public double Double19 { get; set; }
        public double Double20 { get; set; }
        public double Double21 { get; set; }
        public double Double22 { get; set; }
        public double Double23 { get; set; }
        public double Double24 { get; set; }
        public double Double25 { get; set; }
        public double Double26 { get; set; }
        public double Double27 { get; set; }
        public double Double28 { get; set; }
        public double Double29 { get; set; }
        public double Double30 { get; set; }

        public Row(double d)
        {
            Double1 = d;
            Double2 = d + 1;
            Double3 = d + 2;
            Double4  = d + 3;
            Double5  = d + 4;
            Double6  = d + 5;
            Double7  = d + 6;
            Double8  = d + 7;
            Double9  = d + 8;
            Double10 = d + 9;
            Double11 = d + 10;
            Double12 = d + 11;
            Double13 = d + 12;
            Double14 = d + 13;
            Double15 = d + 14;
            Double16 = d + 15;
            Double17 = d + 16;
            Double18 = d + 17;
            Double19 = d + 18;
            Double20 = d + 19;
            Double21 = d + 20;
            Double22 = d + 21;
            Double23 = d + 22;
            Double24 = d + 23;
            Double25 = d + 24;
            Double26 = d + 25;
            Double27 = d + 26;
            Double28 = d + 27;
            Double29 = d + 28;
            Double30 = d + 29;
        }             
    }                 
}

(Group Style - optionally enabled by putting inside DataGrid XML):

<!--<DataGrid.GroupStyle>
                <GroupStyle>
                    <GroupStyle.ContainerStyle>
                        <Style TargetType="{x:Type GroupItem}">
                            <Setter Property="Template">
                                <Setter.Value>
                                    <ControlTemplate TargetType="{x:Type GroupItem}">
                                        <Border BorderBrush="DarkGray" BorderThickness="1" Padding="4,0" >
                                            <Expander VerticalContentAlignment="Center" IsExpanded="True">
                                                <Expander.Header>
                                                    <Canvas>
                                                        <StackPanel Orientation="Horizontal" Canvas.Top="-11" Canvas.Left="4">
                                                            <Label Content="{Binding Name}" Visibility="{Binding DataContext.ShowGroupHeaderVisibility, RelativeSource={RelativeSource AncestorType=DataGrid}}"/>
                                                            <Label Content="{Binding ItemCount}" Visibility="{Binding DataContext.ShowGroupCountVisibility, RelativeSource={RelativeSource AncestorType=DataGrid}}"/>
                                                        </StackPanel>
                                                    </Canvas>
                                                </Expander.Header>
                                                <ItemsPresenter/>
                                            </Expander>
                                        </Border>
                                    </ControlTemplate>
                                </Setter.Value>
                            </Setter>
                        </Style>
                    </GroupStyle.ContainerStyle>
                </GroupStyle>
            </DataGrid.GroupStyle>-->

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

流绪微梦 2024-11-23 13:43:02

我会尝试在行类(或 rowViewModel 类)中使用 Dependecy 属性而不是常规 CLR 属性,特别是因为有很多列。

您使用任何样式/模板吗?我假设您发布的代码有所简化:)
另外,您是说在窗口打开时您没有看到这种延迟?只有当它重新聚焦时?

-编辑-

好的,我做了一些测试,并得出结论,绑定不是问题。当窗口获得焦点时,我似乎确实能够通过定义自定义单元格样式(并将其设置为共享)来降低 cpu 峰值:

<Window>
  <Grid>
    <Grid.Resources>
      <Style TargetType="DataGridCell"
             x:Key="cs"
             x:Shared="True">
        <Setter Property="Content"
                Value="{Binding}" />
      </Style>
    </Grid.Resources>
    <DataGrid Name="dataGrid"
              VirtualizingStackPanel.IsVirtualizing="False"
              CellStyle="{StaticResource cs}">
    </DataGrid>
  </Grid>
</Window>

尽管如此,该程序使用大量内存,几乎 1 GB.. 另外,横向滚动是由于某种原因,我的机器上速度很慢,但向下滚动就可以了。我认为内置的数据网格并不真正适合这么多的数据,至少在非虚拟化模式下是这样。您仍然可以尝试单元格样式,看看是否会有所改善

I would try using Dependecy properties instead of regular CLR properties in the row class, (or a rowViewModel class) especially since there are so many columns.

Are you using any styles/templates? i assume the code you've posted is simplified somewhat :)
also, you're saying you dont see this delay as the window opens? only when its brought back into focus?

-edit-

Ok ive done some tests and also conclude that the binding is not the issue. I did seem to be able to reduce the cpu peak when the window gets focus by defining a custom cell style (and setting it to shared):

<Window>
  <Grid>
    <Grid.Resources>
      <Style TargetType="DataGridCell"
             x:Key="cs"
             x:Shared="True">
        <Setter Property="Content"
                Value="{Binding}" />
      </Style>
    </Grid.Resources>
    <DataGrid Name="dataGrid"
              VirtualizingStackPanel.IsVirtualizing="False"
              CellStyle="{StaticResource cs}">
    </DataGrid>
  </Grid>
</Window>

Still, the program uses massive amounts of memory, almost 1 gb.. also, scrolling sideways is slow on my machine for some reason, but scrolling downwards is fine. i dont think the built in datagrid is really suitable for this amount of data, atleast in non virualized mode. still you can try out the cell style and see if that improves things

野心澎湃 2024-11-23 13:43:02

解决方案非常简单。使用从DataGrid继承的网格,覆盖OnIsKeyboardFocusWithinChanged()并且不要调用base.OnIsKeyboardFocusWithinChangedand()

public class MyGrid : DataGrid
{
    protected override void OnIsKeyboardFocusWithinChanged(DependencyPropertyChangedEventArgs e)
    {
        // Do not call base.OnIsKeyboardFocusWithinChanged(e);
    }
}

性能应该会好得多。至少对于您的示例代码来说:)。

以下堆栈跟踪是瓶颈原因:

0012dd90 58cd5818 System.Windows.DependencyObject.LookupEntry(Int32)
0012ddb0 58cd923b System.Windows.DependencyObject.GetValueSource(System.Windows.DependencyProperty, System.Windows.PropertyMetadata, Boolean ByRef, Boolean ByRef, Boolean ByRef, Boolean ByRef, Boolean ByRef)
0012dde0 58cd91f2 System.Windows.DependencyObject.GetValueSource(System.Windows.DependencyProperty, System.Windows.PropertyMetadata, Boolean ByRef)
0012de04 56c78477 System.Windows.VisualStateManager.GetVisualStateGroupsInternal(System.Windows.FrameworkElement)
0012de14 56c504e7 System.Windows.VisualStateManager.GoToStateCommon(System.Windows.FrameworkElement, System.Windows.FrameworkElement, System.String, Boolean)
0012de38 56c504ac System.Windows.VisualStateManager.GoToState(System.Windows.FrameworkElement, System.String, Boolean)
0012de50 5753506a System.Windows.Controls.DataGridCell.ChangeVisualState(Boolean)
0012de64 56c5012c System.Windows.Controls.Control.UpdateVisualState(Boolean)
0012de74 575353fa System.Windows.Controls.DataGridCell.NotifyPropertyChanged(System.Windows.DependencyObject, System.String, System.Windows.DependencyPropertyChangedEventArgs, System.Windows.Controls.DataGridNotificationTarget)
0012debc 5753b4d0 System.Windows.Controls.Primitives.DataGridCellsPresenter.NotifyPropertyChanged(System.Windows.DependencyObject, System.String, System.Windows.DependencyPropertyChangedEventArgs, System.Windows.Controls.DataGridNotificationTarget)
0012df04 5752a1d5 System.Windows.Controls.DataGridRow.NotifyPropertyChanged(System.Windows.DependencyObject, System.String, System.Windows.DependencyPropertyChangedEventArgs, System.Windows.Controls.DataGridNotificationTarget)
0012df4c 5751becd System.Windows.Controls.DataGrid.NotifyPropertyChanged(System.Windows.DependencyObject, System.String, System.Windows.DependencyPropertyChangedEventArgs, System.Windows.Controls.DataGridNotificationTarget)
0012dfa4 57512d98 System.Windows.Controls.DataGrid.OnIsKeyboardFocusWithinChanged(System.Windows.DependencyObject, System.Windows.DependencyPropertyChangedEventArgs)

视觉状态管理器更新 IsKeyboardFocusWithinChanged 事件上的每个单元格。

顺便说一句,有趣的是,如果您使用 ILSpy 反编译 DataGrid.OnIsKeyboardFocusWithinChanged() ,您将不会在那里找到它,就好像它没有覆盖基类的默认行为一样。 WinDbg 显示的情况恰恰相反。

希望这能回答您的问题,但在不启用虚拟化的情况下,我可以轻松预见更多问题。

更新如何查找原因?只需将调试器附加到您的进程,每次您看到调试器出现明显缓慢的情况时,请检查 UI 线程的堆栈跟踪。如果相同的堆栈跟踪经常出现 - 那可能是你的恶棍。我使用 WinDbg 来追踪这个问题。 VS 调试器应该也有效。

Solution is quite simple. Use a grid inherited from DataGrid, override OnIsKeyboardFocusWithinChanged() and DO NOT call base.OnIsKeyboardFocusWithinChangedand():

public class MyGrid : DataGrid
{
    protected override void OnIsKeyboardFocusWithinChanged(DependencyPropertyChangedEventArgs e)
    {
        // Do not call base.OnIsKeyboardFocusWithinChanged(e);
    }
}

Performance should be much better. At least for your sample code :).

The following stack trace is the bottleneck reason:

0012dd90 58cd5818 System.Windows.DependencyObject.LookupEntry(Int32)
0012ddb0 58cd923b System.Windows.DependencyObject.GetValueSource(System.Windows.DependencyProperty, System.Windows.PropertyMetadata, Boolean ByRef, Boolean ByRef, Boolean ByRef, Boolean ByRef, Boolean ByRef)
0012dde0 58cd91f2 System.Windows.DependencyObject.GetValueSource(System.Windows.DependencyProperty, System.Windows.PropertyMetadata, Boolean ByRef)
0012de04 56c78477 System.Windows.VisualStateManager.GetVisualStateGroupsInternal(System.Windows.FrameworkElement)
0012de14 56c504e7 System.Windows.VisualStateManager.GoToStateCommon(System.Windows.FrameworkElement, System.Windows.FrameworkElement, System.String, Boolean)
0012de38 56c504ac System.Windows.VisualStateManager.GoToState(System.Windows.FrameworkElement, System.String, Boolean)
0012de50 5753506a System.Windows.Controls.DataGridCell.ChangeVisualState(Boolean)
0012de64 56c5012c System.Windows.Controls.Control.UpdateVisualState(Boolean)
0012de74 575353fa System.Windows.Controls.DataGridCell.NotifyPropertyChanged(System.Windows.DependencyObject, System.String, System.Windows.DependencyPropertyChangedEventArgs, System.Windows.Controls.DataGridNotificationTarget)
0012debc 5753b4d0 System.Windows.Controls.Primitives.DataGridCellsPresenter.NotifyPropertyChanged(System.Windows.DependencyObject, System.String, System.Windows.DependencyPropertyChangedEventArgs, System.Windows.Controls.DataGridNotificationTarget)
0012df04 5752a1d5 System.Windows.Controls.DataGridRow.NotifyPropertyChanged(System.Windows.DependencyObject, System.String, System.Windows.DependencyPropertyChangedEventArgs, System.Windows.Controls.DataGridNotificationTarget)
0012df4c 5751becd System.Windows.Controls.DataGrid.NotifyPropertyChanged(System.Windows.DependencyObject, System.String, System.Windows.DependencyPropertyChangedEventArgs, System.Windows.Controls.DataGridNotificationTarget)
0012dfa4 57512d98 System.Windows.Controls.DataGrid.OnIsKeyboardFocusWithinChanged(System.Windows.DependencyObject, System.Windows.DependencyPropertyChangedEventArgs)

Visual state manager updates every single cell on IsKeyboardFocusWithinChanged event.

BTW interestingly enough if you use ILSpy to decompile DataGrid.OnIsKeyboardFocusWithinChanged() you won't find it there as if it does not override default behavior from base class. WinDbg shows quite the opposite.

Hope this answers your question, but I can easily anticipate many more problems without virtualization enabled.

UPDATE How to find the cause? Just attach with debugger to your process and every time you see significant slowness break into debugger and check the stacktrace of UI thread. If the same stack trace appears often - that's probably your villain. I used WinDbg to track down this one. VS debugger should be effective as well.

_畞蕅 2024-11-23 13:43:02

对于那些感兴趣的人,我们通过在应用程序中定义单独的焦点范围来解决这个问题。默认情况下,整个窗口都是焦点范围。创建单独的窗口并确保在窗口停用和再次激活之前将焦点范围设置为网格以外的其他内容,以确保窗口快速移动和返回。我们假设 WPF 中有一些未记录的功能会产生这种现象。

For those interested, we solved resolved this issue by defining separate focus scopes within our application. By default the entire Window is the focus scope. Creating separate ones and making sure we set the focus scopeto something other than the grid before the window deactivates and activates again ensures it goes and comes back snappily. We're assuming there is some undocumented functionality in WPF that produces this phenomena.

孤君无依 2024-11-23 13:43:02

我们在我工作的公司也遇到了同样的问题。我们用第三方网格替换了它,问题就消失了。大多数 WPF 数据网格的性能都很糟糕。

We had the same problem at the company I work for. We replaced it with a third party grid and the problem went away. Most WPF data grids have horrible performance.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文