如何让ListBox控件中仅指定项不可选中?
实现ListBox部分列表项不可选中
Windows原生ListBox的LBS_NOSEL属性会让所有项都无法选中,但要实现仅部分项不可选中,需要通过自绘ListBox+自定义状态标记的方式来实现,具体步骤如下:
1. 修改ListBox创建风格
首先要给ListBox添加自绘相关风格,让我们能控制项的绘制和交互逻辑:
hList = CreateWindowEx(WS_EX_CLIENTEDGE, "LISTBOX", NULL, WS_VISIBLE | WS_BORDER | WS_CHILD | LBS_CENTER | WS_HSCROLL | WS_VSCROLL | LBS_OWNERDRAWFIXED | LBS_HASSTRINGS, 20, 55, 200, 120, hWindow, (HMENU)ID_LIST, hInstance, 0);
注意:原代码中的
ES_CENTER是编辑框的风格,ListBox文本对齐应该用LBS_CENTER;新增的LBS_OWNERDRAWFIXED启用固定高度的自绘,LBS_HASSTRINGS保留ListBox的文本存储能力。
2. 给列表项标记可选中状态
添加项时,用LB_SETITEMDATA给每个项绑定一个布尔值,标记该项目是否可选中:
// 添加可选中项 int itemIdx = SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)"可选中项1"); SendMessage(hList, LB_SETITEMDATA, itemIdx, (LPARAM)TRUE); // TRUE代表可选中 // 添加不可选中项 itemIdx = SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)"不可选中项1"); SendMessage(hList, LB_SETITEMDATA, itemIdx, (LPARAM)FALSE); // FALSE代表不可选中
3. 自绘列表项(WM_DRAWITEM处理)
在窗口过程中处理WM_DRAWITEM消息,根据标记状态绘制不同样式的项(比如不可选中项用灰色文本):
case WM_DRAWITEM: { LPDRAWITEMSTRUCT lpDIS = (LPDRAWITEMSTRUCT)lParam; if (lpDIS->CtlID == ID_LIST) { // 填充背景 FillRect(lpDIS->hDC, &lpDIS->rcItem, (HBRUSH)(COLOR_WINDOW + 1)); // 获取项文本 TCHAR szText[256]; SendMessage(lpDIS->hwndItem, LB_GETTEXT, lpDIS->itemID, (LPARAM)szText); // 获取项的可选中状态 BOOL isSelectable = (BOOL)SendMessage(lpDIS->hwndItem, LB_GETITEMDATA, lpDIS->itemID, 0); // 设置文本颜色与背景色 if (!isSelectable) { SetTextColor(lpDIS->hDC, GetSysColor(COLOR_GRAYTEXT)); } else if ((lpDIS->itemState & ODS_SELECTED) && (lpDIS->itemAction & (ODA_SELECT | ODA_DRAWENTIRE))) { SetTextColor(lpDIS->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT)); SetBkColor(lpDIS->hDC, GetSysColor(COLOR_HIGHLIGHT)); } else { SetTextColor(lpDIS->hDC, GetSysColor(COLOR_WINDOWTEXT)); SetBkColor(lpDIS->hDC, GetSysColor(COLOR_WINDOW)); } // 居中绘制文本 DrawText(lpDIS->hDC, szText, -1, &lpDIS->rcItem, DT_CENTER | DT_VCENTER | DT_SINGLELINE); // 仅给可选中项绘制焦点框 if ((lpDIS->itemState & ODS_FOCUS) && isSelectable) { DrawFocusRect(lpDIS->hDC, &lpDIS->rcItem); } return TRUE; } break; }
4. 拦截选择操作,阻止不可选中项被选中
还要处理交互事件,避免用户通过鼠标或键盘选中不可选项目:
处理LBN_SELCHANGE通知
在WM_COMMAND中拦截列表项选中变更事件,取消不可选中项的选中状态:
case WM_COMMAND: { int wmId = LOWORD(wParam); int wmEvent = HIWORD(wParam); switch (wmId) { case ID_LIST: if (wmEvent == LBN_SELCHANGE) { int curSel = SendMessage(hList, LB_GETCURSEL, 0, 0); if (curSel != LB_ERR) { BOOL isSelectable = (BOOL)SendMessage(hList, LB_GETITEMDATA, curSel, 0); if (!isSelectable) { SendMessage(hList, LB_SETCURSEL, -1, 0); // 取消选中 } } } break; // 其他命令处理逻辑... } break; }
拦截鼠标点击
处理WM_LBUTTONDOWN,直接忽略对不可选中项的点击:
case WM_LBUTTONDOWN: { POINT pt; GetCursorPos(&pt); ScreenToClient(hList, &pt); int itemIdx = SendMessage(hList, LB_ITEMFROMPOINT, 0, MAKELPARAM(pt.x, pt.y)); if (itemIdx != LB_ERR) { BOOL isSelectable = (BOOL)SendMessage(hList, LB_GETITEMDATA, itemIdx, 0); if (!isSelectable) { return 0; // 阻止默认处理,不触发选中 } } break; }
处理键盘导航
处理WM_KEYDOWN,让上下箭头跳过不可选中项:
case WM_KEYDOWN: { if (GetFocus() == hList) { int curSel = SendMessage(hList, LB_GETCURSEL, 0, 0); int itemCount = SendMessage(hList, LB_GETCOUNT, 0, 0); BOOL isSelectable; int newSel; switch (wParam) { case VK_UP: newSel = curSel - 1; while (newSel >= 0) { isSelectable = (BOOL)SendMessage(hList, LB_GETITEMDATA, newSel, 0); if (isSelectable) { SendMessage(hList, LB_SETCURSEL, newSel, 0); break; } newSel--; } return 0; case VK_DOWN: newSel = curSel + 1; while (newSel < itemCount) { isSelectable = (BOOL)SendMessage(hList, LB_GETITEMDATA, newSel, 0); if (isSelectable) { SendMessage(hList, LB_SETCURSEL, newSel, 0); break; } newSel++; } return 0; } } break; }
内容的提问来源于stack exchange,提问作者nabi
相关产品推荐
相关产品推荐

