Низкая производительность при заполнении DataGridView большими данными

Я использую элемент управления BindingSource (ссылка здесь), чтобы заполнить мой DataGridView управления DataGridView. На нем есть записи 1000+. Я использую потоки, чтобы сделать это. В этом случае DataGridView работает очень медленно.

Я попытался установить для свойства DoubleBuffered значение true, для параметра RowHeadersWidthSizeMode значение "отключено", для параметра AutoSizeColumnsMode значение "нет". Но все же поведение.

Пожалуйста, помогите мне в этом. Как я могу улучшить производительность сетки.

Заранее спасибо,
Виджай

Ответ 1

Если у вас огромное количество строк, например 10 000 и более, во избежание снижения производительности - перед привязкой данных сделайте следующее:

dataGridView1.RowHeadersWidthSizeMode = DataGridViewRowHeadersWidthSizeMode.EnableResizing; 
// or even better, use .DisableResizing. Most time consuming enum is DataGridViewRowHeadersWidthSizeMode.AutoSizeToAllHeaders

// set it to false if not needed
dataGridView1.RowHeadersVisible = false;

После привязки данных вы можете снова включить их.

Ответ 2

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

т.е. не делайте этого:

Datagridview.Columns[I].AutoSizeMode = DataGridViewAutoSizeColumnMode.xxxxx;

Ответ 3

Как правило, автоматическое отключение и двойная буферизация помогают ускорить популяцию DataGridView. Проверьте правильность включения двойной буферизации DGV:

if (!System.Windows.Forms.SystemInformation.TerminalServerSession)
{
  Type dgvType = dataGridView1.GetType();
  PropertyInfo pi = dgvType.GetProperty("DoubleBuffered",
    BindingFlags.Instance | BindingFlags.NonPublic);
  pi.SetValue(dataGridView1, value, null);
}

Отключение перерисовки с помощью сообщения WinAPI WM_SETREDRAW также помогает:

// *** API Declarations ***
[DllImport("user32.dll")]
private static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam);
private const int WM_SETREDRAW = 11;

// *** DataGridView population ***
SendMessage(dataGridView1.Handle, WM_SETREDRAW, false, 0);
// Add rows to DGV here
SendMessage(dataGridView1.Handle, WM_SETREDRAW, true, 0);
dataGridView1.Refresh();

Если вам не нужна двухсторонняя привязка данных или некоторые функции, предоставляемые BindingSource (фильтрация и т.д.), вы можете подумать о добавлении строк за один раз с помощью DataGridView.Rows.AddRange().

Ссылка на исходную статью с образцом: http://10tec.com/articles/why-datagridview-slow.aspx

Ответ 4

Я знаю, что опаздываю на вечеринку, но мне недавно стало надоело, насколько медленным было изменение размера для элемента управления DataGridView, и почувствовал, что кто-то может извлечь выгоду из моего решения.

Я создал этот метод расширения для ручного измерения и изменения размеров столбцов в DataGridView. Установите AutoSizeColumnsMode на DataGridViewAutoSizeColumnsMode.None и вызовите этот метод после установки DataSource.

/// <summary>
/// Provides very fast and basic column sizing for large data sets.
/// </summary>
public static void FastAutoSizeColumns(this DataGridView targetGrid)
{
    // Cast out a DataTable from the target grid datasource.
    // We need to iterate through all the data in the grid and a DataTable supports enumeration.
    var gridTable = (DataTable)targetGrid.DataSource;

    // Create a graphics object from the target grid. Used for measuring text size.
    using (var gfx = targetGrid.CreateGraphics())
    {
        // Iterate through the columns.
        for (int i = 0; i < gridTable.Columns.Count; i++)
        {
            // Leverage Linq enumerator to rapidly collect all the rows into a string array, making sure to exclude null values.
            string[] colStringCollection = gridTable.AsEnumerable().Where(r => r.Field<object>(i) != null).Select(r => r.Field<object>(i).ToString()).ToArray();

            // Sort the string array by string lengths.
            colStringCollection = colStringCollection.OrderBy((x) => x.Length).ToArray();

            // Get the last and longest string in the array.
            string longestColString = colStringCollection.Last();

            // Use the graphics object to measure the string size.
            var colWidth = gfx.MeasureString(longestColString, targetGrid.Font);

            // If the calculated width is larger than the column header width, set the new column width.
            if (colWidth.Width > targetGrid.Columns[i].HeaderCell.Size.Width)
            {
                targetGrid.Columns[i].Width = (int)colWidth.Width;
            }
            else // Otherwise, set the column width to the header width.
            {
                targetGrid.Columns[i].Width = targetGrid.Columns[i].HeaderCell.Size.Width;
            }
        }
    }
}

В то время как я, конечно, никогда не рекомендую заполнять DGV с 1000+ строками, этот метод приводит к огромному выигрышу в производительности при одновременном получении очень похожих результатов методу AutoResizeColumns.

Для 10k строк: (10K строк * 12 столбцов.)

AutoResizeColumns= ~ 3000 мс

FastAutoSizeColumns= ~ 140 мс

Ответ 5

Если вы не хотите переопределять требуемые методы виртуального режима DataGridView, существует еще одна альтернатива, если вы можете рассмотреть использование Listview:

http://www.codeproject.com/Articles/16009/A-Much-Easier-to-Use-ListView

  • У него есть версия (FastObjectListView), которая может создать список из 100 000 объектов менее чем за 0,1 секунды.
  • У него есть версия (DataListView) который поддерживает привязку данных, и другой (FastDataListView), который поддерживает привязку данных к большим (100 000+) наборам данных.

Ответ 6

У меня были проблемы с производительностью, когда пользователь загружал 10000 элементов или сортировал их. Когда я прокомментировал строку:

this.dataEvents.AutoSizeRowsMode = System.Windows.Forms.DataGridViewAutoSizeRowsMode.AllCells;

Все стало хорошо.

Ответ 7

Думаю, вам нужно рассмотреть возможность использования вашей сетки данных в виртуальном режиме. В основном, вы устанавливаете экстенты сетки спереди, затем переопределяете "OnCellValueNeeded" по мере необходимости.

Вы должны найти (особенно только около 1000 строк), что ваше распределение сетки будет эффективно мгновенно.

Удачи,

Ответ 8

Мне пришлось отключить автоматическое изменение размеров в нескольких местах, чтобы увидеть наибольшее улучшение производительности. В моем случае у меня были включены режимы авторазмера для AutoSizeRowsMode, AutoSizeColumnsMode и ColumnHeadersHeightSizeMode. Поэтому мне пришлось отключить каждый из них перед привязкой данных к DataGridView:

dataGridView.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.None;
dataGridView.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None;
dataGridView.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;

// ... Bind the data here ...

// Set the DataGridView auto-size modes back to their original settings.
dataGridView.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.AllCells;
dataGridView.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
dataGridView.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;

Ответ 9

@Bobby L - отличный ответ, но он блокирует поток пользовательского интерфейса. Здесь моя адаптация, которая вычисляет ширину столбца в BackgroundWorker перед применением вычисленных значений в потоке пользовательского интерфейса

public partial class Form1 : Form
{
    private BackgroundWorker _worker;

    public Form1()
    {
        InitializeComponent();

        _worker = new BackgroundWorker();
        _worker.DoWork += _worker_DoWork;
        _worker.RunWorkerCompleted += _worker_RunWorkerCompleted;
    }

    private void _worker_DoWork(object sender, DoWorkEventArgs e)
    {
        e.Result = GetAutoSizeColumnsWidth(dataGridView1);
    }

    private void _worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        SetAutoSizeColumnsWidth(dataGridView1, (int[])e.Result);
    }

    private int[] GetAutoSizeColumnsWidth(DataGridView grid)
    {
        var src = ((IEnumerable)grid.DataSource)
            .Cast<object>()
            .Select(x => x.GetType()
                .GetProperties()
                .Select(p => p.GetValue(x, null)?.ToString() ?? string.Empty)
                .ToArray()
            );

        int[] widths = new int[grid.Columns.Count];
        // Iterate through the columns.
        for (int i = 0; i < grid.Columns.Count; i++)
        {
            // Leverage Linq enumerator to rapidly collect all the rows into a string array, making sure to exclude null values.
            string[] colStringCollection = src.Where(r => r[i] != null).Select(r => r[i].ToString()).ToArray();

            // Sort the string array by string lengths.
            colStringCollection = colStringCollection.OrderBy((x) => x.Length).ToArray();

            // Get the last and longest string in the array.
            string longestColString = colStringCollection.Last();

            // Use the graphics object to measure the string size.
            var colWidth = TextRenderer.MeasureText(longestColString, grid.Font);

            // If the calculated width is larger than the column header width, set the new column width.
            if (colWidth.Width > grid.Columns[i].HeaderCell.Size.Width)
            {
                widths[i] = (int)colWidth.Width;
            }
            else // Otherwise, set the column width to the header width.
            {
                widths[i] = grid.Columns[i].HeaderCell.Size.Width;
            }
        }

        return widths;
    }

    public void SetAutoSizeColumnsWidth(DataGridView grid, int[] widths)
    {
        for (int i = 0; i < grid.Columns.Count; i++)
        {
            grid.Columns[i].Width = widths[i];
        }
    }
}