chenluhua1980
2026-01-06 4d9d8d22e3666076988c30afb4e7c6fe365c19aa
SourceCode/Bond/Servo/CExpandableListCtrl.cpp
@@ -3,7 +3,11 @@
IMPLEMENT_DYNAMIC(CExpandableListCtrl, CListCtrl)
CExpandableListCtrl::CExpandableListCtrl() {}
CExpandableListCtrl::CExpandableListCtrl()
{
    m_popupCols = { };
}
CExpandableListCtrl::~CExpandableListCtrl() {}
BEGIN_MESSAGE_MAP(CExpandableListCtrl, CListCtrl)
@@ -91,26 +95,140 @@
    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
@@ -132,7 +250,6 @@
    for (int i = 0; i < (int)m_visible.size(); ++i) {
        if (m_visible[i] == n) {
            RedrawItems(i, i);
            UpdateWindow();
            return;
        }
    }
@@ -147,7 +264,6 @@
        for (int i = 0; i < (int)m_visible.size(); ++i) {
            if (m_visible[i] == n) {
                RedrawItems(i, i);
                UpdateWindow();
                return;
            }
        }
@@ -179,7 +295,6 @@
    m_rowColors[row] = rc;
    RedrawItems(row, row);
    UpdateWindow();
}
CRect CExpandableListCtrl::expanderRectForRow(int row) const
@@ -249,6 +364,38 @@
            }
        }
    }
    // —— 若点击到需要“全文显示”的列,则向父窗口发送自定义通知 —— //
    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;
}
@@ -438,7 +585,30 @@
    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);
}