// CPageGlassList.cpp: 实现文件 // #include "stdafx.h" #include "Servo.h" #include "CPageGlassList.h" #include "afxdialogex.h" #include "GlassJson.h" #include "CServoUtilsTool.h" #include "ToolUnits.h" #include #define PAGE_SIZE 10 #define PAGE_BACKGROUND_COLOR RGB(252, 252, 255) // CPageGlassList 对话框 IMPLEMENT_DYNAMIC(CPageGlassList, CDialogEx) CPageGlassList::CPageGlassList(CWnd* pParent /*=nullptr*/) : CDialogEx(IDD_PAGE_GLASS_LIST, pParent) { m_crBkgnd = PAGE_BACKGROUND_COLOR; m_hbrBkgnd = nullptr; m_pObserver = nullptr; m_strStatus = ""; m_nCurPage = 0; m_nTotalPages = 1; memset(m_szTimeStart, 0, sizeof(m_szTimeStart)); memset(m_szTimeEnd, 0, sizeof(m_szTimeEnd)); m_szTimeStart[0] = '\0'; m_szTimeEnd[0] = '\0'; } CPageGlassList::~CPageGlassList() { if (m_hbrBkgnd != nullptr) { ::DeleteObject(m_hbrBkgnd); } if (m_pObserver != nullptr) { m_pObserver->unsubscribe(); m_pObserver = nullptr; } } void CPageGlassList::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); DDX_Control(pDX, IDC_DATETIMEPICKER_START, m_dateTimeStart); DDX_Control(pDX, IDC_DATETIMEPICKER_END, m_dateTimeEnd); DDX_Control(pDX, IDC_LIST_ALARM, m_listCtrl); } BEGIN_MESSAGE_MAP(CPageGlassList, CDialogEx) ON_WM_CTLCOLOR() ON_WM_DESTROY() ON_WM_SIZE() ON_WM_TIMER() ON_CBN_SELCHANGE(IDC_COMBO_DATETIME, &CPageGlassList::OnCbnSelchangeComboDatetime) ON_CBN_SELCHANGE(IDC_COMBO_STATUS_FILTER, &CPageGlassList::OnCbnSelchangeComboStatusFilter) ON_BN_CLICKED(IDC_BUTTON_SEARCH, &CPageGlassList::OnBnClickedButtonSearch) 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) END_MESSAGE_MAP() // CPageGlassList 消息处理程序 void CPageGlassList::InitRxWindow() { /* code */ // 订阅数据 IRxWindows* pRxWindows = RX_GetRxWindows(); pRxWindows->enableLog(5); if (m_pObserver == NULL) { m_pObserver = pRxWindows->allocObserver([&](IAny* pAny) -> void { // onNext pAny->addRef(); int code = pAny->getCode(); if (RX_CODE_EQ_ROBOT_TASK == code) { UpdatePageData(); } pAny->release(); }, [&]() -> void { // onComplete }, [&](IThrowable* pThrowable) -> void { // onErrorm pThrowable->printf(); }); theApp.m_model.getObservable()->observeOn(pRxWindows->mainThread())->subscribe(m_pObserver); } } void CPageGlassList::Resize() { CRect rcClient; GetClientRect(&rcClient); // ===== 常量定义 ===== const int nLeft = 12; const int nRight = 12; const int nTop = 58; const int nButtonHeight = 28; const int nButtonMarginBottom = 12; const int nSpacing = 8; const int nButtonWidth = 80; const int nLabelWidth = 100; // ===== 分页控件布局 ===== int yBottom = rcClient.bottom - nButtonMarginBottom - nButtonHeight; int xRight = rcClient.Width() - nRight; CWnd* pBtnNext = GetDlgItem(IDC_BUTTON_NEXT_PAGE); CWnd* pBtnPrev = GetDlgItem(IDC_BUTTON_PREV_PAGE); CWnd* pLabelPage = GetDlgItem(IDC_LABEL_PAGE_NUMBER); if (pBtnNext && pBtnPrev && pLabelPage) { // 获取分页文本宽度估算 //CString strLabel; //GetDlgItemText(IDC_LABEL_PAGE_NUMBER, strLabel); //if (strLabel.IsEmpty()) { // strLabel = _T("第 1 / 1 页"); //} //int nCharWidth = 8; //int nLabelWidth = strLabel.GetLength() * nCharWidth + 20; // 设置按钮和标签位置 pBtnNext->MoveWindow(xRight - nButtonWidth, yBottom, nButtonWidth, nButtonHeight); xRight -= nButtonWidth + nSpacing; pLabelPage->MoveWindow(xRight - nLabelWidth, yBottom, nLabelWidth, nButtonHeight); xRight -= nLabelWidth + nSpacing; pBtnPrev->MoveWindow(xRight - nButtonWidth, yBottom, nButtonWidth, nButtonHeight); } // ===== 表格区域布局 ===== if (nullptr != m_listCtrl.m_hWnd) { int listHeight = yBottom - nTop - nSpacing; m_listCtrl.MoveWindow(nLeft, nTop, rcClient.Width() - nLeft - nRight, listHeight); } } void CPageGlassList::InitStatusCombo() { CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_STATUS_FILTER); if (nullptr != pComboBox) { pComboBox->ResetContent(); pComboBox->AddString(_T("全部")); pComboBox->AddString(_T("Ready")); pComboBox->AddString(_T("Running")); pComboBox->AddString(_T("Error")); pComboBox->AddString(_T("Abort")); pComboBox->AddString(_T("Completed")); pComboBox->SetCurSel(0); } } void CPageGlassList::InitTimeRangeCombo() { CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_DATETIME); if (nullptr != pComboBox) { pComboBox->ResetContent(); pComboBox->AddString(_T("不限")); pComboBox->AddString(_T("今天")); pComboBox->AddString(_T("七天内")); pComboBox->AddString(_T("本月")); pComboBox->AddString(_T("今年")); pComboBox->AddString(_T("自定义")); pComboBox->SetCurSel(0); } } void CPageGlassList::InitDateTimeControls() { if (m_dateTimeStart.m_hWnd == nullptr || m_dateTimeEnd.m_hWnd == nullptr) { return; } // 禁用初始状态 m_dateTimeStart.EnableWindow(FALSE); m_dateTimeEnd.EnableWindow(FALSE); // 设置格式:显示日期 + 时间 //m_dateTimeStart.SetFormat(_T("yyyy/MM/dd HH:mm:ss")); //m_dateTimeEnd.SetFormat(_T("yyyy/MM/dd HH:mm:ss")); // 修改样式以支持时间格式 //DWORD dwStyleStart = m_dateTimeStart.GetStyle(); //DWORD dwStyleEnd = m_dateTimeEnd.GetStyle(); //m_dateTimeStart.ModifyStyle(0, DTS_TIMEFORMAT | DTS_UPDOWN); //m_dateTimeEnd.ModifyStyle(0, DTS_TIMEFORMAT); } void CPageGlassList::LoadData() { m_nCurPage = 1; UpdatePageData(); } void CPageGlassList::UpdatePageData() { // 如果为第1页, 取出缓存Glass, 符合条件则显示; m_listCtrl.DeleteAllItems(); UpdateWipData(); // 查询 auto& db = GlassLogDb::Instance(); auto page = db.queryPaged(m_filters, PAGE_SIZE, PAGE_SIZE * (m_nCurPage - 1)); for (const auto& r : page.items) { int index = m_listCtrl.InsertItem(m_listCtrl.GetItemCount(), ""); m_listCtrl.SetItemText(index, 1, std::to_string(r.id).c_str()); m_listCtrl.SetItemText(index, 2, std::to_string(r.cassetteSeqNo).c_str()); m_listCtrl.SetItemText(index, 3, std::to_string(r.jobSeqNo).c_str()); m_listCtrl.SetItemText(index, 4, r.classId.c_str()); m_listCtrl.SetItemText(index, 5, SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)r.materialType).c_str()); m_listCtrl.SetItemText(index, 6, SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)r.state).c_str()); m_listCtrl.SetItemText(index, 7, r.tStart.c_str()); m_listCtrl.SetItemText(index, 8, r.tEnd.c_str()); m_listCtrl.SetItemText(index, 9, r.buddyId.c_str()); m_listCtrl.SetItemText(index, 10, SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)r.aoiResult).c_str()); m_listCtrl.SetItemText(index, 11, r.path.c_str()); m_listCtrl.SetItemText(index, 12, r.params.c_str()); m_listCtrl.SetItemColor(index, RGB(0, 0, 0), RGB(255, 255, 0)); // 测试反序列化 /* SERVO::CGlass g2; std::string err; if (GlassJson::FromString(r.pretty, g2, &err)) { AfxMessageBox(r.pretty.c_str()); } */ } // 上一页 / 下一页 UpdatePageControls(); } void CPageGlassList::UpdateWipData() { if (m_nCurPage != 1) return; // 取出缓存Glass, 符合条件则显示; // 但要删除旧的数据 std::vector wipGlasses; theApp.m_model.m_master.getWipGlasses(wipGlasses); std::vector tempGlasses = wipGlasses; int count = m_listCtrl.GetItemCount(); if (count > 0) { for (int i = count - 1; i >= 0; i--) { SERVO::CGlass* pGlass = (SERVO::CGlass*)m_listCtrl.GetItemData(i); if (eraseGlassInVector(pGlass, wipGlasses) && GlassMatchesFilters(*pGlass, m_filters)) { // 更新 UpdateWipRow(i, pGlass); } else { // 删除 m_listCtrl.DeleteItem(i); } } } // 剩下的如符号插入 for (auto* item : wipGlasses) { if (GlassMatchesFilters(*item, m_filters)) { InsertWipRow(item); } } for (auto* item : tempGlasses) { item->release(); } } void CPageGlassList::UpdatePageControls() { CString strPage; strPage.Format(_T("第 %d / %d 页"), m_nCurPage, m_nTotalPages); SetDlgItemText(IDC_LABEL_PAGE_NUMBER, strPage); GetDlgItem(IDC_BUTTON_PREV_PAGE)->EnableWindow(m_nCurPage > 1); GetDlgItem(IDC_BUTTON_NEXT_PAGE)->EnableWindow(m_nCurPage < m_nTotalPages); } // CPageTransferLog 消息处理程序 BOOL CPageGlassList::OnInitDialog() { CDialogEx::OnInitDialog(); // TODO: 在此添加额外的初始化 SetTimer(1, 3000, nullptr); SetTimer(2, 2000, nullptr); // 下拉框控件 InitStatusCombo(); InitTimeRangeCombo(); // 日期控件 InitDateTimeControls(); // 报表控件 CString strIniFile, strItem; strIniFile.Format(_T("%s\\configuration.ini"), (LPTSTR)(LPCTSTR)theApp.m_strAppDir); DWORD dwStyle = m_listCtrl.GetExtendedStyle(); dwStyle |= LVS_EX_FULLROWSELECT; dwStyle |= LVS_EX_GRIDLINES; m_listCtrl.SetExtendedStyle(dwStyle); HIMAGELIST imageList = ImageList_Create(24, 24, ILC_COLOR24, 1, 1); ListView_SetImageList(m_listCtrl.GetSafeHwnd(), imageList, LVSIL_SMALL); CString headers[] = { _T(""), _T("id"), _T("Cassette Sequence No"), _T("Job Sequence No"), _T("Class ID"), _T("物料类型"), _T("状态"), _T("工艺开始时间"), _T("工艺结束时间"), _T("邦定Glass ID"), _T("AOI检测结果"), _T("路径"), _T("工艺参数") }; int widths[] = { 0, 80, 80, 80, 100, 120, 120, 120, 120, 200, 200, 200, 200 }; for (int i = 0; i < _countof(headers); ++i) { strItem.Format(_T("Col_%d_Width"), i); widths[i] = GetPrivateProfileInt("GlassListCtrl", strItem, widths[i], strIniFile); m_listCtrl.InsertColumn(i, headers[i], i == 0 ? LVCFMT_RIGHT : LVCFMT_LEFT, widths[i]); } Resize(); OnBnClickedButtonSearch(); return TRUE; // return TRUE unless you set the focus to a control // 异常: OCX 属性页应返回 FALSE } HBRUSH CPageGlassList::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) { if (nCtlColor == CTLCOLOR_STATIC) { pDC->SetBkColor(m_crBkgnd); } if (m_hbrBkgnd == nullptr) { m_hbrBkgnd = CreateSolidBrush(m_crBkgnd); } return m_hbrBkgnd; } void CPageGlassList::OnDestroy() { CDialogEx::OnDestroy(); if (m_hbrBkgnd != nullptr) { ::DeleteObject(m_hbrBkgnd); m_hbrBkgnd = nullptr; } if (m_pObserver != nullptr) { m_pObserver->unsubscribe(); m_pObserver = nullptr; } // 保存列宽 CString strIniFile, strItem, strTemp; strIniFile.Format(_T("%s\\configuration.ini"), (LPTSTR)(LPCTSTR)theApp.m_strAppDir); CHeaderCtrl* pHeader = m_listCtrl.GetHeaderCtrl(); for (int i = 0; i < pHeader->GetItemCount(); i++) { RECT rect; pHeader->GetItemRect(i, &rect); strItem.Format(_T("Col_%d_Width"), i); strTemp.Format(_T("%d"), rect.right - rect.left); WritePrivateProfileString("GlassListCtrl", strItem, strTemp, strIniFile); } } void CPageGlassList::OnSize(UINT nType, int cx, int cy) { CDialogEx::OnSize(nType, cx, cy); Resize(); } void CPageGlassList::OnTimer(UINT_PTR nIDEvent) { if (nIDEvent == 1) { KillTimer(1); InitRxWindow(); } else if (nIDEvent == 2) { UpdateWipData(); } CDialogEx::OnTimer(nIDEvent); } void CPageGlassList::OnCbnSelchangeComboDatetime() { CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_DATETIME); int nIndex = pComboBox->GetCurSel(); int nCount = pComboBox->GetCount(); m_dateTimeStart.EnableWindow(nIndex == nCount - 1); m_dateTimeEnd.EnableWindow(nIndex == nCount - 1); // 更新日期过滤器和页面数据 // UpdateDateFilter(); // LoadTransfers(); } void CPageGlassList::OnCbnSelchangeComboStatusFilter() { CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_STATUS_FILTER); int nIndex = pComboBox->GetCurSel(); if (nIndex == 0) { m_strStatus.clear(); } else { CString cstrText; pComboBox->GetLBText(nIndex, cstrText); m_strStatus = CT2A(cstrText); } // LoadTransfers(); } void CPageGlassList::OnBnClickedButtonSearch() { // 获取关键字输入框内容 CString strKeyword; GetDlgItemText(IDC_EDIT_KEYWORD, strKeyword); m_filters.keyword = CT2A(strKeyword); CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_DATETIME); int index = pComboBox->GetCurSel(); if (index == 0) { // 不限 m_filters.tStartFrom = std::nullopt; m_filters.tStartTo = std::nullopt; } else if (index == 1) { auto [fromUtc, toUtc] = CToolUnits::CalcQuickRangeUtc(QuickRange::Today); m_filters.tStartFrom = fromUtc; m_filters.tStartTo = toUtc; } else if (index == 2) { auto [fromUtc, toUtc] = CToolUnits::CalcQuickRangeUtc(QuickRange::Last7Days); m_filters.tStartFrom = fromUtc; m_filters.tStartTo = toUtc; } else if (index == 3) { auto [fromUtc, toUtc] = CToolUnits::CalcQuickRangeUtc(QuickRange::ThisMonth); m_filters.tStartFrom = fromUtc; m_filters.tStartTo = toUtc; } else if (index == 4) { auto [fromUtc, toUtc] = CToolUnits::CalcQuickRangeUtc(QuickRange::ThisYear); m_filters.tStartFrom = fromUtc; m_filters.tStartTo = toUtc; } else if(index == 5){ // 自定义 std::chrono::system_clock::time_point tp; if (CToolUnits::GetCtrlDateRangeUtc_StartOfDay(m_dateTimeStart, tp)) m_filters.tStartFrom = tp; if (CToolUnits::GetCtrlDateRangeUtc_EndOfDay(m_dateTimeEnd, tp)) m_filters.tStartTo = tp; } auto& db = GlassLogDb::Instance(); long long total = db.count(m_filters); m_nTotalPages = (PAGE_SIZE > 0) ? int((total + PAGE_SIZE - 1) / PAGE_SIZE) : 1; LoadData(); } void CPageGlassList::OnBnClickedButtonExport() { CFileDialog fileDialog(FALSE, _T("csv"), NULL, OFN_HIDEREADONLY, _T("CSV Files (*.csv)|*.csv||")); if (fileDialog.DoModal() != IDOK) { return; } // 导出 CSV:导出符合 filters 的“全部记录”(不受分页限制) // 返回导出的行数(不含表头) // csvPath:目标文件路径(UTF-8) auto& db = GlassLogDb::Instance(); std::string csvPath((LPTSTR)(LPCTSTR)fileDialog.GetPathName()); if (db.exportCsv(csvPath, m_filters) > 0) { AfxMessageBox("导出CSV成功!"); } } void CPageGlassList::OnBnClickedButtonPrevPage() { if (m_nCurPage > 1) { m_nCurPage--; UpdatePageData(); } } void CPageGlassList::OnBnClickedButtonNextPage() { if (m_nCurPage < m_nTotalPages) { m_nCurPage++; UpdatePageData(); } } // 核心:WIP 的 CGlass 是否命中当前 Filters // useEndTime=true 时用 tEnd 判时间(比如“完成列表”用 t_end),默认按 tStart。 bool CPageGlassList::GlassMatchesFilters(const SERVO::CGlass& g, const GlassLogDb::Filters& f, bool useEndTime/* = false*/) { // 1) 精确字段 if (f.classId && g.getID() != *f.classId) return false; if (f.cassetteSeqNo && g.getCassetteSequenceNo() != *f.cassetteSeqNo)return false; if (f.jobSeqNo && g.getJobSequenceNo() != *f.jobSeqNo) return false; // 2) 关键字(与 DB 保持一致:class_id / buddy_id / path / params / pretty) if (f.keyword) { const std::string& kw = *f.keyword; if (!(CToolUnits::containsCI(g.getID(), kw) || CToolUnits::containsCI(g.getBuddyId(), kw) || CToolUnits::containsCI(g.getPathDescription(), kw) || CToolUnits::containsCI(g.getParamsDescription(), kw))) return false; } // 3) 时间(与 DB 保持一致:默认按 t_start 过滤;需要可切到 t_end) if (f.tStartFrom || f.tStartTo) { std::optional tp = useEndTime ? g.tEnd() : g.tStart(); // 约定:若没有对应时间戳,则视为不命中(与 DB 相同:NULL 不会命中范围) if (!tp) return false; if (f.tStartFrom && *tp < *f.tStartFrom) return false; if (f.tStartTo && *tp > *f.tStartTo) return false; } return true; } void CPageGlassList::InsertWipRow(SERVO::CGlass* pGlass) { int index = m_listCtrl.InsertItem(0, ""); UpdateWipRow(index, pGlass); } void CPageGlassList::UpdateWipRow(unsigned int index, SERVO::CGlass* pGlass) { ASSERT(index < m_listCtrl.GetItemCount()); m_listCtrl.SetItemData(index, (DWORD_PTR)pGlass); m_listCtrl.SetItemColor(index, RGB(0, 0, 0), RGB(255, 255, 255)); m_listCtrl.SetItemText(index, 2, std::to_string(pGlass->getCassetteSequenceNo()).c_str()); m_listCtrl.SetItemText(index, 3, std::to_string(pGlass->getJobSequenceNo()).c_str()); m_listCtrl.SetItemText(index, 4, pGlass->getID().c_str()); m_listCtrl.SetItemText(index, 5, SERVO::CServoUtilsTool::getMaterialsTypeText(pGlass->getType()).c_str()); m_listCtrl.SetItemText(index, 6, SERVO::CServoUtilsTool::getGlassStateText(pGlass->state()).c_str()); m_listCtrl.SetItemText(index, 7, CToolUnits::TimePointToLocalString(pGlass->tStart()).c_str()); m_listCtrl.SetItemText(index, 8, CToolUnits::TimePointToLocalString(pGlass->tEnd()).c_str()); m_listCtrl.SetItemText(index, 9, pGlass->getBuddyId().c_str()); m_listCtrl.SetItemText(index, 10, SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)pGlass->getAOIInspResult()).c_str()); m_listCtrl.SetItemText(index, 11, pGlass->getPathDescription().c_str()); m_listCtrl.SetItemText(index, 12, pGlass->getParamsDescription().c_str()); } bool CPageGlassList::eraseGlassInVector(SERVO::CGlass* pGlass, std::vector& glasses) { auto iter = std::find(glasses.begin(), glasses.end(), pGlass); if (iter != glasses.end()) { glasses.erase(iter); return true; } return false; }