LAPTOP-SNT8I5JK\Boounion
2025-09-11 b9f5d0325af5dacf98e245a3b897d2ea44ea4790
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()
// ===== 私有小工具 =====
@@ -789,6 +790,7 @@
    }
    // 二次兜底,防止 ini 写进了 0
    if (m_listCtrl.GetColumnWidth(0) < 16) m_listCtrl.SetColumnWidth(0, 24);
    m_listCtrl.SetPopupFullTextColumns({ 11, 12 });
    Resize();
    OnBnClickedButtonSearch(); // 触发一次查询与首屏填充
@@ -961,17 +963,27 @@
    }
}
void CPageGlassList::OnShowFullText(NMHDR* pNMHDR, LRESULT* pResult)
{
    auto* p = reinterpret_cast<NMC_ELC_SHOWFULLTEXT*>(pNMHDR);
    // 这里暂时用消息框显示;后续可换成你的详情页
    MessageBox(p->text, _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) 收集当前可见里的“WIP 行”(第1列 id 为空)
    //    a) wipRowById:classId -> (row, node*),收集“根+子”的全部,便于判断“buddy 是否已在可见表中”
    //    b) wipRootById:classId -> node*,仅收集“根节点”,便于只对根节点补子项
    //    a) wipRowById:classId -> (row, node*),收集根+子,便于判断“buddy 是否已在可见表中”
    //    b) wipRootById:classId -> node*,仅收集根节点,便于只对“根”补子项
    std::unordered_map<std::string, std::pair<int, CExpandableListCtrl::Node*>> wipRowById;
    std::unordered_map<std::string, CExpandableListCtrl::Node*> wipRootById;
    for (int row = 0; row < m_listCtrl.GetItemCount(); ++row) {
@@ -995,26 +1007,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 +1025,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 +1089,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 +1125,50 @@
                    bool buddyExistsAnywhere = (wipRowById.find(newBid) != wipRowById.end());
                    bool hasChildAlready = NodeHasChildWithClassId(container, newBuddyCid);
                    // —— 关键:关系是否发生变化?(oldChildCid 与 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 +1181,51 @@
            }
        }
        else {
            // (B) 不存在:新增根行——为保证“WIP 永远在顶部”,触发全量重建
            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;
        }
    }
    // 4) 应用 UI 更新
    if (needRebuildAllForNewRoot) {
        // 用 key(ClassID)保存并恢复,避免 Node* 失效
            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 不在可见表:为了保持“WIP 永远在顶部”,触发一次全量重建
                needRebuildAllForNewRoot = true;
            }
        }
    }
    // 4) 应用 UI 更新 —— 把“删除/减少”的情况并入全量重建分支
    if (needRebuildAllForNewRoot || needRebuildRemoval) {
        auto selKeys = SnapshotSelectedKeys(m_listCtrl);
        auto topKey = SnapshotTopKey(m_listCtrl);
        UpdatePageData();                      // 全量重建(WIP 顶部)
        UpdatePageData();                      // 全量重建(WIP 顶部 & 删除无效项)
        RestoreSelectionByKeys(m_listCtrl, selKeys);
        RestoreTopByKey(m_listCtrl, topKey);
    }