From 0fb528df2c1f05ef7d52827432bd934ce6f9d8cd Mon Sep 17 00:00:00 2001
From: mrDarker <mr.darker@163.com>
Date: 星期五, 12 九月 2025 09:46:59 +0800
Subject: [PATCH] Merge branch 'clh' into liuyang

---
 SourceCode/Bond/Servo/CPageGlassList.cpp |  349 ++++++++++++++++++++++++++++++++++++----------------------
 1 files changed, 216 insertions(+), 133 deletions(-)

diff --git a/SourceCode/Bond/Servo/CPageGlassList.cpp b/SourceCode/Bond/Servo/CPageGlassList.cpp
index 98daad4..fdbdcd4 100644
--- a/SourceCode/Bond/Servo/CPageGlassList.cpp
+++ b/SourceCode/Bond/Servo/CPageGlassList.cpp
@@ -98,7 +98,7 @@
 }
 
 // ====== 寮�鍏筹細1=鍚敤鍋囨暟鎹紙鍙浛鎹� DB 鏌ヨ锛夛紱0=鐢ㄧ湡瀹� DB ======
-#define USE_FAKE_DB_DEMO 1
+#define USE_FAKE_DB_DEMO 0
 
 #if USE_FAKE_DB_DEMO
 #include <ctime>
@@ -392,6 +392,7 @@
     ON_BN_CLICKED(IDC_BUTTON_EXPORT, &CPageGlassList::OnBnClickedButtonExport)
     ON_BN_CLICKED(IDC_BUTTON_PREV_PAGE, &CPageGlassList::OnBnClickedButtonPrevPage)
     ON_BN_CLICKED(IDC_BUTTON_NEXT_PAGE, &CPageGlassList::OnBnClickedButtonNextPage)
+    ON_NOTIFY(ELCN_SHOWFULLTEXT, IDC_LIST_ALARM, &CPageGlassList::OnShowFullText)
 END_MESSAGE_MAP()
 
 // ===== 绉佹湁灏忓伐鍏� =====
@@ -519,10 +520,10 @@
 {
     m_rebuilding = true;
 
-    // 鏀惧湪浠讳綍娓呯┖/閲嶅缓鍔ㄤ綔涔嬪墠锛�
+    // 鏀惧湪浠讳綍娓呯┖/閲嶅缓鍔ㄤ綔涔嬪墠锛氳褰曞睍寮�鐨勭埗鑺傜偣 key锛圕lassID锛�
     auto expandedKeys = SnapshotExpandedKeys(m_listCtrl);
 
-    // 鈥斺�� 鍙屼繚闄╋細鍏堟竻鎺夊彲瑙侀」锛屽啀娓呮爲缁撴瀯 鈥斺��
+    // 鈥斺�� 鍙屼繚闄╋細鍏堟竻鎺夊彲瑙侀」锛屽啀娓呮爲缁撴瀯 鈥斺�� 
     m_listCtrl.SetRedraw(FALSE);
     m_listCtrl.DeleteAllItems();
     m_listCtrl.SetRedraw(TRUE);
@@ -531,7 +532,7 @@
     m_listCtrl.ClearTree();
 
     const int colCount = m_listCtrl.GetHeaderCtrl() ? m_listCtrl.GetHeaderCtrl()->GetItemCount() : 0;
-    if (colCount <= 0) return;
+    if (colCount <= 0) { m_rebuilding = false; return; }
 
     // ==================== 1) WIP锛氫粎绗� 1 椤垫瀯寤猴紝涓旀斁鍦ㄦ渶椤堕儴 ====================
     if (m_nCurPage == 1) {
@@ -550,9 +551,11 @@
 
             SERVO::CGlass* b = g->getBuddy();
             if (b) {
+                // 鎸変綘鐨勭害瀹氾細g 鏄埗锛宐uddy 鏄瓙
                 SERVO::CGlass* parent = g;
                 SERVO::CGlass* child = b;
 
+                // parent
                 std::vector<CString> pcols(colCount);
                 pcols[1] = _T("");
                 pcols[2] = std::to_string(parent->getCassetteSequenceNo()).c_str();
@@ -571,6 +574,7 @@
                 MaybeRestoreExpandByKey(nParent, expandedKeys);
                 m_listCtrl.SetNodeColor(nParent, kWipText, kWipParentBk);   // 鐖讹細鍩虹缁�
 
+                // child
                 std::vector<CString> ccols(colCount);
                 ccols[1] = _T("");
                 ccols[2] = std::to_string(child->getCassetteSequenceNo()).c_str();
@@ -614,7 +618,7 @@
         for (auto* item : tempGlasses) item->release();
     }
 
-    // ==================== 2) DB 褰撳墠椤碉紙鏃犺绗嚑椤甸兘鏋勫缓锛涙帓鍦� WIP 涔嬪悗锛� ====================
+    // ==================== 2) DB 褰撳墠椤碉紙涓ら樁娈垫瀯寤猴紝澶勭悊鍗曞悜 buddy锛� ====================
 #if USE_FAKE_DB_DEMO
     auto page = _make_page_fake(m_filters, PAGE_SIZE, PAGE_SIZE * (m_nCurPage - 1));
 #else
@@ -622,99 +626,128 @@
     auto page = db.queryPaged(m_filters, PAGE_SIZE, PAGE_SIZE * (m_nCurPage - 1));
 #endif
 
+    // 寤虹储寮曪細classId -> index
     std::unordered_map<std::string, size_t> idxById;
     idxById.reserve(page.items.size());
-    for (size_t i = 0; i < page.items.size(); ++i) idxById[page.items[i].classId] = i;
+    for (size_t i = 0; i < page.items.size(); ++i) {
+        idxById[page.items[i].classId] = i;
+    }
 
-    std::unordered_set<std::string> usedDb;
+    // 宸叉秷璐癸紙宸叉彃鍏ヤ负鐖舵垨瀛愶級
+    std::unordered_set<std::string> consumed;
     int zebra = 0;
+    auto zebraBk = [&](int z) -> COLORREF {
+        return (z % 2 == 0) ? RGB(255, 255, 255) : RGB(235, 235, 235);
+    };
 
+    // -------- Phase 1: 鍏堝鐞嗏�滄湁 buddyId 鐨勮褰曗�濓紙鑳介厤灏遍厤锛涘崟鍚戜篃閰嶏級 ----------
     for (size_t i = 0; i < page.items.size(); ++i) {
         const auto& r = page.items[i];
-        if (usedDb.count(r.classId)) continue;
+        if (consumed.count(r.classId)) continue;
+        if (r.buddyId.empty()) continue;
 
-        COLORREF bk = (zebra % 2 == 0) ? RGB(255, 255, 255) : RGB(235, 235, 235);
-        bool paired = false;
+        COLORREF bk = zebraBk(zebra);
 
-        if (!r.buddyId.empty()) {
-            auto it = idxById.find(r.buddyId);
-            if (it != idxById.end()) {
-                const auto& br = page.items[it->second];
-                if (!usedDb.count(br.classId)) {
-                    bool rIsParent = (r.classId <= br.classId);
-                    const auto& parentRec = rIsParent ? r : br;
-                    const auto& childRec = rIsParent ? br : r;
+        auto it = idxById.find(r.buddyId);
+        if (it != idxById.end()) {
+            const auto& br = page.items[it->second];
+            if (!consumed.count(br.classId)) {
+                // 鈥斺�� 浠モ�滄湁 buddyId 鐨勮繖鏉� r鈥濅负鐖讹紝buddy 浣滀负瀛愶紙鍗曞悜涔熻兘閰嶏級鈥斺��
+                std::vector<CString> pcols(colCount);
+                pcols[1] = std::to_string(r.id).c_str();
+                pcols[2] = std::to_string(r.cassetteSeqNo).c_str();
+                pcols[3] = std::to_string(r.jobSeqNo).c_str();
+                pcols[4] = r.classId.c_str();
+                pcols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)r.materialType).c_str();
+                pcols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)r.state).c_str();
+                pcols[7] = r.tStart.c_str();
+                pcols[8] = r.tEnd.c_str();
+                pcols[9] = r.buddyId.c_str();
+                pcols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)r.aoiResult).c_str();
+                pcols[11] = r.path.c_str();
+                pcols[12] = r.params.c_str();
 
-                    std::vector<CString> pcols(colCount);
-                    pcols[1] = std::to_string(parentRec.id).c_str(); pcols[2] = std::to_string(parentRec.cassetteSeqNo).c_str();
-                    pcols[3] = std::to_string(parentRec.jobSeqNo).c_str(); pcols[4] = parentRec.classId.c_str();
-                    pcols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)parentRec.materialType).c_str();
-                    pcols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)parentRec.state).c_str();
-                    pcols[7] = parentRec.tStart.c_str(); pcols[8] = parentRec.tEnd.c_str(); pcols[9] = parentRec.buddyId.c_str();
-                    pcols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)parentRec.aoiResult).c_str();
-                    pcols[11] = parentRec.path.c_str(); pcols[12] = parentRec.params.c_str();
+                auto* nParent = m_listCtrl.InsertRoot(pcols);
+                MaybeRestoreExpandByKey(nParent, expandedKeys);
+                m_listCtrl.SetNodeColor(nParent, RGB(0, 0, 0), bk);
 
-                    auto* nParent = m_listCtrl.InsertRoot(pcols);
-                    MaybeRestoreExpandByKey(nParent, expandedKeys);
-                    m_listCtrl.SetNodeColor(nParent, RGB(0, 0, 0), bk);
+                std::vector<CString> ccols(colCount);
+                ccols[1] = std::to_string(br.id).c_str();
+                ccols[2] = std::to_string(br.cassetteSeqNo).c_str();
+                ccols[3] = std::to_string(br.jobSeqNo).c_str();
+                ccols[4] = br.classId.c_str();
+                ccols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)br.materialType).c_str();
+                ccols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)br.state).c_str();
+                ccols[7] = br.tStart.c_str();
+                ccols[8] = br.tEnd.c_str();
+                ccols[9] = br.buddyId.c_str();
+                ccols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)br.aoiResult).c_str();
+                ccols[11] = br.path.c_str();
+                ccols[12] = br.params.c_str();
 
-                    std::vector<CString> ccols(colCount);
-                    ccols[1] = std::to_string(childRec.id).c_str(); ccols[2] = std::to_string(childRec.cassetteSeqNo).c_str();
-                    ccols[3] = std::to_string(childRec.jobSeqNo).c_str(); ccols[4] = childRec.classId.c_str();
-                    ccols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)childRec.materialType).c_str();
-                    ccols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)childRec.state).c_str();
-                    ccols[7] = childRec.tStart.c_str(); ccols[8] = childRec.tEnd.c_str(); ccols[9] = childRec.buddyId.c_str();
-                    ccols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)childRec.aoiResult).c_str();
-                    ccols[11] = childRec.path.c_str(); ccols[12] = childRec.params.c_str();
+                auto* nChild = m_listCtrl.InsertChild(nParent, ccols);
+                m_listCtrl.SetNodeColor(nChild, RGB(0, 0, 0), bk);
 
-                    auto* nChild = m_listCtrl.InsertChild(nParent, ccols);
-                    m_listCtrl.SetNodeColor(nChild, RGB(0, 0, 0), bk);
-
-                    usedDb.insert(parentRec.classId);
-                    usedDb.insert(childRec.classId);
-                    paired = true;
-                }
+                consumed.insert(r.classId);
+                consumed.insert(br.classId);
+                ++zebra;
+                continue;
             }
         }
 
-        if (!paired && !r.buddyId.empty()) {
-            std::vector<CString> pcols(colCount);
-            pcols[1] = std::to_string(r.id).c_str(); pcols[2] = std::to_string(r.cassetteSeqNo).c_str();
-            pcols[3] = std::to_string(r.jobSeqNo).c_str(); pcols[4] = r.classId.c_str();
-            pcols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)r.materialType).c_str();
-            pcols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)r.state).c_str();
-            pcols[7] = r.tStart.c_str(); pcols[8] = r.tEnd.c_str(); pcols[9] = r.buddyId.c_str();
-            pcols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)r.aoiResult).c_str();
-            pcols[11] = r.path.c_str(); pcols[12] = r.params.c_str();
+        // 鍚岄〉娌℃壘鍒� buddy锛堟垨宸茶娑堣垂锛夆啋 鎻掑崰浣嶅瓙琛�
+        std::vector<CString> pcols(colCount);
+        pcols[1] = std::to_string(r.id).c_str();
+        pcols[2] = std::to_string(r.cassetteSeqNo).c_str();
+        pcols[3] = std::to_string(r.jobSeqNo).c_str();
+        pcols[4] = r.classId.c_str();
+        pcols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)r.materialType).c_str();
+        pcols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)r.state).c_str();
+        pcols[7] = r.tStart.c_str();
+        pcols[8] = r.tEnd.c_str();
+        pcols[9] = r.buddyId.c_str();
+        pcols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)r.aoiResult).c_str();
+        pcols[11] = r.path.c_str();
+        pcols[12] = r.params.c_str();
 
-            auto* nParent = m_listCtrl.InsertRoot(pcols);
-            MaybeRestoreExpandByKey(nParent, expandedKeys);
-            m_listCtrl.SetNodeColor(nParent, RGB(0, 0, 0), bk);
+        auto* nParent = m_listCtrl.InsertRoot(pcols);
+        MaybeRestoreExpandByKey(nParent, expandedKeys);
+        m_listCtrl.SetNodeColor(nParent, RGB(0, 0, 0), bk);
 
-            std::vector<CString> ccols(colCount);
-            ccols[4] = r.buddyId.c_str(); // 鍗犱綅瀛愯锛氭樉绀� buddy 鐨� classId
-            auto* nChild = m_listCtrl.InsertChild(nParent, ccols);
-            m_listCtrl.SetNodeColor(nChild, RGB(0, 0, 0), bk);
+        std::vector<CString> ccols(colCount); // 鍗犱綅鍙啓 ClassID
+        ccols[4] = r.buddyId.c_str();
+        auto* nChild = m_listCtrl.InsertChild(nParent, ccols);
+        m_listCtrl.SetNodeColor(nChild, RGB(0, 0, 0), bk);
 
-            usedDb.insert(r.classId);
-            paired = true;
-        }
+        consumed.insert(r.classId);
+        ++zebra;
+    }
 
-        if (!paired) {
-            std::vector<CString> cols(colCount);
-            cols[1] = std::to_string(r.id).c_str(); cols[2] = std::to_string(r.cassetteSeqNo).c_str();
-            cols[3] = std::to_string(r.jobSeqNo).c_str(); cols[4] = r.classId.c_str();
-            cols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)r.materialType).c_str();
-            cols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)r.state).c_str();
-            cols[7] = r.tStart.c_str(); cols[8] = r.tEnd.c_str(); cols[9] = r.buddyId.c_str();
-            cols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)r.aoiResult).c_str();
-            cols[11] = r.path.c_str(); cols[12] = r.params.c_str();
+    // -------- Phase 2: 鍓╀綑鏈秷璐圭殑锛屼綔涓衡�滃崟鏉℃牴琛屸�� ----------
+    for (size_t i = 0; i < page.items.size(); ++i) {
+        const auto& r = page.items[i];
+        if (consumed.count(r.classId)) continue;
 
-            auto* n = m_listCtrl.InsertRoot(cols);
-            m_listCtrl.SetNodeColor(n, RGB(0, 0, 0), bk);
-            usedDb.insert(r.classId);
-        }
+        COLORREF bk = zebraBk(zebra);
 
+        std::vector<CString> cols(colCount);
+        cols[1] = std::to_string(r.id).c_str();
+        cols[2] = std::to_string(r.cassetteSeqNo).c_str();
+        cols[3] = std::to_string(r.jobSeqNo).c_str();
+        cols[4] = r.classId.c_str();
+        cols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)r.materialType).c_str();
+        cols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)r.state).c_str();
+        cols[7] = r.tStart.c_str();
+        cols[8] = r.tEnd.c_str();
+        cols[9] = r.buddyId.c_str();
+        cols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)r.aoiResult).c_str();
+        cols[11] = r.path.c_str();
+        cols[12] = r.params.c_str();
+
+        auto* n = m_listCtrl.InsertRoot(cols);
+        m_listCtrl.SetNodeColor(n, RGB(0, 0, 0), bk);
+
+        consumed.insert(r.classId);
         ++zebra;
     }
 
@@ -726,6 +759,7 @@
 
     m_rebuilding = false;
 }
+
 
 void CPageGlassList::UpdatePageControls()
 {
@@ -789,6 +823,7 @@
     }
     // 浜屾鍏滃簳锛岄槻姝� ini 鍐欒繘浜� 0
     if (m_listCtrl.GetColumnWidth(0) < 16) m_listCtrl.SetColumnWidth(0, 24);
+    m_listCtrl.SetPopupFullTextColumns({ 11, 12 });
 
     Resize();
     OnBnClickedButtonSearch(); // 瑙﹀彂涓�娆℃煡璇笌棣栧睆濉厖
@@ -961,19 +996,32 @@
     }
 }
 
+void CPageGlassList::OnShowFullText(NMHDR* pNMHDR, LRESULT* pResult)
+{
+    auto* p = reinterpret_cast<NMC_ELC_SHOWFULLTEXT*>(pNMHDR);
+
+    // 杩欓噷鏆傛椂鐢ㄦ秷鎭鏄剧ず锛涘悗缁彲鎹㈡垚浣犵殑璇︽儏椤�
+    CString strNewMsg = p->text;
+    strNewMsg.Replace(_T(","), _T("\n"));
+    MessageBox(strNewMsg, _T("璇︾粏淇℃伅"), MB_OK | MB_ICONINFORMATION);
+    *pResult = 0;
+}
+
 void CPageGlassList::UpdateWipData()
 {
     // 鍙湪绗� 1 椤靛埛鏂� WIP锛涘叾瀹冮〉涓嶅姩
     if (m_nCurPage != 1) return;
+    // 鑻ュ垰濂藉湪 UpdatePageData() 閲嶅缓鏈熼棿锛岃烦杩囪繖杞閲忥紝閬垮厤浜掔浉骞叉壈
+    if (m_rebuilding) return;
 
     const int colCount = m_listCtrl.GetHeaderCtrl() ? m_listCtrl.GetHeaderCtrl()->GetItemCount() : 0;
     if (colCount <= 0) return;
 
     // 1) 鏀堕泦褰撳墠鍙閲岀殑鈥淲IP 琛屸�濓紙绗�1鍒� id 涓虹┖锛�
-    //    a) wipRowById锛歝lassId -> (row, node*)锛屾敹闆嗏�滄牴+瀛愨�濈殑鍏ㄩ儴锛屼究浜庡垽鏂�渂uddy 鏄惁宸插湪鍙琛ㄤ腑鈥�
-    //    b) wipRootById锛歝lassId -> node*锛屼粎鏀堕泦鈥滄牴鑺傜偣鈥濓紝渚夸簬鍙鏍硅妭鐐硅ˉ瀛愰」
+    //    a) wipRowById锛歝lassId -> (row, node*)锛屾敹闆嗘牴+瀛愶紝渚夸簬鍒ゆ柇鈥渂uddy 鏄惁宸插湪鍙琛ㄤ腑鈥�
+    //    b) wipRootById锛歝lassId -> node*锛屼粎鏀堕泦鏍硅妭鐐癸紝渚夸簬鍙鈥滄牴鈥濊ˉ瀛愰」
     std::unordered_map<std::string, std::pair<int, CExpandableListCtrl::Node*>> wipRowById;
-    std::unordered_map<std::string, CExpandableListCtrl::Node*> wipRootById;
+    std::unordered_map<std::string, CExpandableListCtrl::Node*>                 wipRootById;
     for (int row = 0; row < m_listCtrl.GetItemCount(); ++row) {
         CString idDb = m_listCtrl.GetItemText(row, 1); // 绗�1鍒楁槸 DB id
         if (!idDb.IsEmpty()) continue;                 // 鏈� id 鐨勬槸 DB 琛岋紝璺宠繃
@@ -995,26 +1043,7 @@
     std::vector<SERVO::CGlass*> wipGlasses;
     theApp.m_model.m_master.getWipGlasses(wipGlasses);
     std::vector<SERVO::CGlass*> tempRetain = wipGlasses; // 绋嶅悗缁熶竴 release
-    /*
-    static int i = 0;
-    i++;
-    if (i == 8) {
-        for (auto item : wipGlasses) {
-            if (item->getBuddy() != nullptr) {
-                item->setInspResult(EQ_ID_MEASUREMENT, 0, SERVO::InspResult::Fail);
-                item->getBuddy()->setID("11111");
-            }
-        }
-    }
-    if (i == 16) {
-        for (auto item : wipGlasses) {
-            if (item->getBuddy() != nullptr) {
-                item->setInspResult(EQ_ID_MEASUREMENT, 0, SERVO::InspResult::Pass);
-                item->getBuddy()->setID("22222");
-            }
-        }
-    }
-    */
+
     auto makeColsFromWip = [&](SERVO::CGlass* g) {
         std::vector<CString> cols(colCount);
         cols[1] = _T(""); // WIP 娌� DB id
@@ -1032,21 +1061,47 @@
         return cols;
     };
 
-    bool needRebuildChildren = false;     // 鏈鏄惁鏂板浜嗗瓙鑺傜偣锛堢粨鏋勫彉鍖栵級
-    bool needRebuildAllForNewRoot = false;// 鏈鏄惁鍙戠幇浜嗏�滄柊澧炴牴鑺傜偣鈥濈殑闇�姹傦紙涓轰繚璇� WIP 鍦ㄩ《閮級
-    std::vector<int> rowsToRedraw;        // 浠呮枃鏈彉鍖栫殑琛�
+    // 2.1 鏋勫缓鈥滄渶鏂� WIP 閿泦鈥濓紙鍚� buddy锛屼笖鍛戒腑杩囨护锛夛紝鐢ㄤ簬妫�娴嬧�滅己澶�/鍒犻櫎鈥�
+    std::unordered_set<std::string> newWipKeys;
+    for (auto* g : wipGlasses) {
+        if (!GlassMatchesFilters(*g, m_filters)) continue;
+#ifdef _UNICODE
+        newWipKeys.insert(CT2A(CString(g->getID().c_str())));
+#else
+        newWipKeys.insert(g->getID());
+#endif
+        if (auto* b = g->getBuddy()) {
+            if (GlassMatchesFilters(*b, m_filters)) {
+#ifdef _UNICODE
+                newWipKeys.insert(CT2A(CString(b->getID().c_str())));
+#else
+                newWipKeys.insert(b->getID());
+#endif
+            }
+        }
+    }
+
+    bool needRebuildRemoval = false; // WIP 鍙樺皯/娓呯┖锛氶渶瑕佹暣椤甸噸寤�
+    bool needRebuildChildren = false; // 缁撴瀯鍙樺寲锛氭柊澧炲瓙
+    bool needRebuildAllForNewRoot = false; // 鏂板鏍癸紙淇濊瘉 WIP 浠嶅湪椤堕儴锛�
+    std::vector<int> rowsToRedraw;         // 浠呮枃鏈彉鍖�
+
+    // 鍙闆嗕腑鏈変絾鏂版暟鎹噷娌℃湁 -> 瑙﹀彂鈥滃垹闄�/鍑忓皯鈥濈殑鏁撮〉閲嶅缓
+    for (const auto& kv : wipRowById) {
+        if (newWipKeys.find(kv.first) == newWipKeys.end()) { needRebuildRemoval = true; break; }
+    }
 
     // UI 鐘舵�侊紙褰撻渶瑕侀噸寤烘椂浣跨敤锛�
     std::vector<CExpandableListCtrl::Node*> savedSel;
     CExpandableListCtrl::Node* savedTop = nullptr;
 
     // 3) 閫愪釜澶勭悊 WIP锛氬凡瀛樺湪 -> 灏卞湴鏇存柊锛涘繀瑕佹椂鈥滃彧瀵规牴琛ュ瓙椤光��
-    //                 涓嶅瓨鍦� -> 瑙﹀彂鈥滃叏閲忛噸寤衡�濓紝浠ヤ繚璇佹柊 WIP 鏍硅鍑虹幇鍦ㄥ垪琛ㄩ《閮�
+    //                 涓嶅瓨鍦� -> 浼樺厛鎸傚埌 buddy 瀹瑰櫒锛涘惁鍒欒Е鍙戞暣椤甸噸寤猴紙鏂版牴淇濇寔椤堕儴锛�
     for (auto* g : wipGlasses) {
         if (!GlassMatchesFilters(*g, m_filters)) continue;
 
 #ifdef _UNICODE
-        std::string cid = CT2A(g->getID().c_str());
+        std::string cid = CT2A(CString(g->getID().c_str()));
 #else
         std::string cid = g->getID();
 #endif
@@ -1070,27 +1125,23 @@
 
             // 鈥斺�� 椤哄甫鍒锋柊 buddy 瀛愯锛堝鏋滃畠宸插湪鍙琛ㄩ噷锛夆�斺��
             if (SERVO::CGlass* b = g->getBuddy()) {
-                CString buddyCidCs = b->getID().c_str();
 #ifdef _UNICODE
-                std::string bid = CT2A(buddyCidCs);
+                std::string bid = CT2A(CString(b->getID().c_str()));
 #else
-                std::string bid = buddyCidCs.GetString();
+                std::string bid = b->getID();
 #endif
                 auto itChildAny = wipRowById.find(bid);
                 if (itChildAny != wipRowById.end()) {
                     int crow = itChildAny->second.first;
-                    // 鐢熸垚 buddy 鐨勫垪鏂囨湰骞朵竴娆℃�у啓鍥�
                     auto bcols = makeColsFromWip(b);
                     ApplyColsToRow(m_listCtrl, crow, bcols);
                     rowsToRedraw.push_back(crow);
                 }
-                // 濡傛灉 buddy 琛屽綋鍓嶄笉瀛樺湪锛屽彲淇濈暀浣犲師鏉ョ殑鈥滃彧鍦ㄦ牴涓嬨�佷笖 buddy 涓嶅湪鍙琛ㄦ椂琛ュ瓙椤光�濈殑閫昏緫
             }
 
             // 鈥斺�� 鍙鈥滄牴鑺傜偣鈥濊ˉ瀛愰」锛屼笖浠呭綋 buddy 灏氭湭鍑虹幇鍦ㄥ彲瑙佽〃锛屼笖鏍逛笅涔熸病鏈夎 buddy 鈥斺�� 
             SERVO::CGlass* b = g->getBuddy();
             if (b) {
-                // 褰撳墠鏍瑰鍣紵锛堝瓙鑺傜偣涓嶄綔涓哄鍣級
                 auto itRoot = wipRootById.find(cid);
                 if (itRoot != wipRootById.end()) {
                     CExpandableListCtrl::Node* container = itRoot->second;
@@ -1110,52 +1161,50 @@
                     bool buddyExistsAnywhere = (wipRowById.find(newBid) != wipRowById.end());
                     bool hasChildAlready = NodeHasChildWithClassId(container, newBuddyCid);
 
-                    // 鈥斺�� 鍏抽敭锛氬叧绯绘槸鍚﹀彂鐢熷彉鍖栵紵锛坥ldChildCid 涓� newBuddyCid 涓嶅悓锛�
+                    // 鍏崇郴鏄惁鍙戠敓鍙樺寲锛燂紙oldChildCid 涓� newBuddyCid 涓嶅悓锛屾垨鏈夊瓙浣嗙幇鍦ㄦ病 buddy锛�
                     bool relationChanged =
-                        (!oldChildCid.IsEmpty() && newBuddyCid.IsEmpty()) ||                             // 涔嬪墠鏈夊瓙锛岀幇鍦ㄦ病 buddy
-                        (oldChildCid.IsEmpty() && !newBuddyCid.IsEmpty()) ||                             // 涔嬪墠娌″瓙锛岀幇鍦ㄦ湁 buddy
-                        (!oldChildCid.IsEmpty() && !newBuddyCid.IsEmpty() && oldChildCid.CompareNoCase(newBuddyCid) != 0); // 鏀� buddy
+                        (!oldChildCid.IsEmpty() && newBuddyCid.IsEmpty()) ||
+                        (oldChildCid.IsEmpty() && !newBuddyCid.IsEmpty()) ||
+                        (!oldChildCid.IsEmpty() && !newBuddyCid.IsEmpty() &&
+                            oldChildCid.CompareNoCase(newBuddyCid) != 0);
 
                     if (relationChanged) {
-                        // 鍏崇郴鍙樻洿璧扳�滅粨鏋勯噸寤衡�濓紝閬垮厤閲嶅鎴栧弽鍚戞寕杞�
-                        needRebuildAllForNewRoot = true;
+                        needRebuildAllForNewRoot = true; // 閬垮厤閲嶅鎴栧弽鍚戞寕杞�
                     }
                     else {
-                        // 鍏崇郴鏈彉锛氳嫢 buddy 杩樹笉鍦ㄥ彲瑙佽〃涓斿鍣ㄤ笅涔熸病鏈夛紝鍒欒ˉ瀛�
+                        // 鍏崇郴鏈彉锛氳嫢 buddy 涓嶅湪鍙琛ㄤ笖瀹瑰櫒涓嬩篃娌℃湁锛屽垯琛ュ瓙
                         if (!buddyExistsAnywhere && !hasChildAlready) {
                             if (!needRebuildChildren) { CaptureUiState(m_listCtrl, savedSel, savedTop); }
                             needRebuildChildren = true;
                             auto cols = makeColsFromWip(b);
                             auto* ch = m_listCtrl.InsertChild(container, cols);
-                            m_listCtrl.SetNodeColor(ch, kWipText, kWipChildBk);         // 瀛愶細鏇存祬
-                            m_listCtrl.SetNodeColor(container, kWipText, kWipParentBk); // 鐖讹細鍩虹缁匡紙鍏滃簳绾犳锛�
+                            m_listCtrl.SetNodeColor(ch, kWipText, kWipChildBk);  // 瀛愶細娴呰壊
+                            m_listCtrl.SetNodeColor(container, kWipText, kWipParentBk); // 鐖讹細鍩虹缁�
                         }
-                        // 鑻ュ凡鏈夊瓙锛氶『甯︽妸瀛愯鏂囨湰鍒锋柊涓�涓嬶紙姣斿 AOI 鏇存柊锛�
+                        // 鑻ュ凡鏈夊瓙锛氬悓姝ュ埛鏂板瓙琛屾枃鏈笌棰滆壊
                         else if (hasChildAlready) {
-                            // 鎵惧埌瀵瑰簲瀛愬苟鏇存柊鏂囨湰/cols锛岄伩鍏嶅悗缁� Rebuild 鍊掑洖鏃у��
                             for (auto& ch : container->children) {
                                 if (ch && ch->cols.size() > 4 && ch->cols[4].CompareNoCase(newBuddyCid) == 0) {
                                     auto cols = makeColsFromWip(b);
-                                    ch->cols = cols; // 搴曞眰鏁版嵁
+                                    ch->cols = cols; // 鏇存柊搴曞眰鏁版嵁
                                     // 鍙琛屽埛鏂�
                                     for (int r = 0; r < m_listCtrl.GetItemCount(); ++r) {
                                         if (m_listCtrl.GetNodeByVisibleIndex(r) == ch.get()) {
-                                            for (int c = 1; c < (int)cols.size(); ++c) {
+                                            for (int c = 1; c < (int)cols.size(); ++c)
                                                 m_listCtrl.SetItemText(r, c, cols[c]);
-                                                m_listCtrl.SetNodeColor(ch.get(), kWipText, kWipChildBk);   // 淇濊瘉瀛愯鏄祬鑹�
-                                                m_listCtrl.SetNodeColor(container, kWipText, kWipParentBk); // 鐖朵繚鎸佸熀纭�缁�
-                                            }
                                             rowsToRedraw.push_back(r);
                                             break;
                                         }
                                     }
+                                    m_listCtrl.SetNodeColor(ch.get(), kWipText, kWipChildBk);
+                                    m_listCtrl.SetNodeColor(container, kWipText, kWipParentBk);
                                     break;
                                 }
                             }
                         }
                     }
                 }
-                // 褰撳墠鏄�滃瓙鑺傜偣鈥濈殑鎯呭喌锛氫竴寰嬩笉鎸傚瓙锛屼氦缁欓噸寤猴紙鑻ョ埗鍙樻洿锛�
+                // 鑻ュ綋鍓嶆槸鈥滃瓙鑺傜偣鈥濓紝涓嶅湪杩欓噷璋冩暣鐖跺瓙鍏崇郴锛涜鈥滃叧绯诲彉鍖栤�濊蛋鍏ㄩ噺閲嶅缓
             }
             else {
                 // 娌℃湁 buddy锛氬鏋滃鍣ㄤ笅鐜板湪鏈夊瓙锛屼篃绠楀叧绯诲彉鍖栵紝瑙﹀彂閲嶅缓
@@ -1168,17 +1217,51 @@
             }
         }
         else {
-            // (B) 涓嶅瓨鍦細鏂板鏍硅鈥斺�斾负淇濊瘉鈥淲IP 姘歌繙鍦ㄩ《閮ㄢ�濓紝瑙﹀彂鍏ㄩ噺閲嶅缓
-            needRebuildAllForNewRoot = true;
+            // (B) 涓嶅瓨鍦細鏂板
+            //   鍏堝皾璇曗�滄寕鍒� buddy 鐨勫鍣ㄦ牴鈥濅笅闈紱
+            //   鑻� buddy 涓嶅湪褰撳墠鍙琛紝鍒欒Е鍙戝叏閲忛噸寤猴紙淇濊瘉 WIP 椤堕儴锛夈��
+            SERVO::CGlass* b = g->getBuddy();
+            CExpandableListCtrl::Node* container = nullptr;
+
+            if (b) {
+#ifdef _UNICODE
+                std::string bid = CT2A(CString(b->getID().c_str()));
+#else
+                std::string bid = b->getID();
+#endif
+                auto itB = wipRowById.find(bid);
+                if (itB != wipRowById.end()) {
+                    CExpandableListCtrl::Node* buddyNode = itB->second.second;
+                    container = buddyNode ? (buddyNode->parent ? buddyNode->parent : buddyNode) : nullptr;
+                }
+            }
+
+            if (container) {
+                // buddy 瀹瑰櫒瀛樺湪锛氭妸 g 浣滀负鈥滃瓙琛屸�濇寕涓婂幓锛堥伩鍏嶉噸澶嶏級
+                CString cidCs = g->getID().c_str();
+                if (!NodeHasChildWithClassId(container, cidCs)) {
+                    if (!needRebuildChildren) { CaptureUiState(m_listCtrl, savedSel, savedTop); }
+                    needRebuildChildren = true;
+
+                    auto cols = makeColsFromWip(g);
+                    auto* ch = m_listCtrl.InsertChild(container, cols);
+                    // 瀛愶細鏇存祬锛涚埗锛氬熀纭�缁匡紙鍏滃簳锛�
+                    m_listCtrl.SetNodeColor(ch, kWipText, kWipChildBk);
+                    m_listCtrl.SetNodeColor(container, kWipText, kWipParentBk);
+                }
+            }
+            else {
+                // buddy 涓嶅湪鍙琛細涓轰簡淇濇寔鈥淲IP 姘歌繙鍦ㄩ《閮ㄢ�濓紝瑙﹀彂涓�娆″叏閲忛噸寤�
+                needRebuildAllForNewRoot = true;
+            }
         }
     }
 
-    // 4) 搴旂敤 UI 鏇存柊
-    if (needRebuildAllForNewRoot) {
-        // 鐢� key锛圕lassID锛変繚瀛樺苟鎭㈠锛岄伩鍏� Node* 澶辨晥
+    // 4) 搴旂敤 UI 鏇存柊 鈥斺�� 鎶娾�滃垹闄�/鍑忓皯鈥濈殑鎯呭喌骞跺叆鍏ㄩ噺閲嶅缓鍒嗘敮
+    if (needRebuildAllForNewRoot || needRebuildRemoval) {
         auto selKeys = SnapshotSelectedKeys(m_listCtrl);
         auto topKey = SnapshotTopKey(m_listCtrl);
-        UpdatePageData();                      // 鍏ㄩ噺閲嶅缓锛圵IP 椤堕儴锛�
+        UpdatePageData();                      // 鍏ㄩ噺閲嶅缓锛圵IP 椤堕儴 & 鍒犻櫎鏃犳晥椤癸級
         RestoreSelectionByKeys(m_listCtrl, selKeys);
         RestoreTopByKey(m_listCtrl, topKey);
     }

--
Gitblit v1.9.3