| | |
| | | |
| | | IMPLEMENT_DYNAMIC(CExpandableListCtrl, CListCtrl) |
| | | |
| | | CExpandableListCtrl::CExpandableListCtrl() {} |
| | | CExpandableListCtrl::CExpandableListCtrl() |
| | | { |
| | | m_popupCols = { }; |
| | | } |
| | | |
| | | CExpandableListCtrl::~CExpandableListCtrl() {} |
| | | |
| | | BEGIN_MESSAGE_MAP(CExpandableListCtrl, CListCtrl) |
| | |
| | | m_rowColors.resize(GetItemCount()); |
| | | |
| | | SetRedraw(TRUE); |
| | | Invalidate(); |
| | | Invalidate(FALSE); |
| | | } |
| | | |
| | | // —— 优化后的展开/收起:局部插入/删除,不全量 RebuildVisible —— // |
| | | void CExpandableListCtrl::Expand(Node* n) |
| | | { |
| | | if (!n || n->children.empty()) return; |
| | | if (!n->expanded) { n->expanded = true; RebuildVisible(); } |
| | | if (n->expanded) return; |
| | | |
| | | // 本地工具:找节点在 m_visible 中的行号 |
| | | auto VisibleIndexOf = [&](Node* x)->int { |
| | | for (int i = 0; i < (int)m_visible.size(); ++i) |
| | | if (m_visible[i] == x) return i; |
| | | return -1; |
| | | }; |
| | | // 递归收集“应当可见”的子树(受 expanded 影响) |
| | | std::vector<Node*> toInsert; |
| | | std::function<void(Node*)> CollectExpandedSubtree = [&](Node* x) { |
| | | if (!x) return; |
| | | for (auto& up : x->children) { |
| | | Node* ch = up.get(); |
| | | toInsert.push_back(ch); |
| | | if (ch->expanded && !ch->children.empty()) |
| | | CollectExpandedSubtree(ch); |
| | | } |
| | | }; |
| | | // 从 pos 起插入 nodes,对齐 m_visible / ListCtrl / m_rowColors |
| | | auto InsertRowsAt = [&](int pos, const std::vector<Node*>& nodes) { |
| | | if (nodes.empty()) return; |
| | | const int colCount = GetHeaderCtrl() ? GetHeaderCtrl()->GetItemCount() : 1; |
| | | |
| | | SetRedraw(FALSE); |
| | | |
| | | // 1) 先插 m_visible |
| | | m_visible.insert(m_visible.begin() + pos, nodes.begin(), nodes.end()); |
| | | |
| | | // 2) 再插 ListCtrl |
| | | for (int i = 0; i < (int)nodes.size(); ++i) { |
| | | Node* cur = nodes[i]; |
| | | LVITEM lvi{}; lvi.mask = LVIF_TEXT; |
| | | lvi.iItem = pos + i; |
| | | lvi.iSubItem = 0; |
| | | lvi.pszText = const_cast<LPTSTR>((LPCTSTR)(cur->cols.empty() ? _T("") : cur->cols[0])); |
| | | InsertItem(&lvi); |
| | | |
| | | for (int col = 1; col < colCount; ++col) { |
| | | CString txt = (col < (int)cur->cols.size()) ? cur->cols[col] : _T(""); |
| | | SetItemText(pos + i, col, txt); |
| | | } |
| | | } |
| | | |
| | | // 3) 行号颜色数组同步插入默认色 |
| | | m_rowColors.insert(m_rowColors.begin() + pos, nodes.size(), RowColor{}); |
| | | |
| | | SetRedraw(TRUE); |
| | | Invalidate(FALSE); |
| | | }; |
| | | |
| | | // —— 标记展开 |
| | | n->expanded = true; |
| | | |
| | | // —— 在 UI 里插入其“应当可见”的子树 |
| | | const int pos = VisibleIndexOf(n); |
| | | if (pos < 0) { RebuildVisible(); return; } |
| | | |
| | | CollectExpandedSubtree(n); |
| | | InsertRowsAt(pos + 1, toInsert); |
| | | } |
| | | |
| | | void CExpandableListCtrl::Collapse(Node* n) |
| | | { |
| | | if (!n || n->children.empty()) return; |
| | | if (n->expanded) { n->expanded = false; RebuildVisible(); } |
| | | if (!n->expanded) return; |
| | | |
| | | // 本地工具:找节点行号 |
| | | auto VisibleIndexOf = [&](Node* x)->int { |
| | | for (int i = 0; i < (int)m_visible.size(); ++i) |
| | | if (m_visible[i] == x) return i; |
| | | return -1; |
| | | }; |
| | | // 计算“当前可见的所有后代数量”(基于 level 递减判断) |
| | | auto CountDescendantsInVisible = [&](Node* x)->int { |
| | | if (!x) return 0; |
| | | const int start = VisibleIndexOf(x); |
| | | if (start < 0) return 0; |
| | | const int baseLevel = x->level; |
| | | int cnt = 0; |
| | | for (int i = start + 1; i < (int)m_visible.size(); ++i) { |
| | | if (!m_visible[i]) break; |
| | | if (m_visible[i]->level <= baseLevel) break; |
| | | ++cnt; |
| | | } |
| | | return cnt; |
| | | }; |
| | | // 从 UI 删除 pos 开始的 count 行,并同步 m_visible/m_rowColors |
| | | auto DeleteRowsAt = [&](int pos, int count) { |
| | | if (count <= 0) return; |
| | | |
| | | SetRedraw(FALSE); |
| | | |
| | | // 删 ListCtrl:一直删 pos,因为删一行后后续上移 |
| | | for (int i = 0; i < count; ++i) { |
| | | DeleteItem(pos); |
| | | } |
| | | // 删 m_visible |
| | | m_visible.erase(m_visible.begin() + pos, m_visible.begin() + pos + count); |
| | | // 删颜色 |
| | | if (pos >= 0 && pos <= (int)m_rowColors.size()) { |
| | | int end = min((int)m_rowColors.size(), pos + count); |
| | | m_rowColors.erase(m_rowColors.begin() + pos, m_rowColors.begin() + end); |
| | | } |
| | | |
| | | SetRedraw(TRUE); |
| | | Invalidate(FALSE); |
| | | }; |
| | | |
| | | // —— 标记收起 |
| | | n->expanded = false; |
| | | |
| | | // —— 只删除其“当前可见”的所有后代 |
| | | const int pos = VisibleIndexOf(n); |
| | | if (pos < 0) { RebuildVisible(); return; } |
| | | |
| | | const int cnt = CountDescendantsInVisible(n); |
| | | if (cnt > 0) { |
| | | DeleteRowsAt(pos + 1, cnt); |
| | | } |
| | | } |
| | | |
| | | void CExpandableListCtrl::Toggle(Node* n) |
| | | { |
| | | if (!n || n->children.empty()) return; |
| | | n->expanded = !n->expanded; |
| | | RebuildVisible(); |
| | | if (n->expanded) Collapse(n); |
| | | else Expand(n); |
| | | } |
| | | |
| | | CExpandableListCtrl::Node* CExpandableListCtrl::GetNodeByVisibleIndex(int i) const |
| | |
| | | for (int i = 0; i < (int)m_visible.size(); ++i) { |
| | | if (m_visible[i] == n) { |
| | | RedrawItems(i, i); |
| | | UpdateWindow(); |
| | | return; |
| | | } |
| | | } |
| | |
| | | for (int i = 0; i < (int)m_visible.size(); ++i) { |
| | | if (m_visible[i] == n) { |
| | | RedrawItems(i, i); |
| | | UpdateWindow(); |
| | | return; |
| | | } |
| | | } |
| | |
| | | m_rowColors[row] = rc; |
| | | |
| | | RedrawItems(row, row); |
| | | UpdateWindow(); |
| | | } |
| | | |
| | | CRect CExpandableListCtrl::expanderRectForRow(int row) const |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | // —— 若点击到需要“全文显示”的列,则向父窗口发送自定义通知 —— // |
| | | if (!m_popupCols.empty()) { |
| | | LPNMITEMACTIVATE pia2 = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR); |
| | | |
| | | // 用 SubItemHitTest 更精准拿到列 |
| | | LVHITTESTINFO ht{}; |
| | | ht.pt = pia2->ptAction; |
| | | int hit = SubItemHitTest(&ht); |
| | | if (hit >= 0 && ht.iItem >= 0 && ht.iSubItem >= 0) { |
| | | const int row = ht.iItem; |
| | | const int col = ht.iSubItem; |
| | | |
| | | if (m_popupCols.count(col)) { |
| | | CString full = GetItemText(row, col); |
| | | if (!full.IsEmpty() && _IsCellTruncated(row, col, full)) { |
| | | NMC_ELC_SHOWFULLTEXT nm{}; |
| | | nm.hdr.hwndFrom = m_hWnd; |
| | | nm.hdr.idFrom = GetDlgCtrlID(); |
| | | nm.hdr.code = ELCN_SHOWFULLTEXT; |
| | | nm.iItem = row; |
| | | nm.iSubItem = col; |
| | | nm.text = full; |
| | | |
| | | if (CWnd* pParent = GetParent()) { |
| | | pParent->SendMessage(WM_NOTIFY, nm.hdr.idFrom, reinterpret_cast<LPARAM>(&nm)); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | *pResult = 0; |
| | | } |
| | | |
| | |
| | | DeleteAllItems(); |
| | | SetRedraw(TRUE); |
| | | |
| | | Invalidate(); |
| | | Invalidate(FALSE); |
| | | } |
| | | |
| | | void CExpandableListCtrl::SetPopupFullTextColumns(const std::vector<int>& cols) |
| | | { |
| | | m_popupCols.clear(); |
| | | for (int c : cols) m_popupCols.insert(c); |
| | | } |
| | | |
| | | bool CExpandableListCtrl::_IsCellTruncated(int row, int col, const CString& text) const |
| | | { |
| | | if (text.IsEmpty()) return false; |
| | | |
| | | // 单元格显示区域宽度 |
| | | CRect rcCell; |
| | | if (!const_cast<CExpandableListCtrl*>(this)->GetSubItemRect(row, col, LVIR_BOUNDS, rcCell)) |
| | | return false; |
| | | |
| | | // 用控件字体测量文本像素宽 |
| | | CClientDC dc(const_cast<CExpandableListCtrl*>(this)); |
| | | CFont* pOld = dc.SelectObject(const_cast<CExpandableListCtrl*>(this)->GetFont()); |
| | | CSize sz = dc.GetTextExtent(text); |
| | | dc.SelectObject(pOld); |
| | | |
| | | const int kPadding = 8; // 预留一点边距/省略号余量 |
| | | return sz.cx > (rcCell.Width() - kPadding); |
| | | } |