• СТАТЬЯ  Использование ListView в режиме виртуального списка
  • ЭКЗАМЕН
  • Программирование на Visual C++

    Выпуск №51 от 21 октября 2001 г.

    Здравствуйте, уважаемые подписчики!

    Наконец-то вы дождались нового сезона выхода рассылки! Вы наверное уже наверняка заметили, что отпуск немного (честно говоря, намного) затянулся. Это связано с моим переездом – получилось так, что мне повезло выиграть стипендию на прохождение магистерской программы в немецком университете. В такой ситуации без всяческих проблем не обойтись, и надо сказать адаптация в Германии заняла гораздо больше времени, чем я рассчитывал. Да и интернет тут у меня появился совсем недавно. Так что прошу прощения за столь долгую отлучку. Теперь все в порядке, и рассылка опять будет набирать обороты.

    Видя, что рассылка так долго не выходит, меня спрашивали, не нужна ли помощь. Отвечаю – помощь всегда нужна! Сейчас помощь больше всего нужна сайту RSDN – в том смысле, что нам не хватает талантливых людей, готовых вести разделы на сайте, верстать статьи и пр. и пр. Если вы чувствуете себя в силах помочь – милости просим, пишите на team@rsdn.ru!

    И еще один организационный вопрос. Рассылка, как и раньше, будет выходить один раз в неделю и предположительно по воскресеньям, хотя день может и меняться.

    Подключившись наконец к интернету и забрав накопившиеся письма, я узнал, что в выложенном мной перед уходом в отпуск chm-файле с архивом рассылки есть некоторые недоработки – а именно, с поиском. Это в принципе неудивильно, так как создавался этот файл достаточно спешно ;-) Я в ближайшее время постараюсь разобраться с этим.

    Ну, это пока все, что я хотел сказать. Теперь – вперед, к вершинам мастерства!

    СТАТЬЯ 

    Использование ListView в режиме виртуального списка

    Автор: Тимофей Чадов

    Демонстрационная программа – 161 KB

    Исходные тексты – 39 KB 

    Все программисты делятся на тех, кто повсеместно применяет виртуальный режим, и тех, кто о нем даже и не слышал. Конечно, это шутка, как и любая с долей … шутки-) 

    На таких сайтах как CodeGuru, есть несколько неплохих примеров применения виртуальных списков. Однако, многие программисты, с которыми мне приходится сталкиваться, лишь изредка прибегают к этой технике, ошибочно считая, что это если не извращение, то уж по крайней мере излишество. Многие заблуждаются, считая, что применение виртуальных списков необходимо только лишь в случаях больших массивов данных, например, при отображении информации из баз данных. Конечно это справедливо, однако, о чем действительно часто забывают – применение виртуальных списков позволяет не только повысить производительность, но и обеспечивает разделение данных и их представлений. Последнее, на мой взгляд, не менее важно. 

    Конечно, как и везде, нужно знать меру. Не стоит сломя голову бежать переписывать свой код, если требуется вывести диалоговое окно для выбора десятков элементов: cойдет и обычный подход. Однако, если логика вашего приложения основана на применении представления на основе ListView с широкими возможностями по добавлению|удалению|редактированию, да к тому же большого объема записей, – стоит задуматься о виртуальном режиме. 

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

    Виртуальность это просто

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

    • Создать виртуальный список

    • Вовремя заботиться о количестве элементов

    • По запросу списка заполнять элементы нужной информацией

    В некоторых случаях возможно понадобятся и более сложные вещи:

    • Кеширование

    • Сортировка

    • Быстрый поиск элементов

    Итак, обо всем по порядку.

    Переход в режим виртуальности

    Чтобы включить режим "виртуальности", необходимо установить стиль LVS_OWNERDATA. Текущая версия библиотеки элементов управления не позволяет переводить список из обычного режима в виртуальный "на лету", поэтому установку данного стиля необходимо делать при создании элемента. Если вы использует редактор диалога достаточно отметить переключатель Owner Data на вкладке More Style в окне свойств List Control. В случае применения класса СListView следует перекрыть PreCreateWindow.

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

    ПРИМЕЧАНИЕ

    В MSDN сказано, что после установки данного стиля, число элементов, которые сможет хранить список, будет ограничено максимальным значением DWORD (для обычных списков только int). Однако, все функции (в том числе и API) для работы со списком принимают int. Кроме этого, мне не удалось использовать более 100.000.000 элементов. Более того, в примере MSJ за ноябрь 1996 г. от Strohm Armstrong встречается именно эта магическая цифра. Отговорка стандартна: "Сложно представить, что возникнет необходимость использовать больше". Нет вопросов, если бы использовалась хотя бы степень двойки, а так, IMHO, ограничение такой странной (круглой) цифрой выглядит коварным замыслом.

    Количество элементов

    Итак, список создан. Чтобы вставить в него элементы, достаточной задать их количество. Количество элементов в виртуальном списке задается одной из следующих функций.

    void CListCtrl::SetItemCount(int iCount)

    void CListCtrl::SetItemCountEx(int iCount, DWORD dwFlags = LVSICF_NOINVALIDATEALL);

    iCount новое количество элементов
    dwDlags Комбинация флагов, определяющая реакцию списка на изменение количества элементов. LVSICF_NOINVALIDATEALL Список не будет перерисован, пока добавленные элементы не окажутся с поле видимости. LVSICF_NOSCROLL Позиция скроллинга не изменится

    Таким образом, все что нам нужно, чтобы оперировать элементами списка, – это задать их количество. Никаких вызовов InsertItem, DeleteItem и т.п. Это существенно упрощает код, отвечающий за манипуляцию с данными. Конечно, это не избавляет от подобных операций с самой информацией, однако, разделение данные-представление благоприятно сказывается на ясности кода, а значит способствует уменьшению ошибок.

    Содержание элементов

    Итак, виртуальный список хранит очень мало информации. За заполнение элементов перед отрисовкой отвечает приложение. Для этого список посылает уведомление LVN_GETDISPINFO. Обработчик несложно добавить, воспользовавшись ClassWizard.

    В обработчике уведомления LVN_GETDISPINFO необходимо проверить, какая информация требуется, и заполнить соответствующие поля.

    В следующем примере показан один из способов реализации.

    void CMyListView::OnGetdispinfo(NMHDR* pNMHDR, LRESULT* pResult) {

     LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;

     LV_ITEM* pItem= &(pDispInfo)->item;

     CMyDocument* pDoc = GetDocument();

     int nIndex = pItem->iItem

     if (pItem->mask & LVIF_TEXT) //требуется текст?

     strcpy(pItem->pszText, pDoc->GetItemText(pItem->iSubItem, nIndex));

     if pItem->mask & LVIF_IMAGE) //требуется картинка

     pItem->iImage = pDoc->GetItemImage(nIndex);

    }

    Здесь GetItemText и GetItemImage функции класса документа, возвращающие текст меток и номер изображения требуемого элемента соответственно.

    По умолчанию виртуальный список не хранит информацию поля state, за исключением двух флагов LVIS_SELECTED и LVIS_FOCUSED. Это приводит к тому, что использование иконок состояния (state image) невозможно. Однако эту ситуацию легко исправить. Необходимо использовать сообщение LVM_SETCALLBACKMASK, позволяющее задать маску для хранимой списком информации об элементах.

    // Разрешаем использовать иконки состояния

    SendMessage(LVM_SETCALLBACKMASK, LVIS_STATEIMAGEMASK, 0)

    Кажущиеся трудности

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

    Управление кешированием

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

    void CMyListView::OnOdcachehint(NMHDR* pNMHDR, LRESULT* pResult) {

     NMLVCACHEHINT* pCacheHint = (NMLVCACHEHINT*)pNMHDR;

     // Подготовить кеш

     PrepareCach( pCacheHint->iFrom, pCacheHint->iTo);

     *pResult = 0;

    }

    Реализация функции, подобной PrepareCach, зависит от того, каким способом вы храните и обрабатываете данные, и может быть различной в зависимости от решаемой задачи. Данное уведомление всего лишь своевременная подсказка.

    Нахождение специфических элементов

    Когда списку необходимо найти специфический элемент, он посылает уведомление LVN_ODFINDITEM. Это может случиться, если требуется реализовать нажатие быстрой клавиши (поиск по имени), или элемент получил сообщение LVM_FINDITEM. Информация для поиска передается в двух структурах NMLVFINDITEM и LVFINDINFO. В них содержится: номер элемента, с которого следует начать поиск; элемент искомой строки; направление поиска и т.п.

    void CMyListView::OnOdfinditem(NMHDR* pNMHDR, LRESULT* pResult) {

     NMLVFINDITEM* pFindInfo = (NMLVFINDITEM*)pNMHDR;

     LVFINDINFO FindItem = pFindInfo->lvfi;

     if (FindItem.flags & LVFI_STRING)

      //ищем FindItem.psz начиная pFindInfo->iStart

      *pResult = GetDocument()->FindItem(FindItem.psz, pFindInfo->iStart);

      return;

     }

     *pResult = –1;

    }

    Обработчик уведомления должен вернуть номер искомого элемента или –1 в случае неудачи.

    Сортировка

    Трудности? Это еще что такое? Однако бесплатный сыр сами знаете где. Дело в том, что, так как сами элементы в списке не хранятся, придется самим заботится о сортировке. Не удастся воспользоваться функцией CListCtrl::SortItems, бесполезно писать CompareItems и т.п. Все, что у вас есть – это порядковый номер элемента.

    Но, нет худа без добра. Действительно, обладая дополнительной информацией об используемых данных, можно выбрать более подходящую функцию сортировки, а значит – повысить производительность. Кроме того, в ряде случаев, даже такая проблема не стоит. Если информация берется из базы данных, нет необходимости самостоятельно сортировать элементы, достаточно учесть это при составлении запроса. При использовании в качестве хранилища элементов контейнеров из STL, можно использовать возможности этой библиотеки. В большинстве практических случаев этого бывает достаточно.

    Альтернатива в заключение

    Виртуальный режим – не единственный способ заставить список запрашивать информацию об элементах. Можно при добавлении элемента задать значение pszText структуры LVITEM равным LPSTR_TEXTCALLBACK. В этом случае, также будут приходить уведомления LVN_GETDISPINFO. Однако при этом, придется самостоятельно заботиться о добавлении|удалении элементов, вместо одного вызова SetItemCount для виртуального режима. Кроме того, не будет заметного выигрыша в экономии памяти и скорости. Более подробно данный способ описан в статье Chris Maunder. Using text callbacks in ListView Controls.

    Напоследок, небольшое резюме. Как вы уже поняли, элемент управления ListView достаточно гибок в использовании, и для написания качественного кода, важно не ошибиться в выборе необходимого режима работы. У каждого, как всегда, свои плюсы и минусы. Выбор (и ответственность) за Вами. Я всего лишь хотел помочь разобраться с этим.

    P.S. Специальное спасибо Willi за подсказку об иконках состояния.

    ЭКЗАМЕН

    В этой новой рубрике будут публиковаться вопросы из самых различных экзаменов и тестов по Visual C++, WinAPI, MFC и др., а также конечно ответы на них ;-). Надеюсь вы найдете эту рубрику полезной. 

    What synchronization object can only be used to synchronize threads for a single process? 

    1. CriticalSection

    2. Semaphore

    3. Mutex

    4. Timer

    5. Event 

    Верный ответ – 1. Critical Section. Критические секции не могут быть использованы для синхронизации потоков, принадлежащих разным процессам.


    Это все на сегодня. До встречи!

    (Алекс Jenter jenter@rsdn.ru) (Duisburg, 2001. Рассылка является частью проекта RSDN. )







    Главная | В избранное | Наш E-MAIL | Прислать материал | Нашёл ошибку | Наверх