From e8a27bb203fe2aff70390a5eca002d7438da9b0f Mon Sep 17 00:00:00 2001
From: mrDarker <mr.darker@163.com>
Date: 星期三, 22 十月 2025 14:24:34 +0800
Subject: [PATCH] Merge branch 'clh' into liuyang

---
 SourceCode/Bond/Servo/CExpandableListCtrl.cpp |  545 +++++++++++++++++++++++++++++++++++++++++++++--------
 1 files changed, 459 insertions(+), 86 deletions(-)

diff --git a/SourceCode/Bond/Servo/CExpandableListCtrl.cpp b/SourceCode/Bond/Servo/CExpandableListCtrl.cpp
index 276e9a0..39831cc 100644
--- a/SourceCode/Bond/Servo/CExpandableListCtrl.cpp
+++ b/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)
@@ -17,20 +21,20 @@
     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);
-    }
+        | LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP | LVS_EX_GRIDLINES | LVS_EX_DOUBLEBUFFER);
 
     return 0;
 }
 
+void CExpandableListCtrl::PreSubclassWindow()
+{
+    SetExtendedStyle(GetExtendedStyle()
+        | LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP | LVS_EX_GRIDLINES | LVS_EX_DOUBLEBUFFER);
+    CListCtrl::PreSubclassWindow();
+}
+
+// ===== 鏍� API =====
 CExpandableListCtrl::Node* CExpandableListCtrl::InsertRoot(const std::vector<CString>& cols)
 {
     auto n = std::make_unique<Node>((int)max(1, (int)cols.size()));
@@ -65,15 +69,12 @@
 
 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{};
@@ -83,32 +84,151 @@
         lvi.pszText = const_cast<LPTSTR>((LPCTSTR)(n->cols.empty() ? _T("") : n->cols[0]));
         InsertItem(&lvi);
 
-        for (int col = 1; col < GetHeaderCtrl()->GetItemCount(); ++col) {
+        const int colCount = GetHeaderCtrl() ? GetHeaderCtrl()->GetItemCount() : 1;
+        for (int col = 1; col < colCount; ++col) {
             CString txt = (col < (int)n->cols.size()) ? n->cols[col] : _T("");
             SetItemText(i, col, txt);
         }
     }
+
+    // 閲嶅缓鍚庯紝鎸夎鍙烽鑹叉暟缁勫榻�
+    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;
+    };
+    // 璁$畻鈥滃綋鍓嶅彲瑙佺殑鎵�鏈夊悗浠f暟閲忊�濓紙鍩轰簬 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
@@ -117,21 +237,118 @@
     return m_visible[i];
 }
 
+// ===== 棰滆壊 API =====
+void CExpandableListCtrl::SetNodeColor(Node* n, COLORREF text, COLORREF bk)
+{
+    if (!n) return;
+    RowColor rc{};
+    rc.text = text; rc.bk = bk;
+    rc.hasText = (text != CLR_DEFAULT);
+    rc.hasBk = (bk != CLR_DEFAULT);
+    m_colorByNode[n] = rc;
+
+    for (int i = 0; i < (int)m_visible.size(); ++i) {
+        if (m_visible[i] == n) {
+            RedrawItems(i, i);
+            return;
+        }
+    }
+}
+
+void CExpandableListCtrl::ClearNodeColor(Node* n)
+{
+    if (!n) return;
+    auto it = m_colorByNode.find(n);
+    if (it != m_colorByNode.end()) {
+        m_colorByNode.erase(it);
+        for (int i = 0; i < (int)m_visible.size(); ++i) {
+            if (m_visible[i] == n) {
+                RedrawItems(i, i);
+                return;
+            }
+        }
+    }
+}
+
+void CExpandableListCtrl::ClearAllColors()
+{
+    m_colorByNode.clear();
+    m_rowColors.clear();
+    Invalidate(FALSE);
+}
+
+// 鍏煎鏃ф帴鍙o細鎸夆�滃彲瑙佽鍙封�濈潃鑹�
+void CExpandableListCtrl::SetItemColor(DWORD_PTR iItem, COLORREF TextColor, COLORREF TextBkColor)
+{
+    SetItemColorByVisibleIndex((int)iItem, TextColor, TextBkColor);
+}
+void CExpandableListCtrl::SetItemColorByVisibleIndex(int row, COLORREF text, COLORREF bk)
+{
+    if (row < 0) return;
+    if (row >= (int)m_rowColors.size())
+        m_rowColors.resize(row + 1);
+
+    RowColor rc{};
+    rc.text = text; rc.bk = bk;
+    rc.hasText = (text != CLR_DEFAULT);
+    rc.hasBk = (bk != CLR_DEFAULT);
+    m_rowColors[row] = rc;
+
+    RedrawItems(row, row);
+}
+
 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
+    );
+}
+
+// 棰滆壊璁$畻锛氫紭鍏� Node*锛屽叾娆¤鍙凤紱鑻ラ渶瑕佸垯璁╃郴缁熼珮浜鐩�
+void CExpandableListCtrl::computeColorsForRow(int row, COLORREF& outText, COLORREF& outBk) const
+{
+    outText = ListView_GetTextColor(const_cast<CExpandableListCtrl*>(this)->m_hWnd);
+    outBk = ListView_GetBkColor(const_cast<CExpandableListCtrl*>(this)->m_hWnd);
+
+    const bool selected = (const_cast<CExpandableListCtrl*>(this)->GetItemState(row, LVIS_SELECTED) & LVIS_SELECTED) != 0;
+    const bool focusOnCtrl = (const_cast<CExpandableListCtrl*>(this)->GetSafeHwnd() == ::GetFocus());
+
+    if (m_preserveSelHighlight && selected) {
+        outBk = GetSysColor(focusOnCtrl ? COLOR_HIGHLIGHT : COLOR_3DFACE);
+        outText = GetSysColor(focusOnCtrl ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT);
+        return;
+    }
+
+    // Node* 棰滆壊
+    if (Node* n = const_cast<CExpandableListCtrl*>(this)->GetNodeByVisibleIndex(row)) {
+        auto it = m_colorByNode.find(n);
+        if (it != m_colorByNode.end()) {
+            if (it->second.hasText) outText = it->second.text;
+            if (it->second.hasBk)   outBk = it->second.bk;
+            return;
+        }
+    }
+
+    // 琛屽彿棰滆壊
+    if (row >= 0 && row < (int)m_rowColors.size()) {
+        const RowColor& rc = m_rowColors[row];
+        if (rc.hasText) outText = rc.text;
+        if (rc.hasBk)   outBk = rc.bk;
+    }
 }
 
 void CExpandableListCtrl::OnClick(NMHDR* pNMHDR, LRESULT* pResult)
@@ -139,8 +356,6 @@
     LPNMITEMACTIVATE pia = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
     if (pia->iItem >= 0) {
         CPoint pt = pia->ptAction;
-
-        // 鍛戒腑灞曞紑鎸夐挳锛�
         CRect expRc = expanderRectForRow(pia->iItem);
         if (expRc.PtInRect(pt)) {
             Node* n = GetNodeByVisibleIndex(pia->iItem);
@@ -149,6 +364,38 @@
             }
         }
     }
+
+    // 鈥斺�� 鑻ョ偣鍑诲埌闇�瑕佲�滃叏鏂囨樉绀衡�濈殑鍒楋紝鍒欏悜鐖剁獥鍙e彂閫佽嚜瀹氫箟閫氱煡 鈥斺�� //
+    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;
 }
 
@@ -163,8 +410,15 @@
         return;
 
     case CDDS_ITEMPREPAINT:
+    {
+        const int row = (int)pCD->nmcd.dwItemSpec;
+        COLORREF txt, bk;
+        computeColorsForRow(row, txt, bk);
+        pCD->clrText = txt;
+        pCD->clrTextBk = bk;
         *pResult = CDRF_NOTIFYSUBITEMDRAW;
         return;
+    }
 
     case CDDS_ITEMPREPAINT | CDDS_SUBITEM:
     {
@@ -172,70 +426,189 @@
         const int col = pCD->iSubItem;
         CDC* pDC = CDC::FromHandle(pCD->nmcd.hdc);
 
-        if (col == 0)
-        {
-            CRect rc; GetSubItemRect(row, 0, LVIR_BOUNDS, 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) 灞曞紑/鎶樺彔鎸囩ず
-            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 };
-                }
-                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);
-            }
-
-            // 3) 鏂囨湰锛氬彸绉婚伩鍏嶉伄鎸�
-            const int indentPx = n->level * 16;
-            CRect textRc = rc;
-            textRc.left = rc.left + m_expanderPadding + indentPx + m_expanderSize + m_textGap;
-
-            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;
+        // 濡傛灉娌℃湁鏍戣妭鐐癸紙绾钩鍒楄〃锛夛紝棣栧垪涔熻蛋榛樿缁樺埗
+        Node* n = GetNodeByVisibleIndex(row);
+        if (col != 0 || !n) {
+            *pResult = CDRF_DODEFAULT;
             return;
         }
 
-        // 鍏朵粬鍒楅粯璁ょ粯鍒�
-        *pResult = CDRF_DODEFAULT;
+        // 棣栧垪鑷粯锛堟爲妯″紡锛�
+        CRect rc; GetSubItemRect(row, 0, LVIR_LABEL, rc);
+
+        COLORREF bk, txt;
+        computeColorsForRow(row, txt, bk);
+
+        CBrush bkBrush(bk);
+        pDC->FillRect(rc, &bkBrush);
+
+        if (!n->children.empty())
+        {
+            CRect box = expanderRectForRow(row);
+            const int ROFFSET = 2;
+            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);
+
+            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;
+            }
+
+            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);
+        }
+
+        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;
+            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);
+
+        if (GetExtendedStyle() & LVS_EX_GRIDLINES)
+        {
+            CRect rcRow; GetSubItemRect(row, 0, LVIR_BOUNDS, rcRow);
+            const int y = rcRow.bottom - 1;
+            CPen pen(PS_SOLID, 1, GetSysColor(COLOR_3DLIGHT));
+            CPen* oldPen = pDC->SelectObject(&pen);
+            pDC->MoveTo(rcRow.left, y);
+            pDC->LineTo(rcRow.right, y);
+            pDC->SelectObject(oldPen);
+        }
+
+        *pResult = CDRF_SKIPDEFAULT;
         return;
     }
-
     }
 
     *pResult = CDRF_DODEFAULT;
 }
 
+// 鍏煎琛屼负锛氬悓姝� SetItemText 鍒� Node->cols锛涚淮鎶よ鍙烽鑹叉暟缁勭殑鎻掑叆/鍒犻櫎
+LRESULT CExpandableListCtrl::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
+{
+    // 鍚屾 SetItemText 鍒� Node锛圓/W 鍏煎锛�
+    if (message == LVM_SETITEMTEXT
+#ifdef LVM_SETITEMTEXTA
+        || message == LVM_SETITEMTEXTA
+#endif
+#ifdef LVM_SETITEMTEXTW
+        || message == LVM_SETITEMTEXTW
+#endif
+        )
+    {
+        int row = static_cast<int>(wParam);
+        LVITEM* p = reinterpret_cast<LVITEM*>(lParam);
+        if (p) {
+            Node* n = GetNodeByVisibleIndex(row);
+            if (n) {
+                int sub = p->iSubItem;
+                if (sub >= (int)n->cols.size())
+                    n->cols.resize(sub + 1);
+                CString newText = p->pszText ? p->pszText : _T("");
+                n->cols[sub] = newText;
+            }
+        }
+        // 缁х画浜ょ粰鍩虹被澶勭悊
+    }
 
+    LRESULT nRet = CListCtrl::WindowProc(message, wParam, lParam);
+
+    // 缁存姢琛屽彿棰滆壊鏁扮粍锛堝吋瀹规棫 SetItemColor锛�
+    if (message == LVM_INSERTITEM) {
+        if (nRet != -1) {
+            LVITEM* p = (LVITEM*)lParam;
+            int pos = p ? p->iItem : (int)nRet;
+            if (pos < 0) pos = (int)nRet;
+            if (pos > (int)m_rowColors.size()) pos = (int)m_rowColors.size();
+            m_rowColors.insert(m_rowColors.begin() + pos, RowColor{}); // 榛樿鑹�
+        }
+    }
+    else if (message == LVM_DELETEITEM) {
+        if (nRet != 0) {
+            int pos = (int)wParam;
+            if (pos >= 0 && pos < (int)m_rowColors.size())
+                m_rowColors.erase(m_rowColors.begin() + pos);
+        }
+    }
+    else if (message == LVM_DELETEALLITEMS) {
+        if (nRet != 0) {
+            m_rowColors.clear();
+        }
+    }
+
+    return nRet;
+}
+
+// CExpandableListCtrl.cpp 閲�
+void CExpandableListCtrl::ClearTree()
+{
+    // 娓呮暟鎹�
+    m_roots.clear();
+    m_visible.clear();
+
+    // 娓呭彲瑙侀」锛堝姟蹇咃紒鍚﹀垯鏃ч〉鐨勮浼氭畫鐣欙級
+    SetRedraw(FALSE);
+    DeleteAllItems();
+    SetRedraw(TRUE);
+
+    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);
+}

--
Gitblit v1.9.3