| | |
| | | #include "GlassJson.h" |
| | | #include "CServoUtilsTool.h" |
| | | #include "ToolUnits.h" |
| | | |
| | | #include <optional> |
| | | #include <unordered_set> |
| | | #include <unordered_map> |
| | | #include <vector> |
| | | #include <string> |
| | | #include "CProcessDataListDlg.h" |
| | | |
| | | #define PAGE_SIZE 50 |
| | | #define PAGE_BACKGROUND_COLOR RGB(252, 252, 255) |
| | |
| | | } |
| | | } |
| | | |
| | | bool CopyUtf8ToClipboard(const std::string& utf8) |
| | | { |
| | | // 1) UTF-8 -> UTF-16 长度(含结尾 '\0') |
| | | int wlen = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, nullptr, 0); |
| | | if (wlen <= 0) return false; |
| | | |
| | | // 2) 为剪贴板分配全局可移动内存(必须 GMEM_MOVEABLE) |
| | | SIZE_T bytes = static_cast<SIZE_T>(wlen) * sizeof(wchar_t); |
| | | HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, bytes); |
| | | if (!hMem) return false; |
| | | |
| | | // 3) 填充 UTF-16 文本 |
| | | wchar_t* wbuf = static_cast<wchar_t*>(GlobalLock(hMem)); |
| | | if (!wbuf) { GlobalFree(hMem); return false; } |
| | | MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, wbuf, wlen); |
| | | GlobalUnlock(hMem); |
| | | |
| | | // 4) 打开剪贴板并设置数据(CF_UNICODETEXT) |
| | | if (!OpenClipboard(nullptr)) { GlobalFree(hMem); return false; } |
| | | if (!EmptyClipboard()) { CloseClipboard(); GlobalFree(hMem); return false; } |
| | | |
| | | // 成功后,内存所有权交给剪贴板,不能再 GlobalFree |
| | | if (!SetClipboardData(CF_UNICODETEXT, hMem)) { |
| | | CloseClipboard(); |
| | | GlobalFree(hMem); |
| | | return false; |
| | | } |
| | | |
| | | CloseClipboard(); |
| | | return true; |
| | | } |
| | | |
| | | |
| | | // CPageGlassList 对话框 |
| | | |
| | | IMPLEMENT_DYNAMIC(CPageGlassList, CDialogEx) |
| | |
| | | } |
| | | |
| | | // ===== CPageGlassList 消息处理程序 ===== |
| | | void CPageGlassList::InitRxWindow() |
| | | void CPageGlassList::InitRxWindows() |
| | | { |
| | | // 订阅数据 |
| | | IRxWindows* pRxWindows = RX_GetRxWindows(); |
| | |
| | | { |
| | | 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) ==================== |
| | | const int rawLimit = PAGE_SIZE + 1; |
| | | const int rawOffset = PAGE_SIZE * (m_nCurPage - 1); |
| | | #if USE_FAKE_DB_DEMO |
| | | auto page = _make_page_fake(m_filters, PAGE_SIZE, PAGE_SIZE * (m_nCurPage - 1)); |
| | | auto page = _make_page_fake(m_filters, rawLimit, rawOffset); |
| | | #else |
| | | auto& db = GlassLogDb::Instance(); |
| | | auto page = db.queryPaged(m_filters, PAGE_SIZE, PAGE_SIZE * (m_nCurPage - 1)); |
| | | auto pageFull = db.queryPaged(m_filters, rawLimit, rawOffset); |
| | | #endif |
| | | |
| | | 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; |
| | | #if !USE_FAKE_DB_DEMO |
| | | // —— 三元键工具:<classId>|C<cassette>|J<job> —— // |
| | | // —— 三元键工具:<classId>|C<cassette>|J<job> —— // |
| | | auto makeKey = [](const std::string& cls, int csn, int jsn) -> std::string { |
| | | std::string k; |
| | | k.reserve(cls.size() + 32); |
| | | k.append(cls); |
| | | k.push_back('|'); k.push_back('C'); |
| | | k.append(std::to_string(csn)); |
| | | k.push_back('|'); k.push_back('J'); |
| | | k.append(std::to_string(jsn)); |
| | | return k; |
| | | }; |
| | | |
| | | std::unordered_set<std::string> usedDb; |
| | | // ★★★ 这里是关键修复:接收“const Row&”,不要非 const 引用 |
| | | using RowT = std::decay<decltype(pageFull.items.front())>::type; |
| | | auto makeKeyR = [&](const RowT& r) -> std::string { |
| | | return makeKey(r.classId, r.cassetteSeqNo, r.jobSeqNo); |
| | | }; |
| | | |
| | | // 不区分大小写 classId 相等 |
| | | auto iEquals = [](const std::string& a, const std::string& b) { |
| | | #ifdef _WIN32 |
| | | return _stricmp(a.c_str(), b.c_str()) == 0; |
| | | #else |
| | | return strcasecmp(a.c_str(), b.c_str()) == 0; |
| | | #endif |
| | | }; |
| | | |
| | | |
| | | // —— lookahead 预读:若超出 1 条,尝试把“最后一条”与“预读”判为一对(严格优先)—— |
| | | std::optional<decltype(pageFull.items)::value_type> lookahead; |
| | | if (pageFull.items.size() == rawLimit) { |
| | | const auto& last = pageFull.items[PAGE_SIZE - 1]; |
| | | const auto& extra = pageFull.items[PAGE_SIZE]; |
| | | |
| | | bool strictPair = |
| | | (!last.buddyId.empty() && iEquals(last.buddyId, extra.classId) |
| | | && last.cassetteSeqNo == extra.cassetteSeqNo |
| | | && last.jobSeqNo == extra.jobSeqNo) |
| | | || (!extra.buddyId.empty() && iEquals(extra.buddyId, last.classId) |
| | | && extra.cassetteSeqNo == last.cassetteSeqNo |
| | | && extra.jobSeqNo == last.jobSeqNo); |
| | | |
| | | bool loosePair = |
| | | (!last.buddyId.empty() && iEquals(last.buddyId, extra.classId)) || |
| | | (!extra.buddyId.empty() && iEquals(extra.buddyId, last.classId)); |
| | | |
| | | if (strictPair || loosePair) { |
| | | lookahead = extra; |
| | | } |
| | | // 预读不算入本页 |
| | | pageFull.items.pop_back(); |
| | | } |
| | | |
| | | // 之后正常按 page 构建 |
| | | auto& pageRef = pageFull; |
| | | |
| | | // —— 建两个索引 —— // |
| | | // A) byTriple: 三元键 -> index(唯一/已消费依据) |
| | | // B) byClass : classId -> indices(buddy 候选池,允许多个) |
| | | std::unordered_map<std::string, size_t> byTriple; |
| | | std::unordered_map<std::string, std::vector<size_t>> byClass; |
| | | byTriple.reserve(pageRef.items.size()); |
| | | byClass.reserve(pageRef.items.size()); |
| | | |
| | | for (size_t i = 0; i < pageRef.items.size(); ++i) { |
| | | const auto& r = pageRef.items[i]; |
| | | byTriple[makeKeyR(r)] = i; |
| | | byClass[r.classId].push_back(i); |
| | | } |
| | | |
| | | // —— 已消费集合(用三元键)—— |
| | | std::unordered_set<std::string> consumed; |
| | | consumed.reserve(pageRef.items.size()); |
| | | |
| | | int zebra = 0; |
| | | auto zebraBk = [&](int z) -> COLORREF { |
| | | return (z % 2 == 0) ? RGB(255, 255, 255) : RGB(235, 235, 235); |
| | | }; |
| | | |
| | | for (size_t i = 0; i < page.items.size(); ++i) { |
| | | const auto& r = page.items[i]; |
| | | if (usedDb.count(r.classId)) continue; |
| | | // -------- Phase 1: 先处理“有 buddyId 的记录” ---------- |
| | | for (size_t i = 0; i < pageRef.items.size(); ++i) { |
| | | const auto& r = pageRef.items[i]; |
| | | if (consumed.count(makeKeyR(r))) continue; |
| | | if (r.buddyId.empty()) continue; |
| | | |
| | | COLORREF bk = (zebra % 2 == 0) ? RGB(255, 255, 255) : RGB(235, 235, 235); |
| | | bool paired = false; |
| | | // 在同页里为 r 找 buddy 候选 |
| | | size_t buddyIdx = (size_t)-1; |
| | | auto itVec = byClass.find(r.buddyId); |
| | | if (itVec != byClass.end()) { |
| | | const auto& vec = itVec->second; |
| | | |
| | | 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; |
| | | |
| | | 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); |
| | | |
| | | 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); |
| | | |
| | | usedDb.insert(parentRec.classId); |
| | | usedDb.insert(childRec.classId); |
| | | paired = true; |
| | | // 1) 严格匹配:Cassette/Job 一致 |
| | | for (size_t j : vec) { |
| | | const auto& br = pageRef.items[j]; |
| | | if (br.cassetteSeqNo == r.cassetteSeqNo && br.jobSeqNo == r.jobSeqNo) { |
| | | if (!consumed.count(makeKeyR(br))) { buddyIdx = j; break; } |
| | | } |
| | | } |
| | | // 2) 宽松匹配:同 classId 未消费的任意一条 |
| | | if (buddyIdx == (size_t)-1) { |
| | | for (size_t j : vec) { |
| | | const auto& br = pageRef.items[j]; |
| | | if (!consumed.count(makeKeyR(br))) { buddyIdx = j; break; } |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (!paired && !r.buddyId.empty()) { |
| | | COLORREF bk = zebraBk(zebra); |
| | | |
| | | if (buddyIdx != (size_t)-1) { |
| | | const auto& br = pageRef.items[buddyIdx]; |
| | | |
| | | // 父:r(有 buddyId),子:br |
| | | 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[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[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(); |
| | | 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); |
| | | |
| | | std::vector<CString> ccols(colCount); |
| | | ccols[4] = r.buddyId.c_str(); // 占位子行:显示 buddy 的 classId |
| | | 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(); |
| | | |
| | | auto* nChild = m_listCtrl.InsertChild(nParent, ccols); |
| | | m_listCtrl.SetNodeColor(nChild, RGB(0, 0, 0), bk); |
| | | |
| | | usedDb.insert(r.classId); |
| | | paired = true; |
| | | consumed.insert(makeKeyR(r)); |
| | | consumed.insert(makeKeyR(br)); |
| | | ++zebra; |
| | | continue; |
| | | } |
| | | |
| | | 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(); |
| | | // 没找到 buddy → 插占位子行(只写 ClassID) |
| | | 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* n = m_listCtrl.InsertRoot(cols); |
| | | m_listCtrl.SetNodeColor(n, RGB(0, 0, 0), bk); |
| | | usedDb.insert(r.classId); |
| | | } |
| | | auto* nParent = m_listCtrl.InsertRoot(pcols); |
| | | MaybeRestoreExpandByKey(nParent, expandedKeys); |
| | | m_listCtrl.SetNodeColor(nParent, RGB(0, 0, 0), zebraBk(zebra)); |
| | | |
| | | std::vector<CString> ccols(colCount); |
| | | ccols[4] = r.buddyId.c_str(); // 占位 |
| | | auto* nChild = m_listCtrl.InsertChild(nParent, ccols); |
| | | m_listCtrl.SetNodeColor(nChild, RGB(0, 0, 0), zebraBk(zebra)); |
| | | |
| | | consumed.insert(makeKeyR(r)); |
| | | ++zebra; |
| | | } |
| | | |
| | | // -------- Phase 2: 剩余未消费的,作为“单条根行” ---------- |
| | | for (size_t i = 0; i < pageRef.items.size(); ++i) { |
| | | const auto& r = pageRef.items[i]; |
| | | if (consumed.count(makeKeyR(r))) continue; |
| | | |
| | | 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(makeKeyR(r)); |
| | | ++zebra; |
| | | } |
| | | |
| | | // 一次性重绘 |
| | | m_listCtrl.RebuildVisible(); |
| | | |
| | | #else |
| | | // ===== DEMO 分支(保持原样;若要演示同样逻辑,可仿照上面改造)===== |
| | | // 如果多出一条,看看它是否是“本页最后一条”的 buddy |
| | | std::optional<decltype(page.items)::value_type> lookahead; |
| | | auto iEquals = [](const std::string& a, const std::string& b) { |
| | | #ifdef _WIN32 |
| | | return _stricmp(a.c_str(), b.c_str()) == 0; |
| | | #else |
| | | return strcasecmp(a.c_str(), b.c_str()) == 0; |
| | | #endif |
| | | }; |
| | | |
| | | if (page.items.size() == rawLimit) { |
| | | const auto& last = page.items[PAGE_SIZE - 1]; |
| | | const auto& extra = page.items[PAGE_SIZE]; |
| | | bool pair = |
| | | (!last.buddyId.empty() && iEquals(last.buddyId, extra.classId)) || |
| | | (!extra.buddyId.empty() && iEquals(extra.buddyId, last.classId)); |
| | | if (pair) lookahead = extra; |
| | | page.items.pop_back(); |
| | | } |
| | | |
| | | // 你可以把 DEMO 分支也切到三元键逻辑;这里从略 |
| | | auto& pageRef = page; |
| | | std::unordered_map<std::string, size_t> idxById; |
| | | idxById.reserve(pageRef.items.size()); |
| | | for (size_t i = 0; i < pageRef.items.size(); ++i) idxById[pageRef.items[i].classId] = i; |
| | | |
| | | 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); |
| | | }; |
| | | |
| | | for (size_t i = 0; i < pageRef.items.size(); ++i) { |
| | | const auto& r = pageRef.items[i]; |
| | | if (consumed.count(r.classId)) continue; |
| | | if (!r.buddyId.empty()) { |
| | | auto it = idxById.find(r.buddyId); |
| | | if (it != idxById.end()) { |
| | | const auto& br = pageRef.items[it->second]; |
| | | if (!consumed.count(br.classId)) { |
| | | COLORREF bk = zebraBk(zebra); |
| | | std::vector<CString> pcols(colCount), ccols(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); |
| | | |
| | | 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(); |
| | | auto* nChild = m_listCtrl.InsertChild(nParent, ccols); |
| | | m_listCtrl.SetNodeColor(nChild, RGB(0, 0, 0), bk); |
| | | |
| | | consumed.insert(r.classId); |
| | | consumed.insert(br.classId); |
| | | ++zebra; |
| | | continue; |
| | | } |
| | | } |
| | | |
| | | // 插占位子 |
| | | COLORREF bk = zebraBk(zebra); |
| | | std::vector<CString> pcols(colCount), ccols(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); |
| | | |
| | | ccols[4] = r.buddyId.c_str(); |
| | | auto* nChild = m_listCtrl.InsertChild(nParent, ccols); |
| | | m_listCtrl.SetNodeColor(nChild, RGB(0, 0, 0), bk); |
| | | |
| | | consumed.insert(r.classId); |
| | | ++zebra; |
| | | } |
| | | } |
| | | for (size_t i = 0; i < pageRef.items.size(); ++i) { |
| | | const auto& r = pageRef.items[i]; |
| | | if (consumed.count(r.classId)) continue; |
| | | |
| | | 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_listCtrl.RebuildVisible(); |
| | | #endif |
| | | |
| | | // 上一页 / 下一页 |
| | | UpdatePageControls(); |
| | | |
| | | m_rebuilding = false; |
| | | } |
| | | |
| | | |
| | | void CPageGlassList::UpdatePageControls() |
| | | { |
| | |
| | | CString headers[] = { |
| | | _T(""), |
| | | _T("id"), |
| | | _T("Cassette Sequence No"), |
| | | _T("Job Sequence No"), |
| | | _T("Cassette SN"), |
| | | _T("Job SN"), |
| | | _T("Class ID"), |
| | | _T("物料类型"), |
| | | _T("状态"), |
| | |
| | | { |
| | | if (nIDEvent == 1) { |
| | | KillTimer(1); |
| | | InitRxWindow(); |
| | | InitRxWindows(); |
| | | } |
| | | else if (nIDEvent == 2) { |
| | | UpdateWipData(); // 只做增量,不重建 |
| | |
| | | void CPageGlassList::OnShowFullText(NMHDR* pNMHDR, LRESULT* pResult) |
| | | { |
| | | auto* p = reinterpret_cast<NMC_ELC_SHOWFULLTEXT*>(pNMHDR); |
| | | // 这里暂时用消息框显示;后续可换成你的详情页 |
| | | MessageBox(p->text, _T("详细信息"), MB_OK | MB_ICONINFORMATION); |
| | | |
| | | // 对话框显示工艺参数 |
| | | CProcessDataListDlg dlg; |
| | | dlg.setRawText(p->text); |
| | | dlg.DoModal(); |
| | | |
| | | *pResult = 0; |
| | | } |
| | | |
| | |
| | | CExpandableListCtrl::Node* savedTop = nullptr; |
| | | |
| | | // 3) 逐个处理 WIP:已存在 -> 就地更新;必要时“只对根补子项” |
| | | // 不存在 -> 优先挂到 buddy 容器;否则触发整页重建(新根保持顶部) |
| | | // 不存在 -> 挂到 buddy 容器;若 buddy 不在可见表,触发全量重建(保证 WIP 顶部) |
| | | for (auto* g : wipGlasses) { |
| | | if (!GlassMatchesFilters(*g, m_filters)) continue; |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | // —— 只对“根节点”补子项,且仅当 buddy 尚未出现在可见表,且根下也没有该 buddy —— |
| | | // —— 只对“根节点”补子项 —— |
| | | SERVO::CGlass* b = g->getBuddy(); |
| | | if (b) { |
| | | auto itRoot = wipRootById.find(cid); |
| | |
| | | bool buddyExistsAnywhere = (wipRowById.find(newBid) != wipRowById.end()); |
| | | bool hasChildAlready = NodeHasChildWithClassId(container, newBuddyCid); |
| | | |
| | | // 关系是否发生变化?(oldChildCid 与 newBuddyCid 不同,或有子但现在没 buddy) |
| | | // 关系是否发生变化? |
| | | bool relationChanged = |
| | | (!oldChildCid.IsEmpty() && newBuddyCid.IsEmpty()) || |
| | | (oldChildCid.IsEmpty() && !newBuddyCid.IsEmpty()) || |
| | |
| | | } |
| | | } |
| | | } |
| | | // 若当前是“子节点”,不在这里调整父子关系;让“关系变化”走全量重建 |
| | | } |
| | | else { |
| | | // 没有 buddy:如果容器下现在有子,也算关系变化,触发重建 |
| | | // 没 buddy 但容器下有子 -> 关系变化,触发全量重建 |
| | | auto itRoot = wipRootById.find(cid); |
| | | if (itRoot != wipRootById.end()) { |
| | | CExpandableListCtrl::Node* container = itRoot->second; |
| | |
| | | } |
| | | else { |
| | | // (B) 不存在:新增 |
| | | // 先尝试“挂到 buddy 的容器根”下面; |
| | | // 若 buddy 不在当前可见表,则触发全量重建(保证 WIP 顶部)。 |
| | | SERVO::CGlass* b = g->getBuddy(); |
| | | CExpandableListCtrl::Node* container = nullptr; |
| | | |
| | |
| | | } |
| | | |
| | | if (container) { |
| | | // buddy 容器存在:把 g 作为“子行”挂上去(避免重复) |
| | | CString cidCs = g->getID().c_str(); |
| | | if (!NodeHasChildWithClassId(container, cidCs)) { |
| | | if (!needRebuildChildren) { CaptureUiState(m_listCtrl, savedSel, savedTop); } |
| | |
| | | |
| | | return true; |
| | | } |
| | | |
| | | BOOL CPageGlassList::PreTranslateMessage(MSG* pMsg) |
| | | { |
| | | if (pMsg->wParam == VK_RETURN || pMsg->wParam == VK_ESCAPE) { |
| | | return TRUE; |
| | | } |
| | | |
| | | return CDialogEx::PreTranslateMessage(pMsg); |
| | | } |