| | |
| | | } |
| | | |
| | | // ====== 开关:1=启用假数据(只替换 DB 查询);0=用真实 DB ====== |
| | | #define USE_FAKE_DB_DEMO 1 |
| | | #define USE_FAKE_DB_DEMO 0 |
| | | |
| | | #if USE_FAKE_DB_DEMO |
| | | #include <ctime> |
| | |
| | | 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() |
| | | |
| | | // ===== 私有小工具 ===== |
| | |
| | | { |
| | | m_rebuilding = true; |
| | | |
| | | // 放在任何清空/重建动作之前: |
| | | // 放在任何清空/重建动作之前:记录展开的父节点 key(ClassID) |
| | | auto expandedKeys = SnapshotExpandedKeys(m_listCtrl); |
| | | |
| | | // —— 双保险:先清掉可见项,再清树结构 —— |
| | | // —— 双保险:先清掉可见项,再清树结构 —— |
| | | m_listCtrl.SetRedraw(FALSE); |
| | | m_listCtrl.DeleteAllItems(); |
| | | m_listCtrl.SetRedraw(TRUE); |
| | |
| | | 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) { |
| | |
| | | |
| | | SERVO::CGlass* b = g->getBuddy(); |
| | | if (b) { |
| | | // 按你的约定:g 是父,buddy 是子 |
| | | 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(); |
| | |
| | | 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(); |
| | |
| | | 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 |
| | |
| | | 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; |
| | | } |
| | | |
| | |
| | | |
| | | m_rebuilding = false; |
| | | } |
| | | |
| | | |
| | | void CPageGlassList::UpdatePageControls() |
| | | { |
| | |
| | | } |
| | | // 二次兜底,防止 ini 写进了 0 |
| | | if (m_listCtrl.GetColumnWidth(0) < 16) m_listCtrl.SetColumnWidth(0, 24); |
| | | m_listCtrl.SetPopupFullTextColumns({ 11, 12 }); |
| | | |
| | | Resize(); |
| | | OnBnClickedButtonSearch(); // 触发一次查询与首屏填充 |
| | |
| | | } |
| | | } |
| | | |
| | | 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) 收集当前可见里的“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; |
| | | 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 行,跳过 |
| | |
| | | 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 |
| | |
| | | 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 |
| | |
| | | |
| | | // —— 顺带刷新 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; |
| | |
| | | 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:如果容器下现在有子,也算关系变化,触发重建 |
| | |
| | | } |
| | | } |
| | | 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; |
| | | } |
| | | } |
| | | |
| | | 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) { |
| | | // 用 key(ClassID)保存并恢复,避免 Node* 失效 |
| | | // 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); |
| | | } |