| | |
| | | #include "CControlJobDlg.h" |
| | | #include "afxdialogex.h" |
| | | |
| | | // ===== 新增:标准库头 ===== |
| | | #include <array> |
| | | #include <string> |
| | | #include <unordered_set> |
| | | #include <algorithm> |
| | | |
| | | // ===== 新增:CString 的 Hash/Equal(跨 ANSI/Unicode 通吃)===== |
| | | struct CStringHash { |
| | | size_t operator()(const CString& s) const noexcept { |
| | | #ifdef _UNICODE |
| | | return std::hash<std::wstring>{}(std::wstring(s.GetString())); |
| | | #else |
| | | return std::hash<std::string>{}(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) |
| | | : CDialogEx(IDD_DIALOG_CONTROL_JOB, pParent) |
| | | { |
| | | m_pControlJob = nullptr; |
| | | } |
| | |
| | | |
| | | void CControlJobDlg::DoDataExchange(CDataExchange* pDX) |
| | | { |
| | | CDialogEx::DoDataExchange(pDX); |
| | | DDX_Control(pDX, IDC_LIST1, m_listCtrl); |
| | | 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() |
| | | |
| | | |
| | |
| | | // CControlJobDlg 消息处理程序 |
| | | BOOL CControlJobDlg::OnInitDialog() |
| | | { |
| | | CDialogEx::OnInitDialog(); |
| | | CDialogEx::OnInitDialog(); |
| | | |
| | | |
| | | // label字体 |
| | |
| | | |
| | | // 控件状态 |
| | | Resize(); |
| | | OnBnClickedButtonReload(); |
| | | SetTimer(1, 2000, nullptr); |
| | | |
| | | // 如果m_pControlJob为空,取master的 |
| | | 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); |
| | | |
| | | return TRUE; // return TRUE unless you set the focus to a control |
| | | // 异常: OCX 属性页应返回 FALSE |
| | | return TRUE; // return TRUE unless you set the focus to a control |
| | | // 异常: OCX 属性页应返回 FALSE |
| | | } |
| | | |
| | | void CControlJobDlg::OnSize(UINT nType, int cx, int cy) |
| | |
| | | |
| | | |
| | | // 关闭按钮 |
| | | int x2 = rcClient.right - 12; |
| | | int y = rcClient.bottom - 12; |
| | | pItem = GetDlgItem(IDCANCEL); |
| | | pItem->GetClientRect(&rcItem); |
| | | pItem->MoveWindow(rcClient.right - 12 - rcItem.Width(), |
| | | 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()); |
| | | |
| | |
| | | rcItem.Width(), rcItem.Height()); |
| | | y -= rcItem.Height(); |
| | | y -= 12; |
| | | |
| | | |
| | | // 线 |
| | | pItem = GetDlgItem(IDC_LINE1); |
| | | pItem->MoveWindow(12, y, rcClient.Width() - 24, 2); |
| | |
| | | |
| | | void CControlJobDlg::LoadData(SERVO::CControlJob* pControlJob) |
| | | { |
| | | m_listCtrl.ClearTree(); |
| | | if (pControlJob == nullptr) return; |
| | | // —— 工具:按“第一列键”在 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; |
| | | }; |
| | | |
| | | auto* root1 = m_listCtrl.InsertRoot({ pControlJob->id().c_str(), _T("ControlJob"), |
| | | pControlJob->getStateText().c_str(), _T(""), _T(""), pControlJob->failReason().c_str()}); |
| | | // —— 工具:有则更新、无则创建(6列) |
| | | auto EnsureChild = [&](CExpandableListCtrl::Node* parent, const std::array<CString, 6>& 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<CString, CStringHash, CStringEqual>& keep) { |
| | | if (!parent) return; |
| | | auto& vec = parent->children; |
| | | vec.erase(std::remove_if(vec.begin(), vec.end(), [&](const std::unique_ptr<CExpandableListCtrl::Node>& 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<SERVO::CGlass*>(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<CString, CStringHash, CStringEqual> pjKeysWanted; |
| | | |
| | | auto pjs = pControlJob->getPjs(); |
| | | for (auto pj : pjs) { |
| | | auto* root2 = m_listCtrl.InsertChild(root1, {pj->id().c_str(), _T("ProcessJob"), |
| | | pj->getStateText().c_str(), pj->recipeSpec().c_str(), _T(""), pj->failReason().c_str()}); |
| | | 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<CString, CStringHash, CStringEqual> gKeysWanted; |
| | | |
| | | auto cs = pj->carriers(); |
| | | for (auto c : cs) { |
| | | for (auto g : c.contexts) { |
| | | SERVO::CGlass* pGlass = (SERVO::CGlass*)g; |
| | | if (pGlass != nullptr) { |
| | | int port, slot; |
| | | auto* pGlass = static_cast<SERVO::CGlass*>(g); |
| | | if (pGlass) { |
| | | int port = 0, slot = 0; |
| | | pGlass->getOrginPort(port, slot); |
| | | std::string carrier = c.carrierId + " / Port" + std::to_string(port + 1) + " / Slot" + std::to_string(slot + 1); |
| | | m_listCtrl.InsertChild(root2, { pGlass->getID().c_str(), _T("Glass"), |
| | | pGlass->getStateText().c_str(), _T(""), carrier.c_str(), _T("") }); |
| | | 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 { |
| | | m_listCtrl.InsertChild(root2, { "Null", _T("Glass"), _T(""), _T(""), c.carrierId.c_str(), _T("") }); |
| | | 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(""); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | root2->expanded = true; |
| | | |
| | | // 删除本次不存在的 Glass 子节点 |
| | | RemoveStaleChildren(pjNode, gKeysWanted); |
| | | } |
| | | root1->expanded = true; |
| | | |
| | | // 删除本次不存在的 PJ 子节点 |
| | | RemoveStaleChildren(m_rootNode, pjKeysWanted); |
| | | |
| | | // 3.4 重建可见行(不改变 expanded 标志,避免闪烁/折叠状态丢失) |
| | | m_listCtrl.RebuildVisible(); |
| | | } |
| | | |
| | |
| | | AfxMessageBox("结批完成"); |
| | | } |
| | | |
| | | OnBnClickedButtonReload(); |
| | | } |
| | | |
| | | void CControlJobDlg::OnBnClickedButtonReload() |
| | | { |
| | | auto* cj = m_pControlJob; |
| | | if (cj == nullptr) { |
| | | cj = theApp.m_model.getMaster().getControlJob(); |
| | |
| | | GetDlgItem(IDC_BUTTON_COMPLETION_BATH)->EnableWindow(cj != nullptr); |
| | | LoadData(cj); |
| | | } |
| | | |
| | | void CControlJobDlg::OnTimer(UINT_PTR nIDEvent) |
| | | { |
| | | if (1 == nIDEvent) { |
| | | OnBnClickedButtonReload(); |
| | | } |
| | | |
| | | CDialogEx::OnTimer(nIDEvent); |
| | | } |