From 43c7dc211f10851480352b12bd01f3443079bb01 Mon Sep 17 00:00:00 2001
From: mrDarker <mr.darker@163.com>
Date: 星期二, 26 八月 2025 09:09:21 +0800
Subject: [PATCH] Merge branch 'clh' into liuyang

---
 SourceCode/Bond/Servo/CExpandableListCtrl.cpp |  313 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 313 insertions(+), 0 deletions(-)

diff --git a/SourceCode/Bond/Servo/CExpandableListCtrl.cpp b/SourceCode/Bond/Servo/CExpandableListCtrl.cpp
new file mode 100644
index 0000000..11470ce
--- /dev/null
+++ b/SourceCode/Bond/Servo/CExpandableListCtrl.cpp
@@ -0,0 +1,313 @@
+锘�#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 | LVS_EX_DOUBLEBUFFER);
+
+    return 0;
+}
+
+void CExpandableListCtrl::PreSubclassWindow()
+{
+    // 鎶ヨ〃椋庢牸鍒椾妇渚�
+    SetExtendedStyle(GetExtendedStyle()
+        | LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP | LVS_EX_GRIDLINES | LVS_EX_DOUBLEBUFFER);
+
+    CListCtrl::PreSubclassWindow();
+}
+
+CExpandableListCtrl::Node* CExpandableListCtrl::InsertRoot(const std::vector<CString>& cols)
+{
+    auto n = std::make_unique<Node>((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<CString>& cols)
+{
+    ASSERT(parent);
+    auto n = std::make_unique<Node>((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<LPTSTR>((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<CExpandableListCtrl*>(this)->GetSubItemRect(row, 0, LVIR_LABEL, rcLabel))
+        return CRect(0, 0, 0, 0);
+
+    Node* n = const_cast<CExpandableListCtrl*>(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<LPNMITEMACTIVATE>(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<LPNMLVCUSTOMDRAW>(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) 灞曞紑/鎶樺彔鎸囩ず锛堝弬鑰冩棫椤圭洰鐨勫彸瀵归綈鍧愭爣娉曪紝鍋氬儚绱犲榻愶紝绾疓DI锛�
+            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);
+
+
+            // 鈥斺�� 鐢诲畬涓夎涓庢枃鏈箣鍚庯紝琛ヤ竴鏉¤琛岀殑搴曢儴妯悜缃戞牸绾� 鈥斺��
+            // 浠呭綋寮�鍚簡 LVS_EX_GRIDLINES 鎵嶇粯鍒�
+            if (GetExtendedStyle() & LVS_EX_GRIDLINES)
+            {
+                // 鐢ㄦ暣琛� bounds锛屼繚璇佹í绾胯疮绌挎墍鏈夊垪鐨勫彲瑙佸搴�
+                CRect rcRow;
+                GetSubItemRect(row, 0, LVIR_BOUNDS, rcRow);
+
+                // 搴曡竟 y 鍧愭爣锛堜笌绯荤粺缃戞牸绾垮榻愶級
+                const int y = rcRow.bottom - 1;
+
+                // 棰滆壊涓庣郴缁熼鏍兼帴杩戯紱鑻ヨ寰楀亸娴咃紝鍙崲 COLOR_3DSHADOW
+                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;
+        return;
+    }
+
+    }
+
+    *pResult = CDRF_DODEFAULT;
+}

--
Gitblit v1.9.3