C# WinForm DataGridView 非常频繁地更新或重新绘制慢问题及解决

发布于:2025-05-10 ⋅ 阅读:(10) ⋅ 点赞:(0)

非常频繁地更新 DataGridView问题描述:

        在 C# 中无法在合理的时间内刷新我的 DataGridView ,我每秒通过网络发送 20 个数据包,获取数据。我想解析这些数据并将其放入 DataGridView 中。我还想调整 DataGridView 的更新间隔,从 0.1 秒到 1 分钟。

        因此,我创建了一个额外的线程,用于读取包并将其解析为数组。我还创建了一个计时器,用于更改间隔。每次计时器计时,我都会将 DataSource 重新分配给 DataGridView。

        有趣的是,当我这样做时,即使我将计时器设置为 0.1 秒,它也只会每秒触发一次。如果我不刷新 DataGridView,它每秒就会触发 10 次,正如预期的那样。

        所以我认为我更新 DataGridView 的方法太耗时了。但是我该怎么做才能让它更高效,以便每秒更新 10 次而不会出现任何问题呢?

这是使用的代码:

public MyForm()
    {
        InitializeComponent();

        timer = new System.Windows.Forms.Timer();
        timer.Interval = (1 * 1000); // 1 secs
        timer.Tick += new EventHandler(timer_Tick);
        timer.Start();

        readNetworkValues = true;
        networkReader = new Thread(() =>
        {
            Thread.CurrentThread.IsBackground = true;
            byte[] data = new byte[1024];
            IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 49003);
            UdpClient newsock = new UdpClient(ipep);
            IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);

            while (readNetworkValues)
            {
                data = newsock.Receive(ref sender);
                dataSet = parseData(data); //Decrypts the data
            }
        newsock.Close();
        });
        networkReader.Start();
    }

    private void timer_Tick(object sender, EventArgs e)
    {
        if (dataSet != null)
        {
            lock (dataSet)
            {
                int currentRow = dataGrid.FirstDisplayedScrollingRowIndex;
                dataGrid.DataSource = dataSet;
                dataGrid.FirstDisplayedScrollingRowIndex = currentRow;
            }
        }
    }

解决方法: 

您想要更新的单元数量以及您想要的更新率足够高,从而导致闪烁和延迟。

为了避免这种情况,您可以DoubleBuffering打开DataGridView。

此属性默认不公开。因此,您可以选择创建子类或通过反射访问它,它主要针对滚动闪烁的情况,但也有助于避免更新延迟。类可能看起来像这样:

public class DBDataGridView : DataGridView
{
    public new bool DoubleBuffered
    {
        get { return base.DoubleBuffered; }
        set { base.DoubleBuffered = value; }
    }

    public DBDataGridView()
    {
        DoubleBuffered = true;
    }
}

您可以将此类添加到项目中,或者仅添加到表单类中(在最后一个花括号之前)。编译后它将显示在工具箱中。

另一个选项是使用反射;这是一个适用于任何类型控件的通用函数:

using System.Reflection;

static void SetDoubleBuffer(Control ctl, bool DoubleBuffered)
{
    typeof(Control).InvokeMember("DoubleBuffered", 
        BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty, 
        null, ctl, new object[] { DoubleBuffered });
}

两种方式都可以DoubleBuffering随意打开和关闭;前者通过现在公开的属性,后者通过bool方法的参数。

DataGridView 重新绘制慢问题描述:

我知道有几个关于速度慢的 DataGridViews 的问题,确实尝试了那里提到的所有内容,大多数都是关于更复杂的数据绑定。

之前做过的事情:

    测试了 DataTable 作为数据源
    未绑定的 Datagridview,循环填充
    使用 autoColumnResizing 等测试了许多设置,但所有测试都太慢了。

此示例仅是主窗体中的一个 datgridview,来自设计器工具箱的普通库存,已编辑。

即使只有 100 行,滚动和重新绘制时也会感觉非常滞后,并且会占用一个核心的最大值。

我在 datagridview 中滚动时进行了 VS 分析器会话:

想深入挖掘并了解究竟是什么让这个控制如此缓慢,以及可以做些什么来加速它,或者可以使用什么替代方法来获得一个可以通过单击列标题进行排序的快速表。

测试代码: 

public partial class Form1 : Form
{
   List<Record> myData;
    public Form1()
    {
        InitializeComponent();
        myData = getListofRecord();

        dataGridView1.DataSource = myData;
    }

    private static List<Record> getListofRecord()
    {
        var myTable = new List<Record>();
        for (int i = 0; i < 500; i++)
        {
            myTable.Add(new Record() { a = 121+i, b = 123.2434F, c = 2342, d = 312+i*2, e = 123343, f = 12323 });
        }
        return myTable;
    }

}

public class Record
{
    public Single a { get; set; }
    public Single b { get; set; }
    public Single c { get; set; }
    public Single d { get; set; }
    public Single e { get; set; }
    public Single f { get; set; }

}

编辑1:

        两天后,我在同一台机器上重新运行测试应用时得到了不同的结果。它的速度和我预期的一样快,分析结果或许能解释热路径堆栈中缺失的原因。

编辑2:

        无论第一个差异背后的真正原因是什么,通过反射启用双缓冲的效果最为明显,大约提升了 20 倍。 我在其他 DGV 问题中也看到过类似的建议,但我原本只期望获得轻微的视觉效果提升,而不是全新的体验。

分析热路径现在显示完全不同的调用堆栈。

编辑3:

        为了保存我在这个问题中的分析工作,避免被当作重复数据删除,想获取一些以毫秒为单位的目标数据,用于重新绘制DGV。需要把秒表放在哪里才能获取这些数据?

解决方法:

其实与上面闪烁方法一致,方法就这么简单:

C#.NET:

using System.Reflection;

static void SetDoubleBuffer(Control dgv, bool DoubleBuffered)
{
    typeof(Control).InvokeMember("DoubleBuffered", 
        BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty, 
        null, dgv, new object[] { DoubleBuffered });
}

VB.NET:

 Public Sub DoubleBuffered(ByVal dgv As DataGridView, ByVal setting As Boolean)
      Dim dgvType As Type = dgv.[GetType]()
      Dim pi As PropertyInfo = dgvType.GetProperty("DoubleBuffered", BindingFlags.Instance Or BindingFlags.NonPublic)
      pi.SetValue(dgv, setting, Nothing)
   End Sub

 如果您喜欢此文章,请收藏、点赞、评论,谢谢,祝您快乐每一天。


网站公告

今日签到

点亮在社区的每一天
去签到