| | |
| | | |
| | | CRect CExpandableListCtrl::expanderRectForRow(int row) const |
| | | { |
| | | CRect rc; |
| | | // 取首列矩形 |
| | | if (!GetSubItemRect(row, 0, LVIR_BOUNDS, rc)) |
| | | CRect rcLabel; |
| | | if (!const_cast<CExpandableListCtrl*>(this)->GetSubItemRect(row, 0, LVIR_LABEL, rcLabel)) |
| | | return CRect(0, 0, 0, 0); |
| | | |
| | | Node* n = const_cast<CExpandableListCtrl*>(this)->GetNodeByVisibleIndex(row); |
| | | int indent = (n ? n->level : 0); |
| | | if (!n || n->children.empty()) |
| | | return CRect(0, 0, 0, 0); // 叶子不占位,文本就不会被多推一格 |
| | | |
| | | // 缩进:每级给 16px |
| | | int left = rc.left + m_expanderPadding + indent * 16; |
| | | CRect box(left, rc.CenterPoint().y - m_expanderSize / 2, |
| | | left + m_expanderSize, rc.CenterPoint().y + m_expanderSize / 2); |
| | | return box; |
| | | 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) |
| | | { |
| | |
| | | |
| | | if (col == 0) |
| | | { |
| | | CRect rc; GetSubItemRect(row, 0, LVIR_BOUNDS, rc); |
| | | CRect rc; GetSubItemRect(row, 0, LVIR_LABEL, rc); |
| | | Node* n = GetNodeByVisibleIndex(row); |
| | | if (!n) { *pResult = CDRF_DODEFAULT; return; } |
| | | |
| | |
| | | CBrush bkBrush(bk); |
| | | pDC->FillRect(rc, &bkBrush); |
| | | |
| | | // 2) 展开/折叠指示 |
| | | // 2) 展开/折叠指示(参考旧项目的右对齐坐标法,做像素对齐,纯GDI) |
| | | if (!n->children.empty()) |
| | | { |
| | | CRect box = expanderRectForRow(row); |
| | | // 画三角(▶/▼),并恢复画笔/画刷 |
| | | HGDIOBJ oldPen = pDC->SelectObject(GetStockObject(BLACK_PEN)); |
| | | HGDIOBJ oldBrush = pDC->SelectObject(GetStockObject(BLACK_BRUSH)); |
| | | POINT tri[3]; |
| | | if (n->expanded) { // ▼ |
| | | tri[0] = { box.left + 2, box.top + 2 }; |
| | | tri[1] = { box.right - 2, box.top + 2 }; |
| | | tri[2] = { box.CenterPoint().x, box.bottom - 2 }; |
| | | |
| | | // ---- 可调参数:与旧代码命名一致 ---- |
| | | // 右侧留白(与文本间隙/网格线保持距离) |
| | | 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 { // ▶ |
| | | tri[0] = { box.left + 2, box.top + 2 }; |
| | | tri[1] = { box.right - 2, box.CenterPoint().y }; |
| | | tri[2] = { box.left + 2, box.bottom - 2 }; |
| | | } |
| | | pDC->Polygon(tri, 3); |
| | | pDC->SelectObject(oldPen); |
| | | pDC->SelectObject(oldBrush); |
| | | 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; |
| | | } |
| | | |
| | | // 3) 文本:右移避免遮挡 |
| | | const int indentPx = n->level * 16; |
| | | // 仅填充,不描边(描边会加重台阶感);颜色用 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; |
| | | textRc.left = rc.left + m_expanderPadding + indentPx + m_expanderSize + m_textGap; |
| | | 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; |
| | | } |