суббота, 9 июня 2018 г.

CListCtrl::SortItems()

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

Положим, что есть объект
CListCtrl lwObjects;
Чтобы вызвать сортировку, надо передать функции SortItems() указатель на функцию сортировки предопределенного прототипа и указатель или данные контекста сортировки.

Функция сортировки имеет прототип
int CALLBACK CompareLVItems(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
И должна вернуть коды в соглашениях языка Си - либо отрицательное значение если первый элемент меньше, либо 0 если элементы равны либо положительное. Неприятность состоит в том, что если в параметр lParamSort передается наш контекст сортировки, который мы передали в функцию SortItems, то в параметры lParam1 и lParam2 передаются не указатели на элементы списка, а данные, которые были ассоциированы с этими элементами вызовом функции CListCtrl::SetItemData. Поэтому во-первых мы вынуждены использовать эту функцию и придумать некий идентифицирующий код или указатель, по которому сможем найти как эти элементы должны быть отсортированы.

В моем случае это был действительно уникальный в рамках набора элементов код, для каждого элемента списка свой. Если такого нельзя придумать, то надо заводить массив объектов и прописывать в CListCtrl::SetItemData указатель на соответствующий.

Мне надо было отсортировать по строке записанной в первой колонке списка. Для этого надо по коду ассоциированному с элементом получить номер элемента списка и по номеру получить строку первой колонки. И собственно сам код получился таким:
static int CALLBACK CompareLVItems(LPARAM lParam1, 
        LPARAM lParam2, LPARAM lParamSort)
{
 // lParamSort is pointer to CListCtrl
 CListCtrl* pListCtrl = (CListCtrl*) lParamSort;
 // lParam1 and lParam2 are not pointers to items and are not item indices, 
 // there are assisiated data, so we need to find them first, 
        // and keep this data sort-avare
 LVFINDINFO info;
 info.flags = LVFI_PARTIAL | LVFI_PARAM;

 info.lParam = lParam1;
 int n1 = pListCtrl->FindItem(&info);

 info.lParam = lParam2;
 int n2 = pListCtrl->FindItem(&info);

 CString strItem1 = pListCtrl->GetItemText(n1, 0);
 CString strItem2 = pListCtrl->GetItemText(n2, 0);

 return strItem1.CompareNoCase(strItem2);
};

...
lwObjects.SortItems( CompareLVItems, (DWORD_PTR)&lwObjects);
...
Хотя в документации Microsoft написано что DWORD_PTR это 32 битное значение, на самом деле это устаревшие сведения и суффикс _PTR решает проблему для x64 систем. И тип DWORD_PTR и тип LPARAM переносят указатели x64 без проблем.

Удачи и успехов)))

Комментариев нет:

Отправить комментарий