Является ли Windows Forms DataGridView реалистичным виртуальным режимом?

У меня есть таблица SQL, содержащая в настоящее время 1 миллион строк, которые со временем будут расти.

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

Я знаком с сетями виртуального режима, которые представляют только видимое подмножество общих данных. Они могут обеспечить отличную производительность пользовательского интерфейса и минимальные требования к памяти (я даже реализовал приложение, использующее этот метод много лет назад).

В Windows Forms DataGridView предусмотрен виртуальный режим, который выглядит как ответ. Однако, в отличие от других виртуальных режимов, с которыми я столкнулся, он по-прежнему выделяет память для каждой строки (подтвержденной в ProcessExplorer). Очевидно, что это приводит к необратимому увеличению общего использования памяти, и, выделяя эти строки, наблюдается заметная задержка. Производительность прокрутки также страдает от 1 миллиона + строк.

Для реального виртуального режима не нужно выделять какую-либо память для строк, которые не отображаются. Вы просто даете ему общее количество строк (например, 1 000 000), и вся сетка действительно масштабирует полосу прокрутки соответственно. Когда он отображается первым, сетка просто запрашивает данные только первые n (скажем, 30) видимых строк, мгновенное отображение.

Когда пользователь прокручивает сетку, предоставляется простое смещение строки и количество видимых строк и могут использоваться для извлечения данных из хранилища данных.

Вот пример кода DataGridView, который я использую в настоящее время:

public void AddVirtualRows(int rowCount)
{
    dataGridList.ColumnCount = 4;


    dataGridList.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None;
    dataGridList.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.None;

    dataGridList.VirtualMode = true;

    dataGridList.RowCount = rowCount;

    dataGridList.CellValueNeeded += new DataGridViewCellValueEventHandler(dataGridList_CellValueNeeded);


}
void dataGridList_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
{
    e.Value = e.RowIndex;
}

Мне что-то не хватает, или виртуальный режим DataGridView вообще не виртуальный?

[Обновление]

Похоже, что старый добрый ListView реализует именно тот виртуальный режим, который я ищу. Но, к сожалению, ListView не имеет возможности форматирования ячеек DataGridView, поэтому я не могу его использовать.

Для других, которые могли бы это сделать, я протестировал его с помощью четырех столбца ListView (в подробном режиме), VirtualMode = True и VirtualListSize = 100 000 000 строк.

Список отображается немедленно, когда первые 30 строк видны. Затем я могу быстро прокручиваться до нижней части списка без задержки. Использование памяти постоянно 10 МБ.

Ответ 1

У нас просто было аналогичное требование, чтобы иметь возможность отображать произвольные, неиндексированные таблицы строк 1M + в нашем приложении с "очень хорошей" производительностью, используя запас DataGridView. Сначала я подумал, что это невозможно, но с достаточным количеством царапин на голове, мы придумали что-то, что очень хорошо работает после того, как они проводят дни, перелистывая Reflector и .NET Profiler. Это было трудно сделать, но результаты были достойны этого.

То, как мы решали эту проблему, заключалось в создании класса, который реализует ITypedList и IBindingList (вы можете назвать его LargeTableView, например), чтобы управлять асинхронным извлечением и кэшированием информации из базы данных. Мы также создали единственный класс наследования PropertyDescriptor (например, LargeTableColumnDescriptor) для извлечения данных из каждого столбца.

Если для свойства DataGridView.DataSource задано класс, реализующий IBindingList, он переходит в псевдо-виртуальный режим , который отличается от обычного VirtualMode, где, когда каждая строка окрашивается (например, когда пользователь прокручивает), DataGridView обращается к индексу [] в IBindingList и соответствующих методах GetValue для каждого столбца PropertyDescriptor для получения значений по мере необходимости. Событие CellValueNeeded не задано. В нашем случае мы обращаемся к базе данных при доступе к индексу, а затем кэшируем значение, чтобы последующие повторные краски не попадали в базу данных.

Я выполнил аналогичные тесты re: использование памяти. DataGridView выделяет массив, который является размером списка (т.е. 1M строк), однако каждый элемент в массиве первоначально ссылается на один DataGridViewRow, поэтому использование памяти является приемлемым. Я не уверен, соответствует ли поведение VirtualMode истинному. Мы смогли устранить задержку прокрутки, сразу же возвращая String.Empty в методе GetValue, если строка не кэширована, а затем выполняет запрос базы данных асинхронно. Когда асинхронный запрос будет завершен, вы можете поднять событие IBindingList.ListChanged, чтобы подать сигнал в DataGridView, чтобы он перекрашивал ячейки, за исключением того, что это время читалось из кэша, который легко доступен. Таким образом, пользовательский интерфейс никогда не блокируется в ожидании вызовов базы данных.

Мы заметили, что производительность значительно лучше, если вы установите DataSource или количество виртуальных строк до, добавив DataGridView в форму - это сократило время инициализации наполовину. Кроме того, убедитесь, что у вас установлена ​​автоматическая настройка Row и Column на None, иначе у вас появятся дополнительные проблемы с производительностью.

Боковое примечание: способ, которым мы выполнили "загрузку" такой большой таблицы в нашем приложении .NET, - это создать временную таблицу на SQL-сервере, в которой перечислены первичные ключи в желаемом порядке сортировки, а также IDENTITY (номер строки), а затем продолжить соединение для последующих запросов строк. Это, естественно, требует времени для инициализации (примерно 3-5 секунд на достаточно быстром SQL-сервере), но без знания доступных индексов у нас нет лучшей альтернативы. Затем в нашей реализации ITypedList мы запрашиваем строки на страницах из 100 строк, где 50-я строка - это строка, которая нарисована, поэтому мы ограничиваем количество запросов, выполняемых каждый раз, когда обращается к индексу, и что мы даем внешний вид наличия всех данных, доступных в нашем приложении.

Дальнейшее чтение:

http://msdn.microsoft.com/en-us/library/ms404298.aspx

http://msdn.microsoft.com/en-us/library/system.componentmodel.ibindinglist.aspx

Ответ 4

Я бы сказал, да... до тех пор, пока вы будете придерживаться событий, вызванных действиями виртуального режима (например, CellValueNeeded), и что вы хорошо заботитесь об очистке своего встроенного буфера. Я уже показывал большой объем данных, более 1 М без суеты.

Мне немного интересно узнать о внедрении Kevin McCormick с использованием DataSource на основе ITypedList или любых реализаций интерфейса, связанных с IList. Я предполагаю, что это еще один слой абстракции, который использует внутренний и прозрачный буфер в дополнение к тому, что пользователь или разработчик поддерживает DataGridView с этим, но все же внутренне взаимодействует с собственным VirtualMode, чтобы отобразить информацию, которую вы загрузили в буфер.

Помимо способа способа обхода виртуального режима, для меня единственной трудной проблемой, остающейся с DataGridView, является ограничение RowCount: это все еще Int32.Max. Вероятно, это связано с наследием системы рисования Winforms... так же, как изображения или даже все члены размера, ширина, высота элементов управления Winform... почему бы не придерживаться типа UInt32?

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

См. мой ответ ниже, может помочь вам, если вы все еще застряли в этой проблеме, если я думаю, что он уже разрешен уже давно. fooobar.com/info/510917/...