Troubleshooting Common CListOptionsCtrl IssuesCListOptionsCtrl is a customizable list control often used in MFC (Microsoft Foundation Classes) applications to present items with multiple columns, optional icons, checkboxes, and extended behaviors. Because it offers many features and customization points, developers can encounter a range of issues: visual glitches, incorrect selection behavior, slow performance with large datasets, unexpected sorting, or problems with custom drawing and editing. This article walks through common problems, their likely causes, and step‑by‑step solutions — including code snippets and practical tips to diagnose and fix issues reliably.
1) Visual artifacts, flicker, or incomplete repainting
Symptoms:
- Flickering when scrolling or updating items.
- Parts of the control not repainted after resizing or data changes.
- Ghosting or remnants of previous contents.
Common causes:
- Not enabling double buffering.
- Incorrect handling of WM_ERASEBKGND.
- Custom drawing code that doesn’t fully paint item backgrounds.
- Overlapping child controls or parent window painting issues.
Fixes:
- Enable double buffering for the control. If using custom paint, create a memory DC and draw to a bitmap, then blit to the screen.
- Override OnEraseBkgnd and return TRUE (or do minimal work) to avoid default background erasing that causes flicker.
- Ensure custom draw handling fills the entire item rectangle (including when disabled/selected).
- Use InvalidateRect with the correct rect and call UpdateWindow if immediate repaint is required.
- If the control is owner-drawn, always draw background, item text, icon, and focus rectangle explicitly.
Example: simple double-buffered painting in OnPaint (MFC-style)
void CMyListCtrl::OnPaint() { CPaintDC dc(this); CRect rc; GetClientRect(&rc); CDC memDC; memDC.CreateCompatibleDC(&dc); CBitmap bmp; bmp.CreateCompatibleBitmap(&dc, rc.Width(), rc.Height()); CBitmap* pOld = memDC.SelectObject(&bmp); // Paint background memDC.FillSolidRect(&rc, GetSysColor(COLOR_WINDOW)); // Let default drawing happen into memDC or call custom drawing routines here dc.BitBlt(0, 0, rc.Width(), rc.Height(), &memDC, 0, 0, SRCCOPY); memDC.SelectObject(pOld); }
2) Selection and focus problems
Symptoms:
- Clicking an item doesn’t change selection reliably.
- Keyboard navigation skips items.
- Focus rectangle not shown or lost when embedding in complex windows.
Common causes:
- Incorrect message handling (e.g., swallowing mouse/keyboard messages in parent or child windows).
- Style flags missing (e.g., LVS_SHOWSELALWAYS).
- Ownership or custom hit-testing misreports item under cursor.
- Using WS_EX_CONTROLPARENT or other extended styles incorrectly with nested controls.
Fixes:
- Ensure the control has appropriate styles: LVS_REPORT, LVS_SINGLESEL or LVS_EX_MULTISELECT as needed, and LVS_SHOWSELALWAYS if you want selection to remain visible when unfocused.
- Avoid intercepting mouse/keyboard messages in parent windows unless necessary; call Default() or pass messages to base handlers.
- Use HitTest to confirm which item is under a point; verify custom hit-testing code returns correct subitem/index.
- In custom click handling, call SetItemState to set LVIS_SELECTED|LVIS_FOCUSED and call EnsureVisible for keyboard navigation.
Example: set selection programmatically
void SelectItem(CListOptionsCtrl& ctrl, int index) { ctrl.SetItemState(index, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED); ctrl.EnsureVisible(index, FALSE); }
3) Slow performance with many items
Symptoms:
- UI hangs when populating the control with thousands of rows.
- Scrolling stutters; resizing is slow.
Common causes:
- Inserting items one by one with expensive operations (e.g., loading icons, measuring text) inside the loop.
- Not using virtualization or deferred updates.
- Excessive use of RedrawWindow/Invalidate during bulk updates.
- Heavy custom drawing per item without caching.
Fixes:
- Use SetRedraw(FALSE) before bulk insertions and SetRedraw(TRUE) + RedrawWindow() after.
- If possible, use virtual list control (LVS_OWNERDATA) to supply items on demand.
- Cache resource-heavy objects (icons, bitmaps) and reuse them rather than recreating per item.
- Batch updates: prepare data in memory, then add in larger groups.
- Minimize per-item custom drawing; compute metrics once and reuse.
Example: bulk insert pattern
ctrl.SetRedraw(FALSE); for (int i = 0; i < count; ++i) { int idx = ctrl.InsertItem(i, items[i].text); ctrl.SetItemText(idx, 1, items[i].subtext); // set image, state, etc. } ctrl.SetRedraw(TRUE); ctrl.RedrawWindow();
4) Incorrect or inconsistent sorting
Symptoms:
- Items appear out of expected order after sort operations.
- Sorting behaves differently on numeric vs. textual columns.
- Click-to-sort column header doesn’t toggle ascending/descending properly.
Common causes:
- Using string comparison for numeric columns.
- Not storing sort state (column and order).
- Custom comparison routine ignores subitems or locale settings.
- Not calling SortItemsEx (with proper LPARAM data) from the main thread.
Fixes:
- Implement a comparison callback that interprets column data types (numeric, date, text) appropriately.
- Keep track of the current sorted column and direction; toggle on user clicks.
- Use StrCmpLogicalW for natural sort or locale-aware comparisons (CompareStringEx).
- Ensure the LPARAM passed to SortItems/SortItemsEx references stable item data (not temporary pointers).
Example: comparator stub
int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) { SortParams* params = (SortParams*)lParamSort; const ItemData* a = (ItemData*)lParam1; const ItemData* b = (ItemData*)lParam2; if (params->column == 0) { // numeric return (a->value < b->value) ? -1 : (a->value > b->value) ? 1 : 0; } else { // text return lstrcmpiW(a->sub[params->column], b->sub[params->column]); } }
5) Problems with custom drawing and themes
Symptoms:
- Drawn items don’t match OS theme (mismatch in selection color, focus rectangle).
- Owner-drawn elements look pixelated or misaligned.
- The control ignores visual styles on Windows versions with themes enabled.
Common causes:
- Not handling NM_CUSTOMDRAW stages properly.
- Drawing hard-coded colors rather than using theme/visual style APIs.
- Incorrect DPI scaling assumptions.
- Not using DrawThemeBackground / DrawThemeText when themes are active.
Fixes:
- Handle NM_CUSTOMDRAW stages: CDDS_PREPAINT, CDDS_ITEMPREPAINT, CDDS_ITEMPOSTPAINT, etc., and return appropriate flags (e.g., CDRF_NOTIFYPOSTPAINT).
- Use UxTheme APIs (OpenThemeData, DrawThemeBackground) or Visual Styles wrappers in MFC to match OS theme.
- Respect DPI by using GetDpiForWindow or system metrics and scale fonts/bitmaps.
- When drawing text, use DrawTextEx with DT_VCENTER | DT_SINGLELINE and proper flags to align correctly.
Example: NM_CUSTOMDRAW handler (outline)
LRESULT CMyListCtrl::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult) { LPNMLVCUSTOMDRAW pLVCD = reinterpret_cast<LPNMLVCUSTOMDRAW>(pNMHDR); switch (pLVCD->nmcd.dwDrawStage) { case CDDS_PREPAINT: *pResult = CDRF_NOTIFYITEMDRAW; return CDRF_NOTIFYITEMDRAW; case CDDS_ITEMPREPAINT: // set text/color via pLVCD->clrText/pLVCD->clrTextBk *pResult = CDRF_DODEFAULT; break; } return CDRF_DODEFAULT; }
6) Editing subitems or inline controls not committing values
Symptoms:
- Edit controls or combo boxes used to edit subitems disappear without saving new text.
- Changes only apply after focus moves but not when pressing Enter.
- Validation code not firing or being bypassed.
Common causes:
- Not handling LVN_ENDLABELEDIT or custom edit control notifications properly.
- Child editor controls destroyed prematurely (e.g., by reiniting items).
- Failure to call SetItemText with edit result or not updating underlying data model.
Fixes:
- Implement LVN_ENDLABELEDIT handler and in it, check pResult->pszText and call SetItemText for the subitem.
- If using custom in-place editors, ensure they notify parent via WM_NOTIFY or a callback before being destroyed.
- Avoid resetting the list contents while an editor is active; if necessary, store editor state and reapply.
- Capture Enter/Escape in the editor to commit or cancel edits explicitly.
Example: LVN_ENDLABELEDIT handling
void CMyListCtrl::OnEndLabelEdit(NMHDR* pNMHDR, LRESULT* pResult) { NMLVDISPINFO* pDisp = (NMLVDISPINFO*)pNMHDR; if (pDisp->item.pszText) { SetItemText(pDisp->item.iItem, pDisp->item.iSubItem, pDisp->item.pszText); // update model *pResult = TRUE; } else { *pResult = FALSE; } }
7) Image list and icon problems
Symptoms:
- Icons not showing, wrong icons displayed, or icons misaligned.
- High-DPI scaling issues with icons appearing blurry or too small.
- Image index mismatches when items are moved or deleted.
Common causes:
- Image list not attached correctly (SetImageList with wrong LVSIL_* value).
- Using bitmaps instead of icons without proper masks leading to background artifacts.
- Not responding to WM_SETTINGCHANGE or DPI change notifications to reload scaled icons.
- Using image indices that become invalid after image list changes.
Fixes:
- Call SetImageList(hImageList, LVSIL_SMALL/LVSIL_NORMAL) with the correct list for view.
- Use CImageList::Create with ILC_COLOR32 | ILC_MASK for transparency and color depth.
- On DPI change, recreate image lists with appropriately scaled icons (use LoadImage with LR_DEFAULTSIZE and scale or use SHGetImageList with desired size).
- Store references to images or unique identifiers instead of numeric indices when reordering items; rebuild mapping if image list changes.
8) Hit testing and subitem clicks not detected
Symptoms:
- Clicks on subitems don’t trigger expected behavior (e.g., clicking a checkbox or link-like subitem).
- HitTest only returns the item index but not subitem index, leading to ambiguity.
Common causes:
- Not using LVHT_ONITEMLABEL, LVHT_ONITEMSTATEICON, or LVHT_ONITEMINDENT flags in hit testing.
- Control styles or extended styles preventing subitem activation.
- Using custom drawing that intercepts click areas without passing coordinates to default handlers.
Fixes:
- Use CListCtrl::HitTestEx or LV_HITTESTINFO with flags to detect subitem: send LVM_SUBITEMHITTEST with LVHITTESTINFO on newer commctrl versions or implement column width-based hit testing manually.
- For checkboxes, ensure LVS_EX_CHECKBOXES is set or implement custom state image handling.
- In NM_CLICK or NM_RCLICK handlers, convert screen to client coordinates and call HitTest with LVHITTESTINFO including subitem index logic.
Example: basic hit test for subitem (manual)
int HitTestSubItem(CListCtrl& list, CPoint pt, int &subItem) { LVHITTESTINFO hti = {}; hti.pt = pt; int item = list.SubItemHitTest(&hti); subItem = hti.iSubItem; return item; }
9) Problems when embedding in dialogs or docking windows
Symptoms:
- List control doesn’t resize properly with its parent.
- Keyboard focus or tab order broken in complex dialog layouts.
- Control disappears or shows behind other controls.
Common causes:
- Incorrect anchoring or layout handling (no adjustments on WM_SIZE).
- Z-order issues when child windows created in wrong order.
- Not setting WS_CLIPCHILDREN/WS_CLIPSIBLINGS where appropriate.
Fixes:
- Implement OnSize in parent to reposition/resize the list control (or use layout managers).
- Ensure proper Z-order by creating controls in right sequence or calling SetWindowPos to adjust.
- Use WS_CLIPCHILDREN on parent to avoid drawing over children, and WS_CLIPSIBLINGS on sibling controls to prevent overlap.
- Verify control is actually visible (IsWindowVisible) and not covered by other controls.
10) Debugging tips and diagnostics
- Use Spy++ (or modern equivalents) to watch messages, window styles, and child windows.
- Log LVN* and NM* notifications to verify sequence of events during clicks, edits, or custom draws.
- Temporarily simplify: turn off custom drawing, editors, or image lists to isolate the issue.
- Create a minimal repro project that reproduces the bug reliably — this often reveals incorrect assumptions.
- Use assertions and runtime checks for pointers, indices, and LPARAM references.
- When in doubt, compare behavior on a plain CListCtrl instance to determine if the issue is with CListOptionsCtrl extensions or base control behavior.
Wrap-up
- Start by reproducing the issue in a minimal environment.
- Isolate the subsystem involved: painting, input, data, images, or layout.
- Apply targeted fixes above, test with varied data sizes and DPI/scaling settings, and use logging to confirm correct flows.
If you share a short code sample or describe the exact behavior (platform/Windows version, MFC version, whether using owner data, etc.), I can pinpoint the likely cause and give a concrete patch.
Leave a Reply