Все, кто программирует на MFC или WinAPI, рано или поздно сталкиваются
со списками в интерфейсе своего приложения. На первый взгляд этот
простой элемент управления в Windows предоставляет потрясающие
возможности для программиста. Зная все его особенности, можно
создавать высокопроизводительные приложения. Как? Подробности ниже.
Все материалы доступны на сайте http://www.devdoc.ru Наш девиз - новые статьи каждую неделю. Ресурс
находится в постоянном развитии. Если у Вас есть интересный материал,
вы можете опубликовать его на сайте.
Пожалуйста, присылайте свои вопросы и пожелания к темам статей на sub12@devdoc.ru
Все, кто использует 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());
returnTRUE; // 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){case0: //заполняем основной текст
lstrcpy(pDispInfo->item.pszText, m_vData[iItemIndx]);
break;
default:
break; //мы не поддерживаем subitems}}
*pResult = 0;
}
На входе pDispInfo.item содержит тип данных, номер элемента
и его сабитема (subitem). Список может запросить текст элемента,
его иконку и даже состояние. В данном примере поддерживаются элементы, которые
содержат только текст. Поддержка остальных типов делается похожим образом.
Обратите внимание на вызов метода класса CListCtrl: SetItemCountEx.
Он указывает списку, сколько элементов надо запрашивать у приложения. Этот же
метод надо вызывать каждый раз, когда у Вас изменилось содержимое контейнера.
Операции вставки и удаления, как вы догадались, выполняются над контейнером
и список не участвует в этой операции – он просто отображает содержимое.
Метод OnLvnGetdispinfoList является обработчиком LVN_GETDISPINFO
и передает списку содержимое запрашиваемого элемента.
Помимо разделения данных и способа отображения, список дает еще одну интересную
возможность. Он запрашивает у программы только видимые на экране элементы. Это
позволяет организовать эффективное кэширование. В момент вызова метода SetItemCountEx,
контейнер не обязан содержать заявленное количество элементов. Можно заполнять
его по мере надобности, т.е. по запросу списка. Можно использовать и другие
стратегии, например: заполнение из отдельного потока.
Заключение
Использование CListCtrl в режиме виртуального списка позволяет
взять под контроль данные, которые отображаются. Т.к. программист имеет прямой
доступ к контейнеру, – можно проделывать любые операции. Например, можно упорядочить
контейнер произвольным образом. Приложив немного фантазии, можно придумать очень
интересные варианты использования этого режима.
Copyright
(C) Kudinov Alexander, 2006-2007
Перепечатка материалов с данного сайта запрещена без писменного
разрешения автора. При перепечатке обязательно указывать ссылку на оригинал.