#include "stdafx.h" #include "CExpandableListCtrl.h" IMPLEMENT_DYNAMIC(CExpandableListCtrl, CListCtrl) CExpandableListCtrl::CExpandableListCtrl() {} CExpandableListCtrl::~CExpandableListCtrl() {} BEGIN_MESSAGE_MAP(CExpandableListCtrl, CListCtrl) ON_WM_CREATE() ON_NOTIFY_REFLECT(NM_CLICK, &CExpandableListCtrl::OnClick) ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, &CExpandableListCtrl::OnCustomDraw) END_MESSAGE_MAP() int CExpandableListCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CListCtrl::OnCreate(lpCreateStruct) == -1) return -1; // 报表风格列举例 SetExtendedStyle(GetExtendedStyle() | LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP | LVS_EX_GRIDLINES); // 示例列(可在外部设置) if (GetHeaderCtrl() == nullptr || GetHeaderCtrl()->GetItemCount() == 0) { InsertColumn(0, _T("名称"), LVCFMT_LEFT, 260); InsertColumn(1, _T("状态"), LVCFMT_LEFT, 120); InsertColumn(2, _T("描述"), LVCFMT_LEFT, 260); } return 0; } CExpandableListCtrl::Node* CExpandableListCtrl::InsertRoot(const std::vector& cols) { auto n = std::make_unique((int)max(1, (int)cols.size())); for (size_t i = 0; i < cols.size(); ++i) n->cols[i] = cols[i]; n->level = 0; Node* raw = n.get(); m_roots.emplace_back(std::move(n)); return raw; } CExpandableListCtrl::Node* CExpandableListCtrl::InsertChild(Node* parent, const std::vector& cols) { ASSERT(parent); auto n = std::make_unique((int)max(1, (int)cols.size())); for (size_t i = 0; i < cols.size(); ++i) n->cols[i] = cols[i]; n->parent = parent; n->level = parent->level + 1; Node* raw = n.get(); parent->children.emplace_back(std::move(n)); return raw; } void CExpandableListCtrl::appendVisible(Node* n) { m_visible.push_back(n); if (n->expanded) { for (auto& ch : n->children) { appendVisible(ch.get()); } } } void CExpandableListCtrl::RebuildVisible() { // 1) 重建可见序列 m_visible.clear(); for (auto& r : m_roots) appendVisible(r.get()); // 2) 重绘/重填数据 SetRedraw(FALSE); DeleteAllItems(); // 插入可见行 for (int i = 0; i < (int)m_visible.size(); ++i) { Node* n = m_visible[i]; LVITEM lvi{}; lvi.mask = LVIF_TEXT; lvi.iItem = i; lvi.iSubItem = 0; lvi.pszText = const_cast((LPCTSTR)(n->cols.empty() ? _T("") : n->cols[0])); InsertItem(&lvi); for (int col = 1; col < GetHeaderCtrl()->GetItemCount(); ++col) { CString txt = (col < (int)n->cols.size()) ? n->cols[col] : _T(""); SetItemText(i, col, txt); } } SetRedraw(TRUE); Invalidate(); } void CExpandableListCtrl::Expand(Node* n) { if (!n || n->children.empty()) return; if (!n->expanded) { n->expanded = true; RebuildVisible(); } } void CExpandableListCtrl::Collapse(Node* n) { if (!n || n->children.empty()) return; if (n->expanded) { n->expanded = false; RebuildVisible(); } } void CExpandableListCtrl::Toggle(Node* n) { if (!n || n->children.empty()) return; n->expanded = !n->expanded; RebuildVisible(); } CExpandableListCtrl::Node* CExpandableListCtrl::GetNodeByVisibleIndex(int i) const { if (i < 0 || i >= (int)m_visible.size()) return nullptr; return m_visible[i]; } CRect CExpandableListCtrl::expanderRectForRow(int row) const { CRect rcLabel; if (!const_cast(this)->GetSubItemRect(row, 0, LVIR_LABEL, rcLabel)) return CRect(0, 0, 0, 0); Node* n = const_cast(this)->GetNodeByVisibleIndex(row); if (!n || n->children.empty()) return CRect(0, 0, 0, 0); // 叶子不占位,文本就不会被多推一格 const int indent = n->level; const int left = rcLabel.left + m_expanderPadding + indent * 16; return CRect( left, rcLabel.CenterPoint().y - m_expanderSize / 2, left + m_expanderSize, rcLabel.CenterPoint().y + m_expanderSize / 2 ); } void CExpandableListCtrl::OnClick(NMHDR* pNMHDR, LRESULT* pResult) { LPNMITEMACTIVATE pia = reinterpret_cast(pNMHDR); if (pia->iItem >= 0) { CPoint pt = pia->ptAction; // 命中展开按钮? CRect expRc = expanderRectForRow(pia->iItem); if (expRc.PtInRect(pt)) { Node* n = GetNodeByVisibleIndex(pia->iItem); if (n && !n->children.empty()) { Toggle(n); } } } *pResult = 0; } void CExpandableListCtrl::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult) { LPNMLVCUSTOMDRAW pCD = reinterpret_cast(pNMHDR); switch (pCD->nmcd.dwDrawStage) { case CDDS_PREPAINT: *pResult = CDRF_NOTIFYITEMDRAW | CDRF_NOTIFYSUBITEMDRAW; return; case CDDS_ITEMPREPAINT: *pResult = CDRF_NOTIFYSUBITEMDRAW; return; case CDDS_ITEMPREPAINT | CDDS_SUBITEM: { const int row = (int)pCD->nmcd.dwItemSpec; const int col = pCD->iSubItem; CDC* pDC = CDC::FromHandle(pCD->nmcd.hdc); if (col == 0) { CRect rc; GetSubItemRect(row, 0, LVIR_LABEL, rc); Node* n = GetNodeByVisibleIndex(row); if (!n) { *pResult = CDRF_DODEFAULT; return; } // 1) 背景/前景颜色:按是否选中 const bool selected = (GetItemState(row, LVIS_SELECTED) & LVIS_SELECTED) != 0; const bool focusOnCtrl = (GetSafeHwnd() == ::GetFocus()); COLORREF bk = selected ? GetSysColor(focusOnCtrl ? COLOR_HIGHLIGHT : COLOR_3DFACE) : ListView_GetBkColor(m_hWnd); COLORREF txt = selected ? GetSysColor(focusOnCtrl ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT) : ListView_GetTextColor(m_hWnd); // 仅在需要时填充背景(避免“黑一片”) CBrush bkBrush(bk); pDC->FillRect(rc, &bkBrush); // 2) 展开/折叠指示(参考旧项目的右对齐坐标法,做像素对齐,纯GDI) if (!n->children.empty()) { CRect box = expanderRectForRow(row); // ---- 可调参数:与旧代码命名一致 ---- // 右侧留白(与文本间隙/网格线保持距离) const int ROFFSET = 2; // 闭合/展开的“宽度”设置:奇数更顺眼(9/11 都行) const int WIDE = max(9, min(min(box.Width(), box.Height()), 13)); // ▶ 的边长 const int WIDE2 = WIDE / 2; // 一半 const int EXPANDED_WIDE = WIDE; // ▼ 的边长 // 轻微内缩,避免贴边(与你旧代码“按钮要刷一下”同效) box.DeflateRect(1, 1); // 统一做偶数对齐,减少半像素锯齿 auto even = [](int v) { return (v & 1) ? (v - 1) : v; }; // 计算“自下向上”的基准偏移,与旧 TreeCtrl 一致 // 这里用 box 作为 pRect POINT pt[3]; if (n->expanded) { // ▼ int nBottomOffset = (box.Height() - EXPANDED_WIDE) / 2; pt[0].x = box.right - ROFFSET - EXPANDED_WIDE; pt[0].y = box.bottom - nBottomOffset; pt[1].x = box.right - ROFFSET; pt[1].y = box.bottom - nBottomOffset; pt[2].x = box.right - ROFFSET; pt[2].y = box.bottom - nBottomOffset - EXPANDED_WIDE; } else { // ▶ int nBottomOffset = (box.Height() - WIDE) / 2; pt[0].x = box.right - ROFFSET - WIDE2; pt[0].y = box.bottom - nBottomOffset - WIDE; pt[1].x = box.right - ROFFSET - WIDE2; pt[1].y = box.bottom - nBottomOffset; pt[2].x = box.right - ROFFSET; pt[2].y = box.bottom - nBottomOffset - WIDE2; } // 仅填充,不描边(描边会加重台阶感);颜色用 txt 与主题一致 HGDIOBJ oldPen = pDC->SelectObject(GetStockObject(NULL_PEN)); HBRUSH hBrush = CreateSolidBrush(txt); HGDIOBJ oldBrush = pDC->SelectObject(hBrush); pDC->Polygon(pt, 3); pDC->SelectObject(oldPen); pDC->SelectObject(oldBrush); DeleteObject(hBrush); } // 3) 文本:基于首列区域右移(区分是否有子节点) const int indentPx = n->level * 14; const int baseLeft = rc.left + m_expanderPadding + indentPx; CRect textRc = rc; if (!n->children.empty()) { // 有子项:预留按钮位 + 文本间隙 textRc.left = baseLeft + m_expanderSize + m_textGap; } else { // 叶子行:不预留按钮位,只给一点点叶子间隙(让层级缩进仍然生效) constexpr int kLeafGap = 2; // 你可调 0~4 textRc.left = baseLeft + kLeafGap; } pDC->SetBkMode(TRANSPARENT); pDC->SetTextColor(txt); CString txt0 = n->cols.empty() ? _T("") : n->cols[0]; pDC->DrawText(txt0, textRc, DT_SINGLELINE | DT_VCENTER | DT_NOPREFIX | DT_END_ELLIPSIS); *pResult = CDRF_SKIPDEFAULT; return; } // 其他列默认绘制 *pResult = CDRF_DODEFAULT; return; } } *pResult = CDRF_DODEFAULT; }