Низкая производительность при заполнении 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];
}
}
}