DevDoc home page DevDoc background gradient
 
   
Имя Пароль Запомнить Зарегистрироваться

Автор: Кудинов Александр
Последняя модификация: 2007-02-03 12:22:48

Скрытые возможности CListCtrl. Виртуальный список

Скачать исходники.

Введение

Все, кто использует MFC или функции WinAPI, рано или поздно сталкиваются со списками в графическом интерфейсе. На первый взгляд этот простой элемент управления в Win32 предоставляет потрясающие возможности для программиста. Зная все его особенности, можно создавать высокопроизводительные приложения.

В архитектуру этого элемента управления заложена возможность модификации внешнего вида, графического оформления и даже поведения. CListCtrl зачастую является неотъемлемым элементом графического интерфейса, поэтому важно уметь использовать все его возможности.

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

Описание

Рассматривая список методов класса CListCtrl, становится понятно, как добавлять, удалять, искать, модифицировать элементы списка. В большинстве случаев этого достаточно для работы. Те, кто много работали с этим элементом управления или наблюдали за его поведением в разных программах, могли заметить, что операция добавления в список достаточно медленная.

Некоторое время назад мне надо было создать компонент, который бы показывал список файлов с иконками в папке. Раз надо – сделано. В процессе ближайшего рассмотрения выяснилось, что обновление иногда происходит очень медленно. Это хорошо заметно на папках с большим количеством файлов, например, Windows\System32. После детального исследования я нашел 2 причины тормозов:

  • медленное чтение с диска
  • очень медленное добавление результатов в список.

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

Интригующе? Тогда продолжим. Рассмотрим код:

for(int i = 0; i < 100; i++)
{
  m_cList.InsertItem(i, "Test string");
}

Здесь m_cList - экземпляр класса CListCtrl. Мы просто добавляем 100 элементов в список. Особенность такого метода в том, что если эта операция выполняется в обработчике оконного сообщения (а часто именно так и происходит), то содержимое на экране обновится только после того, как вы вставите все элементы. Если речь идет о тысячах элементов – пользователь может сидеть и ждать заполнения списка несколько секунд, особенно если в этом же цикле мы получаем данные, скажем, с диска.

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

Можно заставить CListCtrl не хранить элементы, которые он отображает. Вместо этого при отображении очередного элемента список будет запрашивать новую порцию данных. Как будет организовано хранение элементов, целиком зависит от фантазии и навыков программиста. В простейшем случае это может быть массив или std::vector. Это и есть режим виртуального списка! Элемент управления отвечает только за правильное отображение информации, а программист - за оптимальное хранение и выборку данных.

Виртуальный список

Включение виртуального режима производится установкой свойства Owner Data: true, в редакторе ресурсов, или стиля LVS_OWNERDATA, если вы создаете экземпляр класса CListCtrl вручную. Виртуальный список не может быть использован со стилями: LVS_SORTASCENDING или LVS_SORTDESCENDING.

После этого стандартные способы добавления/удаления элементов перестают работать, т.к. список больше сам не хранит данные. В нашем примере для хранения данных воспользуемся стандартным контейнером: std::vector<CString>.

Список запрашивает родительское окно о своем содержимом с помощью сообщения LVN_GETDISPINFO.

//virtlistDlg.h
class CvirtlistDlg : public CDialog
{
…..
protected:
	afx_msg void OnLvnGetdispinfoList(NMHDR *pNMHDR, LRESULT *pResult);
 
	CListCtrl m_cList;
 
	vector<CString> m_vData;
}
 
 
//virtlistDlg.cpp
 
BEGIN_MESSAGE_MAP(CvirtlistDlg, CDialog)
	ON_NOTIFY(LVN_GETDISPINFO, IDC_LIST1, OnLvnGetdispinfoList)
END_MESSAGE_MAP()
 
BOOL CvirtlistDlg::OnInitDialog()
{
	CDialog::OnInitDialog();
 …
	//резервируем место, чтобы операция вставки работала быстрее.
	m_vData.reserve(1000);
	for(unsigned i = 0; i < 1000; i++)
	{
		m_vData.push_back("Test string");		//готовим строку данных для отображения
	}
 
	//указываем списку, сколько элементов он содержит
	m_cList.SetItemCountEx((int)m_vData.size());
 
	
	return TRUE;  // return TRUE  unless you set the focus to a control
}
 
void CvirtlistDlg::OnLvnGetdispinfoList(NMHDR *pNMHDR, LRESULT *pResult)
{
	//Эта структура содержит запрос от списка. Сюда же мы помещаем данные, которые
	//надо отображать.
	NMLVDISPINFO *pDispInfo = reinterpret_cast <NMLVDISPINFO*> (pNMHDR);
 
	int iItemIndx= pDispInfo->item.iItem;
 
	if(pDispInfo->item.mask & LVIF_TEXT)
	{
		switch(pDispInfo->item.iSubItem)
		{
		case 0: //заполняем основной текст
			lstrcpy(pDispInfo->item.pszText, m_vData[iItemIndx]);
			break;
		default:
			break;	//мы не поддерживаем subitems
		}
 
	}
	*pResult = 0;
 }

На входе pDispInfo.item содержит тип данных, номер элемента и его сабитема (subitem). Список может запросить текст элемента, его иконку и даже состояние. В данном примере поддерживаются элементы, которые содержат только текст. Поддержка остальных типов делается похожим образом.

Обратите внимание на вызов метода класса CListCtrl: SetItemCountEx. Он указывает списку, сколько элементов надо запрашивать у приложения. Этот же метод надо вызывать каждый раз, когда у Вас изменилось содержимое контейнера.

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

Метод OnLvnGetdispinfoList является обработчиком LVN_GETDISPINFO и передает списку содержимое запрашиваемого элемента.

Помимо разделения данных и способа отображения, список дает еще одну интересную возможность. Он запрашивает у программы только видимые на экране элементы. Это позволяет организовать эффективное кэширование. В момент вызова метода SetItemCountEx, контейнер не обязан содержать заявленное количество элементов. Можно заполнять его по мере надобности, т.е. по запросу списка. Можно использовать и другие стратегии, например: заполнение из отдельного потока.

Заключение

Использование CListCtrl в режиме виртуального списка позволяет взять под контроль данные, которые отображаются. Т.к. программист имеет прямой доступ к контейнеру, – можно проделывать любые операции. Например, можно упорядочить контейнер произвольным образом. Приложив немного фантазии, можно придумать очень интересные варианты использования этого режима.

Продолжение следует...

Подпишитесь, чтобы получать все новые статьи с сайта первым!

Оценить статью Текущий рейтинг: 3.1073. Проголосовало 177 человек.

Коментарии к статье

Нет комментариев

Добавить комментарий

Вы можете добавлять свои комментарии, пожелания или вопросы к статье. Они будут видны всем пользователям, а также автору статьи. Войдите в систему под своим именем, чтобы другие пользователи могли видеть, кто автор сообщения. Форма для входа расположена вверху страницы. Если у Вас нет аккаунта на сайте - рекомендуем зарегистрироваться. Это займет всего пару минут.

Отправлять сообщения могут только зарегистрированные пользователи

Copyright (C) Kudinov Alexander, 2006-2012

Перепечатка материалов с данного сайта запрещена без письменного разрешения автора. При перепечатке обязательно указывать ссылку на оригинал.

Generation time: 0,242383003235 seconds