滚动 DataGridView 似乎会大大降低性能

Posted

技术标签:

【中文标题】滚动 DataGridView 似乎会大大降低性能【英文标题】:Scrolling DataGridView seems to bring down performance drastically 【发布时间】:2021-04-05 10:16:03 【问题描述】:

我正在使用 Microsoft Visual Studio 2019 用 C# 编写一个应用程序。该应用程序与多个 Arduino 板通信。使用 TAP 模型异步发送和接收工作,并且工作正常。

该应用程序基于 Windows 窗体应用程序,使用 .NET Framework 4.7.2。我将 DataGridView 添加到表单中,使用 DataTable 作为 DataSource。目的是将此 DataGridView 用作数据记录器,显示 5 列:TimeStamp、DeviceID、Direction、Command 和 ErrorStatus。

如果我禁用 DataGridView,我会在中文 Arduino 克隆上达到每秒 500 条命令。在真正的 Arduino 上,我似乎每秒最多只能获得 244 个命令 - 请参阅下面关于 *** 的其他问题 - 但这不是现在的问题:

Communication speed over USB (PC/Arduino) using SerialPort in C# seems to “clip”

当我启用我的 DataGridView 时,我看到通信速度下降到大约 25 个命令/秒,这纯粹是因为更新了 DataGridView 中的行。但这似乎只是 DataGridView 开始滚动时的情况。

见下面代码sn-p:

dt.Rows.Add(new string[] 

    DateTime.Now.ToString("HH:mm:ss:fff"),
    device._FMGSDevice,
    action,
    notifyData.Command,
    notifyData.notifyError.ToString()
);
if (dt.Rows.Count > maxLines)
    dt.Rows.RemoveAt(0);

dt 是用作 DataGridView 的 DataSource 的 DataTable。对于每次通信,都会在 DataTable 的末尾添加一行,然后在 DataGridView 中自动更新。

maxLines 是当前设置为 500 的常数。为了避免我的 DataGridView 获得太多行,我将其限制为 500 行。如果达到限制,我会在添加新行后使用“RemoveAt(0)”删除第一行,以使其保持在最多 500 行。

我现在看到,一旦 DataGridView 开始滚动(“RemoveAt(0)”导致所有行都向上移动一行),速度会急剧下降。

有人知道如何加快滚动速度吗? 或者还有其他项目可以用来记录吗? (虽然,我也需要过滤)。

【问题讨论】:

DGV 不会是我实现此日志记录的首选。你是在窗口线程上发送命令吗? 也许你必须有一个列表/表格来保存数据,但在网格中只显示 20 或 30 行。滚动时,然后更新网格。 试试DataGridView.VirtualMode和Implementing Virtual Mode in the Windows Forms DataGridView Control 尝试双缓冲 DGV! @CaiusJard 不,通信使用基于异步任务的实现。 【参考方案1】:

我不完全了解您的应用程序,但您可以尝试使用 delete 方法而不是 RemoveAt,看看性能是否有所提高。应该是:

DataRow dr = dtName.Rows[0];
dr.Delete();
dtName.AcceptChanges();

或者,您可以增加要删除的记录数,例如删除前 10 或 20 条记录,而不是仅删除第一个。

【讨论】:

我尝试了你的两个建议。使用 Delete() 和 AcceptChanges 而不是 RemoveAt() 似乎不会带来任何性能提升。我还使用这两种方法一次尝试了 10 行和 20 行,这也没有给我任何改变。无论如何感谢您的反馈,非常感谢。【参考方案2】:

@TaW 的建议到目前为止效果最好。至少,它是最容易实现的。我还没有尝试过虚拟模式,也没有尝试过 ListView,因为这些都需要相当长的时间来实现。

我从 DataGridView 创建了一个派生类,如下所示(请参阅我找到这个的链接):

Horrible redraw performance of the DataGridView on one of my two screens

class CustomDataGridView : DataGridView

    public CustomDataGridView()
    
        // if not remote desktop session then enable double-buffering optimization
        if (!System.Windows.Forms.SystemInformation.TerminalServerSession)
            DoubleBuffered = true;
    

因为我使用的是 Designer,所以我手动更改了那里的代码以使用 CustomDataGridView 类。

this.dgLogView = new HW_Hub.CustomDataGridView();

虽然我不确定,是否允许更改“...designer.cs”中的代码?我感觉 Visual Studio“拥有”这个文件,并自动进行更改。我是否需要另一种方式来否决标准实施?

【讨论】:

"...designer.cs"?我感觉 Visual Studio“拥有”这个文件有一个部分是保留的,但其余部分可以更改,只要您不破坏语法即可。保留区域标记为这样。 Visual Studio 将您添加到表单设计器的内容序列化。如果CustomDataGridView 自定义控件是可访问的(序列化程序可以找到它),您在Designer.cs 文件中所做的更改将保持不变,并且替换可视化设计器中的控件。顺便说一句,这种用另一个替换控件的方式很常见。该警告与添加到 designer.cs 的代码有关,该代码不受序列化的影响:当然,该代码不会持续存在,因此当序列化程序启动时,所有更改都将丢失。【参考方案3】:

考虑不要自动更新数据。人们将阅读数据,也许会选择其中的一部分。如果您刚刚选择的数据滚动消失,那是相当烦人的。

这类似于编辑文档,您正在阅读或刚刚选择的部分会突然消失。

添加一个“刷新”按钮,它将刷新数据。

如果您认为数据的实时更新很重要,以便操作员可以看到哪些数据发生了变化,请考虑使用计时器。如果计时器到时,请刷新数据。

类似这样的:

// the data in one Row:
class ArduinoLogData

    ... // properties that will be shown in the columns


BindingList<ArduinoLogData> DisplayedArduinoData

    get => (BindingList<LoggedArduinoRow>)this.DataGridView1.DataSource;
    set => this.DataGridView1.DataSource = value;


// Fetch ArduinoData
IEnumerable<ArduinoLogData> FetchArduinoLogdata()

    ...

考虑创建一个重载,在其中声明必须获取哪些 arduino,因此您只能更新当前可见的 arduino。使用 DataGridViewRow.Displayed 获取当前显示的 ArduinoLogData:

IEnumerable<ArduinoLogData> DisplayedData => this.DataGridView1.Rows
    .Where(row => row.Displayed)
    .Select(row => row.DataBoundItem)
    .Cast<ArduinoLogData>();

还有一个方法:

void RefreshArduinoLogData(ICollection<ArduinoLogData> itemsToRefresh)

    ... // update only these items.


void RefreshDisplayedArduinoLogData()

    this.RefreshArduinoLogData(this.DisplayedData.ToList());

这将只更新可见项目,大约 40 行左右。

每秒执行一次:

Timer refreshTime = new Timer

    AutoReset = true,
    Interval = TimeSpan.FromSeconds(1).TotalMilliseconds,
;
timer.Elapsed += OnTimerTick;
timer.Enabled = true;

void OnTimerTick(object sender, ...)

    this.RefreshDisplayedArduinoLogData();

不要忘记在您的表单关闭时或最后在表单被处置时处置您的计时器。

【讨论】:

以上是关于滚动 DataGridView 似乎会大大降低性能的主要内容,如果未能解决你的问题,请参考以下文章

使用 mpirun 执行我的程序会大大降低性能

UITableViewCell 中嵌套的 UIStackViews,会减慢滚动速度,降低性能。如何提高性能?

JavaFX 滚动表更新性能随着时间的推移而降低

将 UIScrollView 添加到表格单元格似乎会影响表格滚动性能

c# 多线程往datagridview会造成假死,滚动条无法滚动

datagridview 不显示滚动条怎么回事