C语言 WinAPI - 如何实现列表视图排序?



尝试在 C 语言的 WinAPI ListView 中对值进行排序,代码基于示例、MSDN 和包括 SO 在内的多个问答论坛。创建列和插入项的代码如下所示:

int CreateColumn(HWND hwndList, int col_number, wchar_t* title, int width)
{
LVCOLUMN lvc;
lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
lvc.fmt = LVCFMT_LEFT;
lvc.cx = width;
lvc.pszText = title;
lvc.iSubItem = col_number;
return ListView_InsertColumn(hwndList, col_number, &lvc);
}
void InsertItem(HWND hwndList, int row, wchar_t* txt0, wchar_t* txt1, wchar_t* txt2)
{
LVITEM lvi = { 0 };
lvi.mask = LVIF_TEXT | LVIF_PARAM;
lvi.iItem = row;
lvi.iSubItem = 0;
lvi.lParam = (LPARAM)txt0;
lvi.pszText = txt0;
ListView_InsertItem(hwndList, &lvi);
ListView_SetItemText(hwndList, row, 0, txt0);
lvi.iSubItem = 1;
lvi.lParam = (LPARAM)txt1;
lvi.pszText = txt1;
ListView_SetItem(hwndList, &lvi);
ListView_SetItemText(hwndList, row, 1, txt1);
lvi.iSubItem = 2;
lvi.lParam = (LPARAM)txt2;
lvi.pszText = txt2;
ListView_SetItem(hwndList, &lvi);
ListView_SetItemText(hwndList, row, 2, txt2);
}
// in another function
CreateColumn(hWndListView, 0, L"Col 0", 150);
CreateColumn(hWndListView, 1, L"Col 1", 150);
CreateColumn(hWndListView, 2, L"Col 2", 150);
InsertItem(hWndListView, 0, L"000.0", L"000.9", L"000.10");
InsertItem(hWndListView, 1, L"000.1", L"000.8", L"000.30");
InsertItem(hWndListView, 2, L"000.2", L"000.7", L"000.20");

然后,比较器函数和事件处理程序如下:

case WM_NOTIFY:
switch (((LPNMHDR)lParam)->code)
{
case LVN_COLUMNCLICK:
{
OnColumnClick((LPNMLISTVIEW)lParam);
break;
}
}
break;
int CALLBACK myCompFunc(LPARAM lp1, LPARAM lp2, LPARAM sortParam)
{
BOOL isAsc = (sortParam > 0);
int column = abs(sortParam) - 1;
wchar_t *p1, *p2;
p1 = (wchar_t*)lp1;
p2 = (wchar_t*)lp2;
if (isAsc)
return strcmp(p1, p2);
else
return strcmp(p2, p1);
}
void OnColumnClick(LPNMLISTVIEW pLVInfo)
{
static int nSortColumn = 0;
static BOOL bSortAscending = TRUE;
LPARAM lParamSort;
// get new sort parameters
if (pLVInfo->iSubItem == nSortColumn)
bSortAscending = !bSortAscending;
else
{
nSortColumn = pLVInfo->iSubItem;
bSortAscending = TRUE;
}
// combine sort info into a single value we can send to our sort function
lParamSort = 1 + nSortColumn;
if (!bSortAscending)
lParamSort = -lParamSort;
// sort list
ListView_SortItems(pLVInfo->hdr.hwndFrom, myCompFunc, lParamSort);
}

我注意到,如果我将指针传递给 InsertItem 函数而不是硬编码文本,那么单击列会产生效果(它"有点"排序,不完全符合预期,但项目的顺序会发生变化(。如果只有硬编码文本,则单击标题不起作用。

问题 1:为什么这不适用于硬编码值?

问题 2:在 C 语言中比较字符串比 strcmp 有没有"更好"的函数?这是排序没有给出预期结果的原因吗(排序不正确,例如 10 -> 30 -> 20 永远不会排序(?

编辑在调试器(Win10 上的 VS2019(中,比较器函数中的值似乎编码错误。不知何故,看起来参数类型在某处丢失了。

lvi.lParam可以是整数值或指针。如果它是一个指针,那么它通常指向堆中分配的数据。

此外,列表视图中的每一行只能与一个lvi.lParam相关联。您似乎正在尝试为每行中的每一列关联单独的lvi.lParam数据。但这无法完成,因此对ListView_SetItem的第二次调用将失败,因为设置了LVIF_PARAM,并且lvi.iSubItem大于零。此代码将不起作用:


lvi.mask = LVIF_TEXT | LVIF_PARAM;
lvi.iItem = row;
lvi.iSubItem = 1;
lvi.lParam = (LPARAM)txt1;
lvi.pszText = txt1;
ListView_SetItem(hwndList, &lvi); //<== this call will fail
解决方案 1:使用ListView_SortItemsEx

我们忽略lParam,我们使用ListView_GetItemText来获取比较函数中的文本:

typedef struct
{
HWND hlist;
int  iSubItem;
BOOL bSortAscending;
}t_data;
int CALLBACK myCompFuncEx(LPARAM lp1, LPARAM lp2, LPARAM sortParam)
{
t_data *data = (t_data*)sortParam;
wchar_t buf1[100], buf2[100];
ListView_GetItemText(data->hlist, lp1, data->iSubItem, buf1, _countof(buf1));
ListView_GetItemText(data->hlist, lp2, data->iSubItem, buf2, _countof(buf2));
int res = wcscmp(buf1, buf2);
return data->bSortAscending ? res >= 0 : res <= 0;
}
void OnColumnClickEx(LPNMLISTVIEW pLVInfo)
{
static int nSortColumn = 0;
static BOOL bSortAscending = TRUE;
if(pLVInfo->iSubItem != nSortColumn)
bSortAscending = TRUE;
else
bSortAscending = !bSortAscending;
nSortColumn = pLVInfo->iSubItem;
t_data data;
data.hlist = pLVInfo->hdr.hwndFrom;
data.iSubItem = nSortColumn;
data.bSortAscending = bSortAscending;
ListView_SortItemsEx(pLVInfo->hdr.hwndFrom, myCompFuncEx, &data);
}


解决方案2:使用ListView_SortItems

我们可以为字符串分配单独的内存,然后将lParam值关联到该字符串分配。

此方法更复杂,但它可以更快,因为我们对ListView_GetItemText进行N调用(其中N是 ListView 中的总数(。在以前的解决方案中,我们ListView_GetItemText进行N * log(N)调用,如果列表太大,可能会有明显的差异(尽管我还没有测试过(

void OnColumnClick(LPNMLISTVIEW pLVInfo)
{
static int column = 0;
static BOOL ascending = TRUE;
if(pLVInfo->iSubItem != column)
ascending = TRUE;
else
ascending = !ascending;
column = pLVInfo->iSubItem;
HWND hlist = pLVInfo->hdr.hwndFrom;
int count = ListView_GetItemCount(hlist);
if(count < 1) return;
//allocate strings
wchar_t** arr = malloc(count * sizeof(wchar_t*));
LVITEM lvi = { 0 };
lvi.mask = LVIF_PARAM;
for(int i = 0; i < count; i++)
{
//get column string
wchar_t buf[100]; //random max buffer size, hopefully it's big enough
ListView_GetItemText(hlist, i, column, buf, _countof(buf));
arr[i] = _wcsdup(buf);
//match lParam to the string
lvi.lParam = (LPARAM)arr[i];
lvi.iItem = i;
ListView_SetItem(hlist, &lvi);
}
ListView_SortItems(hlist, myCompFunc, (LPARAM)&ascending);
//cleanup
for(int i = 0; i < count; i++)
free(arr[i]);
free(arr);
}
int CALLBACK myCompFunc(LPARAM lp1, LPARAM lp2, LPARAM sort_data)
{
BOOL ascending = *(BOOL*)(sort_data);
int res = wcscmp((const wchar_t*)lp1, (const wchar_t*)lp2);
return ascending ? res >= 0 : res <= 0;
}

编辑,我们可以更改InsertItem函数,以便ListView_InsertItem返回一个索引,然后在ListView_SetItemText中使用该索引。

一般来说,这会更安全。例如,如果设置了LVS_SORTASCENDING/LVS_SORTDESCENDING,则ListView_InsertItem返回的索引与请求的行不同。

void InsertItem(HWND hwndList, 
const wchar_t* txt0, const wchar_t* txt1, const wchar_t* txt2)
{
LVITEM lvi = { 0 };
lvi.mask = LVIF_TEXT;
lvi.pszText = (wchar_t*)txt0;
int index = ListView_InsertItem(hwndList, &lvi);
ListView_SetItemText(hwndList, index, 1, (wchar_t*)txt1);
ListView_SetItemText(hwndList, index, 2, (wchar_t*)txt2);
}

最新更新