| | |
| | | #include "GlassJson.h" |
| | | #include "CServoUtilsTool.h" |
| | | #include "ToolUnits.h" |
| | | |
| | | #include <optional> |
| | | #include <unordered_set> |
| | | #include <unordered_map> |
| | | #include <vector> |
| | | #include <string> |
| | | |
| | | #define PAGE_SIZE 10 |
| | | #define PAGE_BACKGROUND_COLOR RGB(252, 252, 255) |
| | | |
| | | |
| | | #define PAGE_SIZE 10 |
| | | #define PAGE_BACKGROUND_COLOR RGB(252, 252, 255) |
| | | // ===== 放在 CPageGlassList.cpp 顶部的匿名工具(文件内静态) ===== |
| | | // 把当前“已展开”的父行,用它们的 classId(第4列文本)做 key 记录下来 |
| | | static std::unordered_set<std::string> SnapshotExpandedKeys(CExpandableListCtrl& lv) { |
| | | std::unordered_set<std::string> keys; |
| | | for (int i = 0; i < lv.GetItemCount(); ++i) { |
| | | auto* n = lv.GetNodeByVisibleIndex(i); |
| | | if (!n || n->children.empty() || !n->expanded) continue; |
| | | if ((int)n->cols.size() > 4) { |
| | | #ifdef _UNICODE |
| | | keys.insert(CT2A(n->cols[4])); |
| | | #else |
| | | keys.insert(n->cols[4].GetString()); |
| | | #endif |
| | | } |
| | | } |
| | | return keys; |
| | | } |
| | | |
| | | // 根据快照恢复展开状态(在你新建完每个“父节点”时调用一次) |
| | | static void MaybeRestoreExpandByKey(CExpandableListCtrl::Node* n, |
| | | const std::unordered_set<std::string>& keys) { |
| | | if (!n || (int)n->cols.size() <= 4) return; |
| | | #ifdef _UNICODE |
| | | std::string k = CT2A(n->cols[4]); |
| | | #else |
| | | std::string k = n->cols[4].GetString(); |
| | | #endif |
| | | if (keys.find(k) != keys.end()) |
| | | n->expanded = true; |
| | | } |
| | | |
| | | static void CaptureUiState(CExpandableListCtrl& lv, |
| | | std::vector<CExpandableListCtrl::Node*>& outSel, |
| | | CExpandableListCtrl::Node*& outTopNode) |
| | | { |
| | | outSel.clear(); outTopNode = nullptr; |
| | | const int top = lv.GetTopIndex(); |
| | | if (top >= 0 && top < lv.GetItemCount()) |
| | | outTopNode = lv.GetNodeByVisibleIndex(top); |
| | | for (int i = 0; i < lv.GetItemCount(); ++i) { |
| | | if ((lv.GetItemState(i, LVIS_SELECTED) & LVIS_SELECTED) != 0) { |
| | | auto* n = lv.GetNodeByVisibleIndex(i); |
| | | if (n) outSel.push_back(n); |
| | | } |
| | | } |
| | | } |
| | | static void RestoreUiState(CExpandableListCtrl& lv, |
| | | const std::vector<CExpandableListCtrl::Node*>& sel, |
| | | CExpandableListCtrl::Node* topNode) |
| | | { |
| | | // 清掉现有选择 |
| | | for (int i = 0; i < lv.GetItemCount(); ++i) |
| | | lv.SetItemState(i, 0, LVIS_SELECTED); |
| | | |
| | | // 恢复选择 |
| | | for (auto* n : sel) { |
| | | for (int i = 0; i < lv.GetItemCount(); ++i) { |
| | | if (lv.GetNodeByVisibleIndex(i) == n) { |
| | | lv.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED); |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | // 尽量把之前的顶行滚回可见 |
| | | if (topNode) { |
| | | for (int i = 0; i < lv.GetItemCount(); ++i) { |
| | | if (lv.GetNodeByVisibleIndex(i) == topNode) { |
| | | lv.EnsureVisible(i, FALSE); |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | // ====== 开关:1=启用假数据(只替换 DB 查询);0=用真实 DB ====== |
| | | #define USE_FAKE_DB_DEMO 1 |
| | | |
| | | #if USE_FAKE_DB_DEMO |
| | | #include <ctime> |
| | | #include <atlconv.h> // CStringA |
| | | #include <initializer_list> |
| | | #include <string> |
| | | #include <vector> |
| | | |
| | | // ---- 模拟记录/分页结构(字段与你现有代码一致)---- |
| | | struct FakeDbRecord { |
| | | int id; |
| | | int cassetteSeqNo; |
| | | int jobSeqNo; |
| | | std::string classId; |
| | | int materialType; |
| | | int state; |
| | | std::string tStart; |
| | | std::string tEnd; |
| | | std::string buddyId; |
| | | int aoiResult; |
| | | std::string path; |
| | | std::string params; |
| | | }; |
| | | struct FakeDbPage { std::vector<FakeDbRecord> items; }; |
| | | |
| | | // ---- CString -> std::string(ANSI,本地代码页;仅用于测试模拟)---- |
| | | static std::string toAnsi(const CString& s) { |
| | | #ifdef _UNICODE |
| | | CStringA a(s); |
| | | return std::string(a.GetString()); |
| | | #else |
| | | return std::string(s.GetString()); |
| | | #endif |
| | | } |
| | | |
| | | // ---- 安全拼接工具:不使用运算符 +(避免重载/转换问题)---- |
| | | static std::string sjoin(std::initializer_list<std::string> parts) { |
| | | std::string out; |
| | | size_t total = 0; for (const auto& p : parts) total += p.size(); |
| | | out.reserve(total); |
| | | for (const auto& p : parts) out.append(p); |
| | | return out; |
| | | } |
| | | |
| | | // ---- 时间格式工具:now + days/minutes 偏移 ---- |
| | | static std::string _fmt_time(int daysOff, int minutesOff) { |
| | | using namespace std::chrono; |
| | | auto now = system_clock::now() + hours(24 * daysOff) + minutes(minutesOff); |
| | | std::time_t tt = system_clock::to_time_t(now); |
| | | std::tm tm{}; |
| | | #ifdef _WIN32 |
| | | localtime_s(&tm, &tt); |
| | | #else |
| | | tm = *std::localtime(&tt); |
| | | #endif |
| | | char buf[32]; |
| | | std::snprintf(buf, sizeof(buf), "%04d-%02d-%02d %02d:%02d:%02d", |
| | | tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); |
| | | return std::string(buf); |
| | | } |
| | | |
| | | // ---- 造全量假数据(含同页配对/跨页配对/单条)---- |
| | | static void _make_all_fake(std::vector<FakeDbRecord>& outAll) { |
| | | outAll.clear(); |
| | | int id = 1000; |
| | | |
| | | // ===== 先造几条“没有 Buddy”的记录(保证出现在第 1 页开头)===== |
| | | for (int n = 1; n <= 4; ++n) { |
| | | CString cid; cid.Format(_T("NB%03d"), n); |
| | | outAll.push_back(FakeDbRecord{ |
| | | id++, 700 + n, 800 + n, toAnsi(cid), |
| | | n % 3, n % 5, |
| | | _fmt_time(0, -n * 5), // 开始时间稍早一点 |
| | | _fmt_time(0, -n * 5 + 1), |
| | | std::string(), // buddyId 为空 |
| | | n % 4, |
| | | sjoin({ "path/", toAnsi(cid) }), |
| | | sjoin({ "{\"noBuddy\":", std::to_string(n), "}" }) |
| | | }); |
| | | } |
| | | |
| | | // ===== 造配对数据的工具 ===== |
| | | auto mkPair = [&](int k, bool crossPage) { |
| | | // 互为 buddy 的两条 |
| | | CString a; a.Format(_T("G%04dA"), k); |
| | | CString b; b.Format(_T("G%04dB"), k); |
| | | |
| | | FakeDbRecord A{ |
| | | id++, 100 + k, 200 + k, toAnsi(a), |
| | | k % 3, k % 5, _fmt_time(0, k * 3), _fmt_time(0, k * 3 + 2), |
| | | toAnsi(b), k % 4, |
| | | sjoin({ "path/", toAnsi(a) }), |
| | | sjoin({ "{\"k\":\"", toAnsi(a), "\"}" }) |
| | | }; |
| | | FakeDbRecord B{ |
| | | id++, 110 + k, 210 + k, toAnsi(b), |
| | | (k + 1) % 3, (k + 2) % 5, _fmt_time(0, k * 3 + 1), _fmt_time(0, k * 3 + 4), |
| | | toAnsi(a), (k + 1) % 4, |
| | | sjoin({ "path/", toAnsi(b) }), |
| | | sjoin({ "{\"k\":\"", toAnsi(b), "\"}" }) |
| | | }; |
| | | |
| | | if (crossPage) { |
| | | // 先放 A,再插 3 条“单条”把 B 挤到后页,最后放 B |
| | | outAll.push_back(A); |
| | | for (int s = 0; s < 3; ++s) { |
| | | CString sid; sid.Format(_T("S%04d_%d"), k, s); |
| | | outAll.push_back(FakeDbRecord{ |
| | | id++, 300 + k * 10 + s, 400 + k * 10 + s, toAnsi(sid), |
| | | (k + s) % 3, (k + s) % 5, _fmt_time(0, k * 2 + s), _fmt_time(0, k * 2 + s + 1), |
| | | std::string(), (k + s) % 4, |
| | | sjoin({ "path/", toAnsi(sid) }), |
| | | sjoin({ "{\"single\":", std::to_string(s), "}" }) |
| | | }); |
| | | } |
| | | outAll.push_back(B); |
| | | } |
| | | else { |
| | | // 同页紧挨着 |
| | | outAll.push_back(A); |
| | | outAll.push_back(B); |
| | | } |
| | | }; |
| | | |
| | | // ===== 然后按原逻辑追加:同页配对 / 跨页配对 / 单条 ===== |
| | | // 6 组同页配对(12 条) |
| | | for (int k = 1; k <= 6; ++k) mkPair(k, false); |
| | | // 4 组跨页配对(每组中间插 3 条“单条”) |
| | | for (int k = 101; k <= 104; ++k) mkPair(k, true); |
| | | // 若干“单条” |
| | | for (int u = 201; u < 206; ++u) { |
| | | CString cid; cid.Format(_T("U%04d"), u); |
| | | outAll.push_back(FakeDbRecord{ |
| | | id++, 500 + u, 600 + u, toAnsi(cid), |
| | | u % 3, u % 5, _fmt_time(0, u % 17), _fmt_time(0, (u % 17) + 1), |
| | | std::string(), u % 4, |
| | | sjoin({ "path/", toAnsi(cid) }), |
| | | sjoin({ "{\"u\":", std::to_string(u), "}" }) |
| | | }); |
| | | } |
| | | } |
| | | |
| | | |
| | | // ---- 做分页切片(可按需加更严格的 filters)---- |
| | | static FakeDbPage _make_page_fake(const GlassLogDb::Filters& /*f*/, int pageSize, int offset) { |
| | | std::vector<FakeDbRecord> all; |
| | | _make_all_fake(all); |
| | | |
| | | FakeDbPage page; |
| | | int n = (int)all.size(); |
| | | int beg = min(max(0, offset), n); |
| | | int end = min(beg + max(0, pageSize), n); |
| | | page.items.insert(page.items.end(), all.begin() + beg, all.begin() + end); |
| | | return page; |
| | | } |
| | | static int _fake_total_count() { |
| | | std::vector<FakeDbRecord> all; |
| | | _make_all_fake(all); |
| | | return (int)all.size(); |
| | | } |
| | | #endif // USE_FAKE_DB_DEMO |
| | | |
| | | // 判断某 parent 下是否已存在 classId == cid 的子节点(忽略大小写) |
| | | static bool NodeHasChildWithClassId(CExpandableListCtrl::Node* parent, const CString& cid) |
| | | { |
| | | if (!parent) return false; |
| | | for (auto& ch : parent->children) { |
| | | if (ch && ch->cols.size() > 4) { |
| | | if (ch->cols[4].CompareNoCase(cid) == 0) |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | // 把 cols 写回到某一行(从第1列开始;第0列我们没用) |
| | | static void ApplyColsToRow(CListCtrl& lv, int row, const std::vector<CString>& cols) { |
| | | int colCount = 0; |
| | | if (auto* hdr = lv.GetHeaderCtrl()) colCount = hdr->GetItemCount(); |
| | | colCount = min(colCount, (int)cols.size()); |
| | | for (int c = 1; c < colCount; ++c) { |
| | | lv.SetItemText(row, c, cols[c]); |
| | | } |
| | | } |
| | | |
| | | // 选中行的 ClassID 快照(用于重建后恢复) |
| | | static std::unordered_set<std::string> SnapshotSelectedKeys(CListCtrl& lv) { |
| | | std::unordered_set<std::string> keys; |
| | | int n = lv.GetItemCount(); |
| | | for (int i = 0; i < n; ++i) { |
| | | if ((lv.GetItemState(i, LVIS_SELECTED) & LVIS_SELECTED) == 0) continue; |
| | | CString cls = lv.GetItemText(i, 4); |
| | | #ifdef _UNICODE |
| | | keys.insert(CT2A(cls)); |
| | | #else |
| | | keys.insert(cls.GetString()); |
| | | #endif |
| | | } |
| | | return keys; |
| | | } |
| | | |
| | | // 顶行(TopIndex)对应的 ClassID |
| | | static std::optional<std::string> SnapshotTopKey(CListCtrl& lv) { |
| | | int top = lv.GetTopIndex(); |
| | | if (top < 0 || top >= lv.GetItemCount()) return std::nullopt; |
| | | CString cls = lv.GetItemText(top, 4); |
| | | #ifdef _UNICODE |
| | | return std::optional<std::string>(CT2A(cls)); |
| | | #else |
| | | return std::optional<std::string>(cls.GetString()); |
| | | #endif |
| | | } |
| | | |
| | | // 用 ClassID 集合恢复选中 |
| | | static void RestoreSelectionByKeys(CListCtrl& lv, const std::unordered_set<std::string>& keys) { |
| | | int n = lv.GetItemCount(); |
| | | for (int i = 0; i < n; ++i) lv.SetItemState(i, 0, LVIS_SELECTED); |
| | | for (int i = 0; i < n; ++i) { |
| | | CString cls = lv.GetItemText(i, 4); |
| | | #ifdef _UNICODE |
| | | if (keys.count(CT2A(cls))) lv.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED); |
| | | #else |
| | | if (keys.count(cls.GetString())) lv.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED); |
| | | #endif |
| | | } |
| | | } |
| | | |
| | | // 尽量把某个 ClassID 滚回可见 |
| | | static void RestoreTopByKey(CListCtrl& lv, const std::optional<std::string>& key) { |
| | | if (!key) return; |
| | | int n = lv.GetItemCount(); |
| | | for (int i = 0; i < n; ++i) { |
| | | CString cls = lv.GetItemText(i, 4); |
| | | #ifdef _UNICODE |
| | | if (CT2A(cls) == *key) { lv.EnsureVisible(i, FALSE); break; } |
| | | #else |
| | | if (cls.GetString() == *key) { lv.EnsureVisible(i, FALSE); break; } |
| | | #endif |
| | | } |
| | | } |
| | | |
| | | // CPageGlassList 对话框 |
| | | |
| | | IMPLEMENT_DYNAMIC(CPageGlassList, CDialogEx) |
| | | |
| | | CPageGlassList::CPageGlassList(CWnd* pParent /*=nullptr*/) |
| | | : CDialogEx(IDD_PAGE_GLASS_LIST, pParent) |
| | | : CDialogEx(IDD_PAGE_GLASS_LIST, pParent) |
| | | { |
| | | m_crBkgnd = PAGE_BACKGROUND_COLOR; |
| | | m_hbrBkgnd = nullptr; |
| | | m_pObserver = nullptr; |
| | | m_crBkgnd = PAGE_BACKGROUND_COLOR; |
| | | m_hbrBkgnd = nullptr; |
| | | m_pObserver = nullptr; |
| | | |
| | | m_strStatus = ""; |
| | | m_nCurPage = 0; |
| | | m_nTotalPages = 1; |
| | | m_strStatus = ""; |
| | | m_nCurPage = 1; |
| | | 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'; |
| | | 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; |
| | | } |
| | | if (m_hbrBkgnd != nullptr) { |
| | | ::DeleteObject(m_hbrBkgnd); |
| | | m_hbrBkgnd = nullptr; |
| | | } |
| | | 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); |
| | | 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) |
| | | 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() |
| | | |
| | | // ===== 私有小工具 ===== |
| | | static int GetColumnCount(CListCtrl& lv) |
| | | { |
| | | CHeaderCtrl* pHdr = lv.GetHeaderCtrl(); |
| | | return pHdr ? pHdr->GetItemCount() : 0; |
| | | } |
| | | |
| | | // CPageGlassList 消息处理程序 |
| | | // ===== 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(); |
| | | // 订阅数据 |
| | | 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(); |
| | | } |
| | | if (RX_CODE_EQ_ROBOT_TASK == code) { |
| | | UpdateWipData(); // 只更新,不重建,不改变展开/选择 |
| | | } |
| | | |
| | | pAny->release(); |
| | | }, [&]() -> void { |
| | | // onComplete |
| | | }, [&](IThrowable* pThrowable) -> void { |
| | | // onErrorm |
| | | pThrowable->printf(); |
| | | }); |
| | | pAny->release(); |
| | | }, [&]() -> void { |
| | | // onComplete |
| | | }, [&](IThrowable* pThrowable) -> void { |
| | | // onError |
| | | pThrowable->printf(); |
| | | }); |
| | | |
| | | theApp.m_model.getObservable()->observeOn(pRxWindows->mainThread())->subscribe(m_pObserver); |
| | | } |
| | | theApp.m_model.getObservable()->observeOn(pRxWindows->mainThread())->subscribe(m_pObserver); |
| | | } |
| | | } |
| | | |
| | | void CPageGlassList::Resize() |
| | | { |
| | | CRect rcClient; |
| | | GetClientRect(&rcClient); |
| | | 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; |
| | | // ===== 常量定义 ===== |
| | | 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; |
| | | // ===== 分页控件布局 ===== |
| | | 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); |
| | | 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; |
| | | if (pBtnNext && pBtnPrev && pLabelPage) { |
| | | pBtnNext->MoveWindow(xRight - nButtonWidth, yBottom, nButtonWidth, nButtonHeight); |
| | | xRight -= nButtonWidth + nSpacing; |
| | | |
| | | // 设置按钮和标签位置 |
| | | pBtnNext->MoveWindow(xRight - nButtonWidth, yBottom, nButtonWidth, nButtonHeight); |
| | | xRight -= nButtonWidth + nSpacing; |
| | | pLabelPage->MoveWindow(xRight - nLabelWidth, yBottom, nLabelWidth, nButtonHeight); |
| | | xRight -= nLabelWidth + nSpacing; |
| | | |
| | | pLabelPage->MoveWindow(xRight - nLabelWidth, yBottom, nLabelWidth, nButtonHeight); |
| | | xRight -= nLabelWidth + nSpacing; |
| | | pBtnPrev->MoveWindow(xRight - nButtonWidth, yBottom, nButtonWidth, nButtonHeight); |
| | | } |
| | | |
| | | 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); |
| | | } |
| | | // ===== 表格区域布局 ===== |
| | | 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); |
| | | } |
| | | 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); |
| | | } |
| | | 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); |
| | | if (m_dateTimeStart.m_hWnd == nullptr || m_dateTimeEnd.m_hWnd == nullptr) { |
| | | return; |
| | | } |
| | | // 自定义范围时才可编辑 |
| | | m_dateTimeStart.EnableWindow(FALSE); |
| | | m_dateTimeEnd.EnableWindow(FALSE); |
| | | } |
| | | |
| | | void CPageGlassList::LoadData() |
| | | { |
| | | m_nCurPage = 1; |
| | | UpdatePageData(); |
| | | m_nCurPage = 1; |
| | | UpdatePageData(); |
| | | } |
| | | |
| | | void CPageGlassList::UpdatePageData() |
| | | { |
| | | // 如果为第1页, 取出缓存Glass, 符合条件则显示; |
| | | m_listCtrl.DeleteAllItems(); |
| | | UpdateWipData(); |
| | | m_rebuilding = true; |
| | | |
| | | // 放在任何清空/重建动作之前: |
| | | auto expandedKeys = SnapshotExpandedKeys(m_listCtrl); |
| | | |
| | | // 查询 |
| | | 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(), std::to_string(r.id).c_str()); |
| | | m_listCtrl.SetItemText(index, 1, std::to_string(r.cassetteSeqNo).c_str()); |
| | | m_listCtrl.SetItemText(index, 2, std::to_string(r.jobSeqNo).c_str()); |
| | | m_listCtrl.SetItemText(index, 3, r.classId.c_str()); |
| | | m_listCtrl.SetItemText(index, 4, SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)r.materialType).c_str()); |
| | | m_listCtrl.SetItemText(index, 5, SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)r.state).c_str()); |
| | | m_listCtrl.SetItemText(index, 6, r.tStart.c_str()); |
| | | m_listCtrl.SetItemText(index, 7, r.tEnd.c_str()); |
| | | m_listCtrl.SetItemText(index, 8, r.buddyId.c_str()); |
| | | m_listCtrl.SetItemText(index, 9, SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)r.aoiResult).c_str()); |
| | | m_listCtrl.SetItemText(index, 10, r.path.c_str()); |
| | | m_listCtrl.SetItemText(index, 11, r.params.c_str()); |
| | | m_listCtrl.SetItemColor(index, RGB(0, 0, 0), RGB(255, 255, 0)); |
| | | // —— 双保险:先清掉可见项,再清树结构 —— |
| | | m_listCtrl.SetRedraw(FALSE); |
| | | m_listCtrl.DeleteAllItems(); |
| | | m_listCtrl.SetRedraw(TRUE); |
| | | |
| | | // 测试反序列化 |
| | | /* |
| | | SERVO::CGlass g2; |
| | | std::string err; |
| | | if (GlassJson::FromString(r.pretty, g2, &err)) { |
| | | AfxMessageBox(r.pretty.c_str()); |
| | | } |
| | | */ |
| | | } |
| | | // —— 清空树(依赖 CExpandableListCtrl::ClearTree())—— |
| | | m_listCtrl.ClearTree(); |
| | | |
| | | // 上一页 / 下一页 |
| | | UpdatePageControls(); |
| | | } |
| | | const int colCount = m_listCtrl.GetHeaderCtrl() ? m_listCtrl.GetHeaderCtrl()->GetItemCount() : 0; |
| | | if (colCount <= 0) return; |
| | | |
| | | void CPageGlassList::UpdateWipData() |
| | | { |
| | | if (m_nCurPage != 1) return; |
| | | // ==================== 1) WIP:仅第 1 页构建,且放在最顶部 ==================== |
| | | if (m_nCurPage == 1) { |
| | | std::vector<SERVO::CGlass*> wipGlasses; |
| | | theApp.m_model.m_master.getWipGlasses(wipGlasses); |
| | | std::vector<SERVO::CGlass*> tempGlasses = wipGlasses; // 待释放 |
| | | |
| | | // 取出缓存Glass, 符合条件则显示; |
| | | // 但要删除旧的数据 |
| | | std::vector<SERVO::CGlass*> wipGlasses; |
| | | theApp.m_model.m_master.getWipGlasses(wipGlasses); |
| | | std::vector<SERVO::CGlass*> 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); |
| | | } |
| | | } |
| | | } |
| | | auto glassHit = [&](SERVO::CGlass* g) -> bool { |
| | | return g && GlassMatchesFilters(*g, m_filters); |
| | | }; |
| | | |
| | | // 剩下的如符号插入 |
| | | for (auto* item : wipGlasses) { |
| | | if (GlassMatchesFilters(*item, m_filters)) { |
| | | InsertWipRow(item); |
| | | } |
| | | } |
| | | for (auto* item : tempGlasses) { |
| | | item->release(); |
| | | } |
| | | std::unordered_set<SERVO::CGlass*> usedWip; |
| | | |
| | | for (auto* g : wipGlasses) { |
| | | if (!glassHit(g) || usedWip.count(g)) continue; |
| | | |
| | | SERVO::CGlass* b = g->getBuddy(); |
| | | if (b) { |
| | | SERVO::CGlass* parent = g; |
| | | SERVO::CGlass* child = b; |
| | | |
| | | std::vector<CString> pcols(colCount); |
| | | pcols[1] = _T(""); |
| | | pcols[2] = std::to_string(parent->getCassetteSequenceNo()).c_str(); |
| | | pcols[3] = std::to_string(parent->getJobSequenceNo()).c_str(); |
| | | pcols[4] = parent->getID().c_str(); |
| | | pcols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText(parent->getType()).c_str(); |
| | | pcols[6] = SERVO::CServoUtilsTool::getGlassStateText(parent->state()).c_str(); |
| | | pcols[7] = CToolUnits::TimePointToLocalString(parent->tStart()).c_str(); |
| | | pcols[8] = CToolUnits::TimePointToLocalString(parent->tEnd()).c_str(); |
| | | pcols[9] = parent->getBuddyId().c_str(); |
| | | pcols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)parent->getAOIInspResult()).c_str(); |
| | | pcols[11] = parent->getPathDescription().c_str(); |
| | | pcols[12] = parent->getParamsDescription().c_str(); |
| | | |
| | | auto* nParent = m_listCtrl.InsertRoot(pcols); |
| | | MaybeRestoreExpandByKey(nParent, expandedKeys); |
| | | m_listCtrl.SetNodeColor(nParent, RGB(0, 0, 0), RGB(201, 228, 180)); |
| | | |
| | | std::vector<CString> ccols(colCount); |
| | | ccols[1] = _T(""); |
| | | ccols[2] = std::to_string(child->getCassetteSequenceNo()).c_str(); |
| | | ccols[3] = std::to_string(child->getJobSequenceNo()).c_str(); |
| | | ccols[4] = child->getID().c_str(); |
| | | ccols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText(child->getType()).c_str(); |
| | | ccols[6] = SERVO::CServoUtilsTool::getGlassStateText(child->state()).c_str(); |
| | | ccols[7] = CToolUnits::TimePointToLocalString(child->tStart()).c_str(); |
| | | ccols[8] = CToolUnits::TimePointToLocalString(child->tEnd()).c_str(); |
| | | ccols[9] = child->getBuddyId().c_str(); |
| | | ccols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)child->getAOIInspResult()).c_str(); |
| | | ccols[11] = child->getPathDescription().c_str(); |
| | | ccols[12] = child->getParamsDescription().c_str(); |
| | | |
| | | auto* nChild = m_listCtrl.InsertChild(nParent, ccols); |
| | | m_listCtrl.SetNodeColor(nChild, RGB(0, 0, 0), RGB(201, 228, 180)); |
| | | |
| | | usedWip.insert(parent); |
| | | usedWip.insert(child); |
| | | } |
| | | else { |
| | | std::vector<CString> cols(colCount); |
| | | cols[1] = _T(""); |
| | | cols[2] = std::to_string(g->getCassetteSequenceNo()).c_str(); |
| | | cols[3] = std::to_string(g->getJobSequenceNo()).c_str(); |
| | | cols[4] = g->getID().c_str(); |
| | | cols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText(g->getType()).c_str(); |
| | | cols[6] = SERVO::CServoUtilsTool::getGlassStateText(g->state()).c_str(); |
| | | cols[7] = CToolUnits::TimePointToLocalString(g->tStart()).c_str(); |
| | | cols[8] = CToolUnits::TimePointToLocalString(g->tEnd()).c_str(); |
| | | cols[9] = g->getBuddyId().c_str(); |
| | | cols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)g->getAOIInspResult()).c_str(); |
| | | cols[11] = g->getPathDescription().c_str(); |
| | | cols[12] = g->getParamsDescription().c_str(); |
| | | |
| | | auto* n = m_listCtrl.InsertRoot(cols); |
| | | m_listCtrl.SetNodeColor(n, RGB(0, 0, 0), RGB(201, 228, 180)); |
| | | usedWip.insert(g); |
| | | } |
| | | } |
| | | for (auto* item : tempGlasses) item->release(); |
| | | } |
| | | |
| | | // ==================== 2) DB 当前页(无论第几页都构建;排在 WIP 之后) ==================== |
| | | #if USE_FAKE_DB_DEMO |
| | | auto page = _make_page_fake(m_filters, PAGE_SIZE, PAGE_SIZE * (m_nCurPage - 1)); |
| | | #else |
| | | auto& db = GlassLogDb::Instance(); |
| | | auto page = db.queryPaged(m_filters, PAGE_SIZE, PAGE_SIZE * (m_nCurPage - 1)); |
| | | #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; |
| | | |
| | | std::unordered_set<std::string> usedDb; |
| | | int zebra = 0; |
| | | |
| | | for (size_t i = 0; i < page.items.size(); ++i) { |
| | | const auto& r = page.items[i]; |
| | | if (usedDb.count(r.classId)) continue; |
| | | |
| | | COLORREF bk = (zebra % 2 == 0) ? RGB(255, 255, 255) : RGB(235, 235, 235); |
| | | bool paired = false; |
| | | |
| | | 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; |
| | | } |
| | | } |
| | | } |
| | | |
| | | 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(); |
| | | |
| | | 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); |
| | | |
| | | usedDb.insert(r.classId); |
| | | paired = true; |
| | | } |
| | | |
| | | 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(); |
| | | |
| | | auto* n = m_listCtrl.InsertRoot(cols); |
| | | m_listCtrl.SetNodeColor(n, RGB(0, 0, 0), bk); |
| | | usedDb.insert(r.classId); |
| | | } |
| | | |
| | | ++zebra; |
| | | } |
| | | |
| | | // 一次性重绘 |
| | | m_listCtrl.RebuildVisible(); |
| | | |
| | | // 上一页 / 下一页 |
| | | UpdatePageControls(); |
| | | |
| | | m_rebuilding = false; |
| | | } |
| | | |
| | | 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); |
| | | 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(); |
| | | CDialogEx::OnInitDialog(); |
| | | |
| | | // TODO: 在此添加额外的初始化 |
| | | SetTimer(1, 3000, nullptr); |
| | | SetTimer(2, 2000, nullptr); |
| | | // 定时器:1=初始化订阅,2=周期刷新(只增量) |
| | | SetTimer(1, 3000, nullptr); |
| | | SetTimer(2, 2000, nullptr); |
| | | |
| | | // 下拉框控件 |
| | | InitStatusCombo(); |
| | | InitTimeRangeCombo(); |
| | | // 下拉框控件 |
| | | InitStatusCombo(); |
| | | InitTimeRangeCombo(); |
| | | |
| | | // 日期控件 |
| | | InitDateTimeControls(); |
| | | // 日期控件 |
| | | InitDateTimeControls(); |
| | | |
| | | // 报表控件 |
| | | CString strIniFile, strItem; |
| | | strIniFile.Format(_T("%s\\configuration.ini"), (LPTSTR)(LPCTSTR)theApp.m_strAppDir); |
| | | // 报表控件 |
| | | 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); |
| | | DWORD dwStyle = m_listCtrl.GetExtendedStyle(); |
| | | dwStyle |= LVS_EX_FULLROWSELECT; |
| | | dwStyle |= LVS_EX_GRIDLINES; |
| | | dwStyle |= LVS_EX_DOUBLEBUFFER; |
| | | m_listCtrl.SetExtendedStyle(dwStyle); |
| | | |
| | | HIMAGELIST imageList = ImageList_Create(24, 24, ILC_COLOR24, 1, 1); |
| | | ListView_SetImageList(m_listCtrl.GetSafeHwnd(), imageList, LVSIL_SMALL); |
| | | HIMAGELIST imageList = ImageList_Create(24, 24, ILC_COLOR24, 1, 1); |
| | | ListView_SetImageList(m_listCtrl.GetSafeHwnd(), imageList, LVSIL_SMALL); |
| | | |
| | | CString headers[] = { |
| | | _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[] = { 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], LVCFMT_LEFT, widths[i]); |
| | | } |
| | | 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[] = { 24, 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); |
| | | int def = widths[i]; |
| | | widths[i] = GetPrivateProfileInt("GlassListCtrl", strItem, def, strIniFile); |
| | | if (i == 0 && widths[i] < 16) widths[i] = 24; // 让三角图标有空间展示 |
| | | m_listCtrl.InsertColumn(i, headers[i], i == 0 ? LVCFMT_RIGHT : LVCFMT_LEFT, widths[i]); |
| | | } |
| | | // 二次兜底,防止 ini 写进了 0 |
| | | if (m_listCtrl.GetColumnWidth(0) < 16) m_listCtrl.SetColumnWidth(0, 24); |
| | | |
| | | Resize(); |
| | | OnBnClickedButtonSearch(); |
| | | Resize(); |
| | | OnBnClickedButtonSearch(); // 触发一次查询与首屏填充 |
| | | |
| | | 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 |
| | | } |
| | | |
| | | 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; |
| | | 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; |
| | | } |
| | | 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); |
| | | } |
| | | // 保存列宽(首列兜底,避免把 0 写回去) |
| | | CString strIniFile, strItem, strTemp; |
| | | strIniFile.Format(_T("%s\\configuration.ini"), (LPTSTR)(LPCTSTR)theApp.m_strAppDir); |
| | | CHeaderCtrl* pHeader = m_listCtrl.GetHeaderCtrl(); |
| | | if (pHeader) { |
| | | for (int i = 0; i < pHeader->GetItemCount(); i++) { |
| | | RECT rect; |
| | | pHeader->GetItemRect(i, &rect); |
| | | strItem.Format(_T("Col_%d_Width"), i); |
| | | int w = rect.right - rect.left; |
| | | if (i == 0 && w < 16) w = 24; |
| | | strTemp.Format(_T("%d"), w); |
| | | WritePrivateProfileString("GlassListCtrl", strItem, strTemp, strIniFile); |
| | | } |
| | | } |
| | | } |
| | | |
| | | void CPageGlassList::OnSize(UINT nType, int cx, int cy) |
| | | { |
| | | CDialogEx::OnSize(nType, cx, cy); |
| | | Resize(); |
| | | CDialogEx::OnSize(nType, cx, cy); |
| | | Resize(); |
| | | } |
| | | |
| | | void CPageGlassList::OnTimer(UINT_PTR nIDEvent) |
| | | { |
| | | if (nIDEvent == 1) { |
| | | KillTimer(1); |
| | | InitRxWindow(); |
| | | } |
| | | if (nIDEvent == 1) { |
| | | KillTimer(1); |
| | | InitRxWindow(); |
| | | } |
| | | else if (nIDEvent == 2) { |
| | | UpdateWipData(); // 只做增量,不重建 |
| | | } |
| | | |
| | | else if (nIDEvent == 2) { |
| | | UpdateWipData(); |
| | | } |
| | | |
| | | CDialogEx::OnTimer(nIDEvent); |
| | | 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(); |
| | | 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); |
| | | } |
| | | |
| | | 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(); |
| | | 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); |
| | | } |
| | | } |
| | | |
| | | void CPageGlassList::OnBnClickedButtonSearch() |
| | | { |
| | | // 获取关键字输入框内容 |
| | | CString strKeyword; |
| | | GetDlgItemText(IDC_EDIT_KEYWORD, strKeyword); |
| | | m_filters.keyword = CT2A(strKeyword); |
| | | // 获取关键字输入框内容 |
| | | 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; |
| | | } |
| | | 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; |
| | | #if USE_FAKE_DB_DEMO |
| | | long long total = _fake_total_count(); |
| | | #else |
| | | auto& db = GlassLogDb::Instance(); |
| | | long long total = db.count(m_filters); |
| | | #endif |
| | | m_nTotalPages = (PAGE_SIZE > 0) ? int((total + PAGE_SIZE - 1) / PAGE_SIZE) : 1; |
| | | |
| | | LoadData(); |
| | | LoadData(); |
| | | } |
| | | |
| | | void CPageGlassList::OnBnClickedButtonExport() |
| | | { |
| | | CFileDialog fileDialog(FALSE, _T("csv"), NULL, OFN_HIDEREADONLY, _T("CSV Files (*.csv)|*.csv||")); |
| | | if (fileDialog.DoModal() != IDOK) { |
| | | return; |
| | | } |
| | | 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成功!"); |
| | | } |
| | | // 导出 CSV:导出符合 filters 的“全部记录”(不受分页限制) |
| | | 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(); |
| | | } |
| | | if (m_nCurPage > 1) { |
| | | m_nCurPage--; |
| | | UpdatePageData(); |
| | | } |
| | | } |
| | | |
| | | void CPageGlassList::OnBnClickedButtonNextPage() |
| | | { |
| | | if (m_nCurPage < m_nTotalPages) { |
| | | m_nCurPage++; |
| | | UpdatePageData(); |
| | | } |
| | | if (m_nCurPage < m_nTotalPages) { |
| | | m_nCurPage++; |
| | | UpdatePageData(); |
| | | } |
| | | } |
| | | |
| | | void CPageGlassList::UpdateWipData() |
| | | { |
| | | // 只在第 1 页刷新 WIP;其它页不动 |
| | | if (m_nCurPage != 1) 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*,仅收集“根节点”,便于只对根节点补子项 |
| | | std::unordered_map<std::string, std::pair<int, CExpandableListCtrl::Node*>> wipRowById; |
| | | 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 行,跳过 |
| | | |
| | | auto* node = m_listCtrl.GetNodeByVisibleIndex(row); |
| | | CString cls = m_listCtrl.GetItemText(row, 4); // 第4列 Class ID |
| | | #ifdef _UNICODE |
| | | std::string key = CT2A(cls); |
| | | #else |
| | | std::string key = cls.GetString(); |
| | | #endif |
| | | wipRowById[key] = { row, node }; |
| | | if (node && node->parent == nullptr) { |
| | | wipRootById[key] = node; // 仅根节点进入这个表 |
| | | } |
| | | } |
| | | |
| | | // 2) 拉当前 WIP 列表 |
| | | 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 |
| | | cols[2] = std::to_string(g->getCassetteSequenceNo()).c_str(); |
| | | cols[3] = std::to_string(g->getJobSequenceNo()).c_str(); |
| | | cols[4] = g->getID().c_str(); |
| | | cols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText(g->getType()).c_str(); |
| | | cols[6] = SERVO::CServoUtilsTool::getGlassStateText(g->state()).c_str(); |
| | | cols[7] = CToolUnits::TimePointToLocalString(g->tStart()).c_str(); |
| | | cols[8] = CToolUnits::TimePointToLocalString(g->tEnd()).c_str(); |
| | | cols[9] = g->getBuddyId().c_str(); |
| | | cols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)g->getAOIInspResult()).c_str(); |
| | | cols[11] = g->getPathDescription().c_str(); |
| | | cols[12] = g->getParamsDescription().c_str(); |
| | | return cols; |
| | | }; |
| | | |
| | | bool needRebuildChildren = false; // 本次是否新增了子节点(结构变化) |
| | | bool needRebuildAllForNewRoot = false;// 本次是否发现了“新增根节点”的需求(为保证 WIP 在顶部) |
| | | std::vector<int> rowsToRedraw; // 仅文本变化的行 |
| | | |
| | | // UI 状态(当需要重建时使用) |
| | | std::vector<CExpandableListCtrl::Node*> savedSel; |
| | | CExpandableListCtrl::Node* savedTop = nullptr; |
| | | |
| | | // 3) 逐个处理 WIP:已存在 -> 就地更新;必要时“只对根补子项” |
| | | // 不存在 -> 触发“全量重建”,以保证新 WIP 根行出现在列表顶部 |
| | | for (auto* g : wipGlasses) { |
| | | if (!GlassMatchesFilters(*g, m_filters)) continue; |
| | | |
| | | #ifdef _UNICODE |
| | | std::string cid = CT2A(g->getID().c_str()); |
| | | #else |
| | | std::string cid = g->getID(); |
| | | #endif |
| | | |
| | | auto itAny = wipRowById.find(cid); |
| | | if (itAny != wipRowById.end()) { |
| | | // (A) 已存在:仅更新文案 & 重绘该行 |
| | | int row = itAny->second.first; |
| | | m_listCtrl.SetItemText(row, 2, std::to_string(g->getCassetteSequenceNo()).c_str()); |
| | | m_listCtrl.SetItemText(row, 3, std::to_string(g->getJobSequenceNo()).c_str()); |
| | | m_listCtrl.SetItemText(row, 4, g->getID().c_str()); |
| | | m_listCtrl.SetItemText(row, 5, SERVO::CServoUtilsTool::getMaterialsTypeText(g->getType()).c_str()); |
| | | m_listCtrl.SetItemText(row, 6, SERVO::CServoUtilsTool::getGlassStateText(g->state()).c_str()); |
| | | m_listCtrl.SetItemText(row, 7, CToolUnits::TimePointToLocalString(g->tStart()).c_str()); |
| | | m_listCtrl.SetItemText(row, 8, CToolUnits::TimePointToLocalString(g->tEnd()).c_str()); |
| | | m_listCtrl.SetItemText(row, 9, g->getBuddyId().c_str()); |
| | | m_listCtrl.SetItemText(row, 10, SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)g->getAOIInspResult()).c_str()); |
| | | m_listCtrl.SetItemText(row, 11, g->getPathDescription().c_str()); |
| | | m_listCtrl.SetItemText(row, 12, g->getParamsDescription().c_str()); |
| | | rowsToRedraw.push_back(row); |
| | | |
| | | // —— 顺带刷新 buddy 子行(如果它已在可见表里)—— |
| | | if (SERVO::CGlass* b = g->getBuddy()) { |
| | | CString buddyCidCs = b->getID().c_str(); |
| | | #ifdef _UNICODE |
| | | std::string bid = CT2A(buddyCidCs); |
| | | #else |
| | | std::string bid = buddyCidCs.GetString(); |
| | | #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; |
| | | |
| | | CString newBuddyCid = b->getID().c_str(); |
| | | #ifdef _UNICODE |
| | | std::string newBid = CT2A(newBuddyCid); |
| | | #else |
| | | std::string newBid = newBuddyCid.GetString(); |
| | | #endif |
| | | |
| | | // 现有容器下的“第一个子 classId”(如果有的话) |
| | | CString oldChildCid; |
| | | if (!container->children.empty() && container->children[0] && container->children[0]->cols.size() > 4) |
| | | oldChildCid = container->children[0]->cols[4]; |
| | | |
| | | bool buddyExistsAnywhere = (wipRowById.find(newBid) != wipRowById.end()); |
| | | bool hasChildAlready = NodeHasChildWithClassId(container, newBuddyCid); |
| | | |
| | | // —— 关键:关系是否发生变化?(oldChildCid 与 newBuddyCid 不同) |
| | | bool relationChanged = |
| | | (!oldChildCid.IsEmpty() && newBuddyCid.IsEmpty()) || // 之前有子,现在没 buddy |
| | | (oldChildCid.IsEmpty() && !newBuddyCid.IsEmpty()) || // 之前没子,现在有 buddy |
| | | (!oldChildCid.IsEmpty() && !newBuddyCid.IsEmpty() && oldChildCid.CompareNoCase(newBuddyCid) != 0); // 改 buddy |
| | | |
| | | if (relationChanged) { |
| | | // 关系变更走“结构重建”,避免重复或反向挂载 |
| | | needRebuildAllForNewRoot = true; |
| | | } |
| | | else { |
| | | // 关系未变:若 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, RGB(0, 0, 0), RGB(201, 228, 180)); |
| | | } |
| | | // 若已有子:顺带把子行文本刷新一下(比如 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; // 底层数据 |
| | | // 可见行刷新 |
| | | 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) |
| | | m_listCtrl.SetItemText(r, c, cols[c]); |
| | | rowsToRedraw.push_back(r); |
| | | break; |
| | | } |
| | | } |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | // 当前是“子节点”的情况:一律不挂子,交给重建(若父变更) |
| | | } |
| | | else { |
| | | // 没有 buddy:如果容器下现在有子,也算关系变化,触发重建 |
| | | auto itRoot = wipRootById.find(cid); |
| | | if (itRoot != wipRootById.end()) { |
| | | CExpandableListCtrl::Node* container = itRoot->second; |
| | | if (!container->children.empty()) |
| | | needRebuildAllForNewRoot = true; |
| | | } |
| | | } |
| | | } |
| | | else { |
| | | // (B) 不存在:新增根行——为保证“WIP 永远在顶部”,触发全量重建 |
| | | needRebuildAllForNewRoot = true; |
| | | } |
| | | } |
| | | |
| | | // 4) 应用 UI 更新 |
| | | if (needRebuildAllForNewRoot) { |
| | | // 用 key(ClassID)保存并恢复,避免 Node* 失效 |
| | | auto selKeys = SnapshotSelectedKeys(m_listCtrl); |
| | | auto topKey = SnapshotTopKey(m_listCtrl); |
| | | UpdatePageData(); // 全量重建(WIP 顶部) |
| | | RestoreSelectionByKeys(m_listCtrl, selKeys); |
| | | RestoreTopByKey(m_listCtrl, topKey); |
| | | } |
| | | else if (needRebuildChildren) { |
| | | auto selKeys = SnapshotSelectedKeys(m_listCtrl); |
| | | auto topKey = SnapshotTopKey(m_listCtrl); |
| | | m_listCtrl.RebuildVisible(); // 仅结构变化(加子) |
| | | RestoreSelectionByKeys(m_listCtrl, selKeys); |
| | | RestoreTopByKey(m_listCtrl, topKey); |
| | | } |
| | | else { |
| | | for (int row : rowsToRedraw) // 仅文本变化 |
| | | m_listCtrl.RedrawItems(row, row); |
| | | } |
| | | |
| | | // 5) 释放 retain |
| | | for (auto* g : tempRetain) g->release(); |
| | | } |
| | | |
| | | void CPageGlassList::InsertWipRow(SERVO::CGlass* /*pGlass*/) |
| | | { |
| | | // 不再使用 |
| | | } |
| | | |
| | | void CPageGlassList::UpdateWipRow(unsigned int /*index*/, SERVO::CGlass* /*pGlass*/) |
| | | { |
| | | // 不再使用 |
| | | } |
| | | |
| | | bool CPageGlassList::eraseGlassInVector(SERVO::CGlass* /*pGlass*/, std::vector<SERVO::CGlass*>& /*glasses*/) |
| | | { |
| | | return false; |
| | | } |
| | | |
| | | // ===== 过滤逻辑(原样保留) ===== |
| | | |
| | | // 核心:WIP 的 CGlass 是否命中当前 Filters |
| | | // useEndTime=true 时用 tEnd 判时间(比如“完成列表”用 t_end),默认按 tStart。 |
| | | bool CPageGlassList::GlassMatchesFilters(const SERVO::CGlass& g, |
| | | const GlassLogDb::Filters& f, |
| | | bool useEndTime/* = false*/) |
| | | 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; |
| | | // 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; |
| | | } |
| | | // 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<std::chrono::system_clock::time_point> 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; |
| | | } |
| | | // 3) 时间(与 DB 保持一致:默认按 t_start 过滤;需要可切到 t_end) |
| | | if (f.tStartFrom || f.tStartTo) { |
| | | std::optional<std::chrono::system_clock::time_point> 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; |
| | | 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, 1, std::to_string(pGlass->getCassetteSequenceNo()).c_str()); |
| | | m_listCtrl.SetItemText(index, 2, std::to_string(pGlass->getJobSequenceNo()).c_str()); |
| | | m_listCtrl.SetItemText(index, 3, pGlass->getID().c_str()); |
| | | m_listCtrl.SetItemText(index, 4, SERVO::CServoUtilsTool::getMaterialsTypeText(pGlass->getType()).c_str()); |
| | | m_listCtrl.SetItemText(index, 5, SERVO::CServoUtilsTool::getGlassStateText(pGlass->state()).c_str()); |
| | | m_listCtrl.SetItemText(index, 6, CToolUnits::TimePointToLocalString(pGlass->tStart()).c_str()); |
| | | m_listCtrl.SetItemText(index, 7, CToolUnits::TimePointToLocalString(pGlass->tEnd()).c_str()); |
| | | m_listCtrl.SetItemText(index, 8, pGlass->getBuddyId().c_str()); |
| | | m_listCtrl.SetItemText(index, 9, SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)pGlass->getAOIInspResult()).c_str()); |
| | | m_listCtrl.SetItemText(index, 10, pGlass->getPathDescription().c_str()); |
| | | m_listCtrl.SetItemText(index, 11, pGlass->getParamsDescription().c_str()); |
| | | } |
| | | |
| | | bool CPageGlassList::eraseGlassInVector(SERVO::CGlass* pGlass, std::vector<SERVO::CGlass*>& glasses) |
| | | { |
| | | auto iter = std::find(glasses.begin(), glasses.end(), pGlass); |
| | | if (iter != glasses.end()) { |
| | | glasses.erase(iter); |
| | | return true; |
| | | } |
| | | |
| | | return false; |
| | | } |