// CControlJobDlg.cpp: 实现文件 // #include "stdafx.h" #include "Servo.h" #include "CControlJobDlg.h" #include "afxdialogex.h" // ===== 新增:标准库头 ===== #include #include #include #include // ===== 新增:CString 的 Hash/Equal(跨 ANSI/Unicode 通吃)===== struct CStringHash { size_t operator()(const CString& s) const noexcept { #ifdef _UNICODE return std::hash{}(std::wstring(s.GetString())); #else return std::hash{}(std::string(s.GetString())); #endif } }; struct CStringEqual { bool operator()(const CString& a, const CString& b) const noexcept { return a == b; } }; // CControlJobDlg 对话框 IMPLEMENT_DYNAMIC(CControlJobDlg, CDialogEx) CControlJobDlg::CControlJobDlg(CWnd* pParent /*=nullptr*/) : CDialogEx(IDD_DIALOG_CONTROL_JOB, pParent) { m_pControlJob = nullptr; } CControlJobDlg::~CControlJobDlg() { } void CControlJobDlg::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); DDX_Control(pDX, IDC_LIST1, m_listCtrl); } BEGIN_MESSAGE_MAP(CControlJobDlg, CDialogEx) ON_WM_SIZE() ON_BN_CLICKED(IDC_BUTTON_COMPLETION_BATH, &CControlJobDlg::OnBnClickedButtonCompletionBath) ON_BN_CLICKED(IDC_BUTTON_RELOAD, &CControlJobDlg::OnBnClickedButtonReload) ON_WM_TIMER() END_MESSAGE_MAP() void CControlJobDlg::SetControlJob(SERVO::CControlJob* pControlJob) { m_pControlJob = pControlJob; } // CControlJobDlg 消息处理程序 BOOL CControlJobDlg::OnInitDialog() { CDialogEx::OnInitDialog(); // label字体 LOGFONT lf{}; GetFont()->GetLogFont(&lf); lf.lfHeight = -20; lf.lfWeight = FW_BOLD; _tcscpy_s(lf.lfFaceName, _T("Arial")); m_fontNoJob.CreateFontIndirect(&lf); GetDlgItem(IDC_LABEL_NO_JOB)->SetFont(&m_fontNoJob); // 列表控件 HIMAGELIST imageList = ImageList_Create(24, 24, ILC_COLOR24, 1, 1); ListView_SetImageList(m_listCtrl.GetSafeHwnd(), imageList, LVSIL_SMALL); m_listCtrl.ModifyStyle(0, LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS); m_listCtrl.InsertColumn(0, _T("ID"), LVCFMT_LEFT, 180); m_listCtrl.InsertColumn(1, _T("类型"), LVCFMT_LEFT, 120); m_listCtrl.InsertColumn(2, _T("状态"), LVCFMT_LEFT, 120); m_listCtrl.InsertColumn(3, _T("配方"), LVCFMT_LEFT, 120); m_listCtrl.InsertColumn(4, _T("Port / Carrier / Slot"), LVCFMT_LEFT, 180); m_listCtrl.InsertColumn(5, _T("描述"), LVCFMT_LEFT, 220); // 控件状态 Resize(); OnBnClickedButtonReload(); SetTimer(1, 2000, nullptr); return TRUE; // return TRUE unless you set the focus to a control // 异常: OCX 属性页应返回 FALSE } void CControlJobDlg::OnSize(UINT nType, int cx, int cy) { CDialogEx::OnSize(nType, cx, cy); if (GetDlgItem(IDC_LIST1) == nullptr) return; Resize(); } void CControlJobDlg::Resize() { CRect rcClient, rcItem; CWnd* pItem; GetClientRect(rcClient); // 关闭按钮 int x2 = rcClient.right - 12; int y = rcClient.bottom - 12; pItem = GetDlgItem(IDCANCEL); pItem->GetClientRect(&rcItem); pItem->MoveWindow(x2 - rcItem.Width(), y - rcItem.Height(), rcItem.Width(), rcItem.Height()); x2 -= rcItem.Width(); x2 -= 8; pItem = GetDlgItem(IDC_BUTTON_RELOAD); pItem->GetClientRect(&rcItem); pItem->MoveWindow(x2 - rcItem.Width(), y - rcItem.Height(), rcItem.Width(), rcItem.Height()); // 结批按钮 pItem = GetDlgItem(IDC_BUTTON_COMPLETION_BATH); pItem->GetClientRect(&rcItem); pItem->MoveWindow(12, y - rcItem.Height(), rcItem.Width(), rcItem.Height()); y -= rcItem.Height(); y -= 12; // 线 pItem = GetDlgItem(IDC_LINE1); pItem->MoveWindow(12, y, rcClient.Width() - 24, 2); y -= 2; // Label pItem = GetDlgItem(IDC_LABEL_NO_JOB); pItem->GetClientRect(&rcItem); pItem->MoveWindow((rcClient.Width() - rcItem.Width()) / 2, (y - rcItem.Height()) / 2, rcItem.Width(), rcItem.Height()); // ListCtrl pItem = GetDlgItem(IDC_LIST1); pItem->MoveWindow(12, 12, rcClient.Width() - 24, y - 12); } void CControlJobDlg::ShowGroup1(BOOL bShow) { GetDlgItem(IDC_LABEL_NO_JOB)->ShowWindow(bShow ? SW_SHOW : SW_HIDE); GetDlgItem(IDC_LINE1)->ShowWindow(bShow ? SW_SHOW : SW_HIDE); } void CControlJobDlg::ShowGroup2(BOOL bShow) { GetDlgItem(IDC_LIST1)->ShowWindow(bShow ? SW_SHOW : SW_HIDE); } void CControlJobDlg::LoadData(SERVO::CControlJob* pControlJob) { // —— 工具:按“第一列键”在 parent 下查找子节点 auto FindChildByKey = [&](CExpandableListCtrl::Node* parent, LPCTSTR key)->CExpandableListCtrl::Node* { if (!parent) return nullptr; for (auto& up : parent->children) { auto* n = up.get(); if (n && n->cols.size() > 0 && n->cols[0].CompareNoCase(key) == 0) return n; } return nullptr; }; // —— 工具:有则更新、无则创建(6列) auto EnsureChild = [&](CExpandableListCtrl::Node* parent, const std::array& cols)->CExpandableListCtrl::Node* { CExpandableListCtrl::Node* n = FindChildByKey(parent, cols[0]); if (!n) { n = m_listCtrl.InsertChild(parent, { cols[0], cols[1], cols[2], cols[3], cols[4], cols[5] }); } else { if ((int)n->cols.size() < 6) n->cols.resize(6); for (int i = 0; i < 6; i++) if (n->cols[i] != cols[i]) n->cols[i] = cols[i]; } return n; }; // —— 工具:删除 parent 下“本次未出现”的子节点(基于第一列键) auto RemoveStaleChildren = [&](CExpandableListCtrl::Node* parent, const std::unordered_set& keep) { if (!parent) return; auto& vec = parent->children; vec.erase(std::remove_if(vec.begin(), vec.end(), [&](const std::unique_ptr& up) { auto* n = up.get(); if (!n || n->cols.empty()) return true; // 防御:无效行直接删 return keep.find(n->cols[0]) == keep.end(); }), vec.end()); }; // ———————————— 1) 无数据:清空并复位缓存 ———————————— if (!pControlJob) { m_listCtrl.ClearTree(); m_rootNode = nullptr; m_lastCjPtr = nullptr; m_lastCjId.Empty(); m_listCtrl.RebuildVisible(); return; } const CString curId = pControlJob->id().c_str(); const bool cjChanged = (pControlJob != m_lastCjPtr) || (curId != m_lastCjId); // ———————————— 2) CJ 变了:整棵重建(保留展开标记不必管,重建即可) ———————————— if (cjChanged || !m_rootNode) { m_listCtrl.ClearTree(); m_rootNode = m_listCtrl.InsertRoot({ pControlJob->id().c_str(), _T("ControlJob"), pControlJob->getStateText().c_str(), _T(""), _T(""), pControlJob->failReason().c_str() }); auto pjs = pControlJob->getPjs(); for (auto pj : pjs) { auto* pjNode = m_listCtrl.InsertChild(m_rootNode, { pj->id().c_str(), _T("ProcessJob"), pj->getStateText().c_str(), pj->recipeSpec().c_str(), _T(""), pj->failReason().c_str() }); auto cs = pj->carriers(); for (auto c : cs) { for (auto g : c.contexts) { auto* pGlass = static_cast(g); if (pGlass) { int port = 0, slot = 0; pGlass->getOrginPort(port, slot); CString carrier; carrier.Format(_T("%s / Port%d / Slot%d"), CString(c.carrierId.c_str()), port + 1, slot + 1); m_listCtrl.InsertChild(pjNode, { pGlass->getID().c_str(), _T("Glass"), pGlass->getStateText().c_str(), _T(""), carrier, _T("") }); } else { m_listCtrl.InsertChild(pjNode, { _T("Null@") + CString(c.carrierId.c_str()), _T("Glass"), _T(""), _T(""), CString(c.carrierId.c_str()), _T("") }); } } } pjNode->expanded = true; } m_rootNode->expanded = true; m_lastCjPtr = pControlJob; m_lastCjId = curId; m_listCtrl.RebuildVisible(); return; } // ———————————— 3) CJ 未变:增量更新 ———————————— // 3.1 更新 CJ 行文本(状态可能变化) if ((int)m_rootNode->cols.size() < 6) m_rootNode->cols.resize(6); m_rootNode->cols[0] = pControlJob->id().c_str(); m_rootNode->cols[1] = _T("ControlJob"); m_rootNode->cols[2] = pControlJob->getStateText().c_str(); // cols[3] 保留为空 // cols[4] 保留为空 m_rootNode->cols[5] = pControlJob->failReason().c_str(); // 3.2 同步 PJ 层 std::unordered_set pjKeysWanted; auto pjs = pControlJob->getPjs(); for (auto pj : pjs) { CString pjId = pj->id().c_str(); pjKeysWanted.insert(pjId); auto* pjNode = FindChildByKey(m_rootNode, pjId); if (!pjNode) { pjNode = m_listCtrl.InsertChild(m_rootNode, { pjId, _T("ProcessJob"), pj->getStateText().c_str(), pj->recipeSpec().c_str(), _T(""), pj->failReason().c_str() }); pjNode->expanded = true; // 初次出现默认展开 } else { if ((int)pjNode->cols.size() < 6) pjNode->cols.resize(6); pjNode->cols[1] = _T("ProcessJob"); pjNode->cols[2] = pj->getStateText().c_str(); pjNode->cols[3] = pj->recipeSpec().c_str(); pjNode->cols[5] = pj->failReason().c_str(); } // 3.3 同步 Glass 层(第一列键:GlassID;空对象用 "Null@CarrierId" 防冲突) std::unordered_set gKeysWanted; auto cs = pj->carriers(); for (auto c : cs) { for (auto g : c.contexts) { auto* pGlass = static_cast(g); if (pGlass) { int port = 0, slot = 0; pGlass->getOrginPort(port, slot); CString carrier; carrier.Format(_T("%s / Port%d / Slot%d"), CString(c.carrierId.c_str()), port + 1, slot + 1); CString gid = pGlass->getID().c_str(); gKeysWanted.insert(gid); auto* rowG = FindChildByKey(pjNode, gid); if (!rowG) { m_listCtrl.InsertChild(pjNode, { gid, _T("Glass"), pGlass->getStateText().c_str(), _T(""), carrier, _T("") }); } else { if ((int)rowG->cols.size() < 6) rowG->cols.resize(6); rowG->cols[1] = _T("Glass"); rowG->cols[2] = pGlass->getStateText().c_str(); rowG->cols[3] = _T(""); rowG->cols[4] = carrier; rowG->cols[5] = _T(""); } } else { CString key = _T("Null@") + CString(c.carrierId.c_str()); gKeysWanted.insert(key); auto* rowG = FindChildByKey(pjNode, key); if (!rowG) { m_listCtrl.InsertChild(pjNode, { key, _T("Glass"), _T(""), _T(""), CString(c.carrierId.c_str()), _T("") }); } else { if ((int)rowG->cols.size() < 6) rowG->cols.resize(6); rowG->cols[1] = _T("Glass"); rowG->cols[2] = _T(""); rowG->cols[3] = _T(""); rowG->cols[4] = CString(c.carrierId.c_str()); rowG->cols[5] = _T(""); } } } } // 删除本次不存在的 Glass 子节点 RemoveStaleChildren(pjNode, gKeysWanted); } // 删除本次不存在的 PJ 子节点 RemoveStaleChildren(m_rootNode, pjKeysWanted); // 3.4 重建可见行(不改变 expanded 标志,避免闪烁/折叠状态丢失) m_listCtrl.RebuildVisible(); } void CControlJobDlg::OnBnClickedButtonCompletionBath() { if (theApp.m_model.getMaster().completeControlJob("测试手动结批")) { AfxMessageBox("结批完成"); } OnBnClickedButtonReload(); } void CControlJobDlg::OnBnClickedButtonReload() { auto* cj = m_pControlJob; if (cj == nullptr) { cj = theApp.m_model.getMaster().getControlJob(); } ShowGroup1(cj == nullptr); ShowGroup2(cj != nullptr); GetDlgItem(IDC_BUTTON_COMPLETION_BATH)->EnableWindow(cj != nullptr); LoadData(cj); } void CControlJobDlg::OnTimer(UINT_PTR nIDEvent) { if (1 == nIDEvent) { OnBnClickedButtonReload(); } CDialogEx::OnTimer(nIDEvent); }