// SGMeasurementDlg.cpp: 实现文件 // #include "pch.h" #include "framework.h" #include "SGMeasurement.h" #include "SGMeasurementDlg.h" #include "afxdialogex.h" #include "SGIF.h" #include "Logger.h" #ifdef _DEBUG #define new DEBUG_NEW #endif #define DeviceID 0 // 托盘图标 ID 与消息宏 #define ID_TRAY_RESTORE 2001 // 恢复窗口 #define ID_TRAY_EXIT 2002 // 退出程序 #define WM_TRAY_ICON_NOTIFY (WM_USER + 1000) // 托盘图标回调消息 ID // 托盘提示文本宏 #define TRAY_ICON_TOOLTIP_TEXT _T("SGMeasurement") // 日志颜色宏定义 #define LOG_COLOR_NORMAL RGB(0, 0, 0) // 普通:黑色 #define LOG_COLOR_SUCCESS RGB(0, 128, 0) // 成功:绿色 #define LOG_COLOR_ERROR RGB(255, 0, 0) // 错误:红色 #define LOG_COLOR_WARNING RGB(255, 165, 0) // 警告:橙色 #define LOG_COLOR_TIME RGB(0, 0, 255) // 时间戳:蓝色 // 定时器相关宏定义 #define TIMER_INTERVAL_MS 500 #define TIMER_ID_OUTPUT_UPDATE 1 // 计时宏定义 #define MEASURE_FUNC_START() \ clock_t __startClock = clock(); #define MEASURE_FUNC_END() \ do { \ clock_t __endClock = clock(); \ double __elapsedMs = 1000.0 * (__endClock - __startClock) / CLOCKS_PER_SEC; \ CString __strElapsed; \ __strElapsed.Format(_T("%s 执行耗时:%.1f ms"), _T(__FUNCTION__), __elapsedMs); \ AppendLogLineRichStyled(__strElapsed, LOG_COLOR_SUCCESS); \ } while (0) class CAboutDlg : public CDialogEx { public: CAboutDlg(); // 对话框数据 #ifdef AFX_DESIGN_TIME enum { IDD = IDD_ABOUTBOX }; #endif protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持 // 实现 protected: DECLARE_MESSAGE_MAP() }; CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX) { } void CAboutDlg::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); } BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx) END_MESSAGE_MAP() CSGMeasurementDlg::CSGMeasurementDlg(CWnd* pParent /*=nullptr*/) : CDialogEx(IDD_SGMEASUREMENT_DIALOG, pParent) , m_bConnected(false) , m_bSaving(false) , m_dOutValues{ 0.0, 0.0, 0.0, 0.0 } , m_nUseTrigger(0) , m_nSavePointCount(100000) , m_fJumpThreshold(0.2f) , m_nJumpWindow(3) , m_nValleyMargin(0) , m_nMinGlass1Count(10) , m_nFixedCount(5) , m_fMaxDelta(0.05f) , m_nTrayIconID(0) , m_bTrayIconCreated(FALSE) , m_bExitingFromTray(FALSE) { m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); } void CSGMeasurementDlg::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); DDX_Text(pDX, IDC_EDIT_OUT1, m_dOutValues[0]); DDX_Text(pDX, IDC_EDIT_OUT2, m_dOutValues[1]); DDX_Text(pDX, IDC_EDIT_OUT3, m_dOutValues[2]); DDX_Text(pDX, IDC_EDIT_OUT4, m_dOutValues[3]); DDX_Control(pDX, IDC_RICHEDIT_LOG, m_editLog); DDX_Check(pDX, IDC_CHECK_USE_TRIGGER, m_nUseTrigger); DDX_Control(pDX, IDC_COMBO_OUTPUT_PORT, m_comboOutputPort); DDX_Text(pDX, IDC_EDIT_STORE_COUNT, m_nSavePointCount); DDX_Text(pDX, IDC_EDIT_JUMP_WINDOW, m_nJumpWindow); DDX_Text(pDX, IDC_EDIT_VALLEY_MARGIN, m_nValleyMargin); DDX_Text(pDX, IDC_EDIT_MIN_GLASS_COUNT, m_nMinGlass1Count); DDX_Text(pDX, IDC_EDIT_FIXED_COUNT, m_nFixedCount); DDX_Text(pDX, IDC_EDIT_JUMP_THRESHOLD, m_fJumpThreshold); DDX_Text(pDX, IDC_EDIT_MAX_DELTA, m_fMaxDelta); } void CSGMeasurementDlg::ExitApplication() { // 断开设备连接(如已连接) if (m_bConnected) { DisconnectFromDevice(); } // 删除托盘图标(如已添加) if (m_bTrayIconCreated) { Shell_NotifyIcon(NIM_DELETE, &m_trayIconData); m_bTrayIconCreated = FALSE; } m_plcListener.Stop(); DestroyWindow(); CDialogEx::OnClose(); } void CSGMeasurementDlg::UpdateControlStatus(BOOL bConnected, BOOL bStoring/* = FALSE*/) { // 连接按钮和断开按钮状态互斥 GetDlgItem(IDC_BUTTON_CONNECT)->EnableWindow(!bConnected); GetDlgItem(IDC_BUTTON_DISCONNECT)->EnableWindow(bConnected); // IP地址输入只允许在未连接时修改 GetDlgItem(IDC_IPADDRESS)->EnableWindow(!bConnected); // 存储配置控件仅在已连接且未开始存储时允许操作 BOOL bCanConfigure = bConnected && !bStoring; /*GetDlgItem(IDC_CHECK_USE_TRIGGER)->EnableWindow(bCanConfigure);*/ GetDlgItem(IDC_COMBO_OUTPUT_PORT)->EnableWindow(bCanConfigure); GetDlgItem(IDC_EDIT_STORE_COUNT)->EnableWindow(bCanConfigure); GetDlgItem(IDC_EDIT_JUMP_THRESHOLD)->EnableWindow(bCanConfigure); GetDlgItem(IDC_EDIT_JUMP_WINDOW)->EnableWindow(bCanConfigure); GetDlgItem(IDC_EDIT_VALLEY_MARGIN)->EnableWindow(bCanConfigure); GetDlgItem(IDC_EDIT_MIN_GLASS_COUNT)->EnableWindow(bCanConfigure); GetDlgItem(IDC_EDIT_FIXED_COUNT)->EnableWindow(bCanConfigure); GetDlgItem(IDC_EDIT_MAX_DELTA)->EnableWindow(bCanConfigure); // 存储相关按钮 GetDlgItem(IDC_BUTTON_RECEIVE_FROM_CTRL)->EnableWindow(bCanConfigure); GetDlgItem(IDC_BUTTON_SEND_TO_CTRL)->EnableWindow(bCanConfigure); GetDlgItem(IDC_BUTTON_CLEAR_STORE)->EnableWindow(bCanConfigure); GetDlgItem(IDC_BUTTON_START_STORE)->EnableWindow(bCanConfigure); GetDlgItem(IDC_BUTTON_STOP_STORE)->EnableWindow(bStoring); } void CSGMeasurementDlg::AppendLogLineBatchBegin() { m_editLog.SetRedraw(FALSE); m_editLog.SetEventMask(0); // 防止触发不必要的通知 } void CSGMeasurementDlg::AppendLogLineBatchEnd() { m_editLog.SetRedraw(TRUE); m_editLog.Invalidate(); // 强制重绘 m_editLog.SetEventMask(ENM_CHANGE | ENM_SELCHANGE); } void CSGMeasurementDlg::TrimRichEditLineLimit(int nMaxLines) { int nLineCount = m_editLog.GetLineCount(); if (nLineCount < nMaxLines) { return; } // 获取多余行的字符数范围 int charIndex = m_editLog.LineIndex(nMaxLines); m_editLog.SetSel(0, charIndex); // 选中多余内容 m_editLog.ReplaceSel(_T("")); // 删除 } void CSGMeasurementDlg::AppendLogLineRichStyled(const CString& strContent, COLORREF color /*= RGB(0, 0, 0)*/) { if (!::IsWindow(GetSafeHwnd()) || !::IsWindow(m_editLog.GetSafeHwnd())) { return; } // 时间戳 CString strTimestamp; CTime now = CTime::GetCurrentTime(); strTimestamp.Format(_T("[%02d:%02d:%02d] "), now.GetHour(), now.GetMinute(), now.GetSecond()); // 插入点移到最后(也可以设为 0 表示顶部) m_editLog.SetSel(-1, -1); // 插入时间(蓝色) CHARFORMAT2 cfTime = {}; cfTime.cbSize = sizeof(cfTime); cfTime.dwMask = CFM_COLOR; cfTime.crTextColor = LOG_COLOR_TIME; m_editLog.SetSelectionCharFormat(cfTime); m_editLog.ReplaceSel(strTimestamp); // 生成日志级别标签 CString strLevel; if (color == LOG_COLOR_WARNING) { strLevel = _T("[警告]"); } else if (color == LOG_COLOR_ERROR) { strLevel = _T("[错误]"); } else if (color == LOG_COLOR_NORMAL) { strLevel = _T("[信息]"); } else if (color == LOG_COLOR_SUCCESS) { strLevel = _T("[成功]"); } else { strLevel = _T("[未知]"); } // 插入日志正文(传入颜色) CHARFORMAT2 cfMsg = {}; cfMsg.cbSize = sizeof(cfMsg); cfMsg.dwMask = CFM_COLOR; cfMsg.crTextColor = color; m_editLog.SetSelectionCharFormat(cfMsg); m_editLog.ReplaceSel(strLevel + strContent + _T("\r\n")); // 限制最大行数 TrimRichEditLineLimit(100); // 拼接完整日志行 CString strFullLogLine; strFullLogLine.Format(_T("%s %s"), strLevel, strContent); // 写入日志文件 LOG_LINE(strFullLogLine); } void CSGMeasurementDlg::HighlightAllMatches(const CString& strSearch, COLORREF clrHighlight/* = RGB(255, 165, 0)*/) { if (strSearch.IsEmpty()) { return; } long nStart = 0; long nEnd = m_editLog.GetTextLength(); FINDTEXTEX ft = { 0 }; ft.chrg.cpMin = 0; ft.chrg.cpMax = nEnd; ft.lpstrText = strSearch.GetString(); // 高亮前不清除全文颜色,避免历史多色混淆 while (m_editLog.FindText(FR_DOWN, &ft) != -1) { m_editLog.SetSel(ft.chrgText.cpMin, ft.chrgText.cpMax); CHARFORMAT2 cf = {}; cf.cbSize = sizeof(cf); cf.dwMask = CFM_COLOR; cf.crTextColor = clrHighlight; m_editLog.SetSelectionCharFormat(cf); // 下次搜索从后面开始 ft.chrg.cpMin = ft.chrgText.cpMax; } m_editLog.SetSel(-1, 0); } void CSGMeasurementDlg::PrintSampleData(int nOutNo, const std::vector& vecBuffer) { int nReceived = static_cast(vecBuffer.size()); if (vecBuffer.empty() || nReceived < 0) { CString strError; strError.Format(_T("OUT%d: 接收数据为空或无效,无法打印。"), nOutNo); AppendLogLineRichStyled(strError, LOG_COLOR_WARNING); return; } CString strLine; for (int i = 0; i < nReceived; ++i) { CString strOne; strOne.Format(_T("%10.3f "), vecBuffer[i]); strLine += strOne; // 每 7 个值输出一行 if ((i + 1) % 7 == 0 || i == nReceived - 1) { AppendLogLineRichStyled(strLine, LOG_COLOR_NORMAL); strLine.Empty(); } } } bool CSGMeasurementDlg::ConnectToDevice() { if (m_bConnected) { AppendLogLineRichStyled(_T("设备已连接,请先断开连接。"), LOG_COLOR_WARNING); return false; } SGIF_OPENPARAM_ETHERNET stIpAddress = { 0 }; CIPAddressCtrl* pIPCtrl = (CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS); if (!::IsWindow(pIPCtrl->GetSafeHwnd())) { AppendLogLineRichStyled(_T("IP 控件无效,连接失败!"), LOG_COLOR_ERROR); return false; } pIPCtrl->GetAddress( stIpAddress.IPAddress.S_un.S_un_b.s_b1, stIpAddress.IPAddress.S_un.S_un_b.s_b2, stIpAddress.IPAddress.S_un.S_un_b.s_b3, stIpAddress.IPAddress.S_un.S_un_b.s_b4 ); RC nRet = SGIF_OpenDeviceETHER(DeviceID, &stIpAddress); if (nRet == RC_OK) { AppendLogLineRichStyled(_T("打开连接成功!"), LOG_COLOR_SUCCESS); m_bConnected = TRUE; UpdateControlStatus(TRUE, FALSE); SetTimer(TIMER_ID_OUTPUT_UPDATE, TIMER_INTERVAL_MS, nullptr); return true; } else { CString strError; strError.Format(_T("打开连接失败,错误码:%#X"), nRet); AppendLogLineRichStyled(strError, LOG_COLOR_ERROR); m_bConnected = FALSE; UpdateControlStatus(FALSE, FALSE); return false; } } bool CSGMeasurementDlg::DisconnectFromDevice() { if (!m_bConnected) { AppendLogLineRichStyled(_T("设备未连接,无需断开。"), LOG_COLOR_WARNING); return false; } // 停止定时器,避免在断开后仍尝试更新输出数据 KillTimer(TIMER_ID_OUTPUT_UPDATE); // 先停止数据存储(如果正在进行) RC nRet; if (m_bConnected && m_bSaving) { nRet = SGIF_DataStorageStop(DeviceID); if (nRet == RC_OK) { m_bSaving = FALSE; AppendLogLineRichStyled(_T("数据存储已停止。"), LOG_COLOR_SUCCESS); } else { CString strError; strError.Format(_T("停止数据存储失败,错误码:%#X"), nRet); AppendLogLineRichStyled(strError, LOG_COLOR_ERROR); } } // 断开设备连接 nRet = SGIF_CloseDevice(DeviceID); if (nRet == RC_OK) { AppendLogLineRichStyled(_T("断开连接成功!"), LOG_COLOR_SUCCESS); m_bConnected = FALSE; UpdateControlStatus(FALSE, FALSE); return true; } else { CString strError; strError.Format(_T("断开连接失败,错误码:%#X"), nRet); AppendLogLineRichStyled(strError, LOG_COLOR_ERROR); AppendLogLineRichStyled(_T("保持当前状态,断开失败。"), LOG_COLOR_ERROR); UpdateControlStatus(m_bConnected, m_bSaving); return false; } } void CSGMeasurementDlg::CleanInvalidValuesInPlace(int nOutNo, std::vector& vecData, float fInvalid/* = -999.0f*/) { MEASURE_FUNC_START(); // 找到第一个有效值 auto itStart = std::find_if(vecData.begin(), vecData.end(), [=](float v) { return v > fInvalid; }); // 找到最后一个有效值 auto itEnd = std::find_if(vecData.rbegin(), vecData.rend(), [=](float v) { return v > fInvalid; }).base(); // 检查是否有有效数据 if (itStart < itEnd) { // 先裁边 std::vector vecTrimmed(itStart, itEnd); // 再移除中间的无效值 auto itNewEnd = std::remove_if(vecTrimmed.begin(), vecTrimmed.end(), [=](float v) { return v == fInvalid; }); vecTrimmed.erase(itNewEnd, vecTrimmed.end()); vecData = std::move(vecTrimmed); CString strLog; strLog.Format(_T("OUT%d: 已裁剪无效边界数据,剩余有效数据 %d 个。"), nOutNo, static_cast(vecData.size())); AppendLogLineRichStyled(strLog, LOG_COLOR_NORMAL); if (vecData.size() < 20) { AppendLogLineRichStyled(_T("警告:有效数据量过少,可能影响后续处理。"), LOG_COLOR_WARNING); } else { CString strInfo; strInfo.Format(_T("OUT%d: 有效数据范围 [%f, %f]"), nOutNo, *std::min_element(vecData.begin(), vecData.end()), *std::max_element(vecData.begin(), vecData.end())); AppendLogLineRichStyled(strInfo, LOG_COLOR_NORMAL); } PrintSampleData(nOutNo, vecData); } else { vecData.clear(); } MEASURE_FUNC_END(); } bool CSGMeasurementDlg::SplitGlassSegments(int nOutNo, const std::vector& vecData, std::vector& vecGlass1, std::vector& vecGlass2, float fJumpThreshold /*= 0.2f*/, int nWindow /*= 3*/, int nValleyMargin /*= 0*/, int nMinGlassCount /*= 10*/) { MEASURE_FUNC_START(); CString strLog; const int nTotal = static_cast(vecData.size()); if (nTotal < 2 * nWindow + 1 + nMinGlassCount) { strLog.Format(_T("OUT%d: 数据量不足,至少需要 %d 个点才能切割。"), nOutNo, 2 * nWindow + 1 + nMinGlassCount); AppendLogLineRichStyled(strLog, LOG_COLOR_WARNING); return false; } // 从中间向两边查找 valley 候选点 int nMid = nTotal / 2; int nValleyLeft = -1, nValleyRight = -1; // 向左查找 valley for (int i = nMid; i >= nWindow; --i) { float fDelta = std::fabs(vecData[i] - vecData[i - nWindow]); if (fDelta > fJumpThreshold && vecData[i] < vecData[i - 1] && vecData[i] < vecData[i + 1]) { nValleyLeft = i; break; } } // 向右查找 valley for (int i = nMid; i <= nTotal - nWindow - 2; ++i) { float fDelta = std::fabs(vecData[i + nWindow] - vecData[i]); if (fDelta > fJumpThreshold && vecData[i] < vecData[i - 1] && vecData[i] < vecData[i + 1]) { nValleyRight = i; break; } } // 选定 valley 点 int nValleyIdx = -1; if (nValleyLeft > 0 && nValleyRight > 0) { nValleyIdx = (vecData[nValleyLeft] < vecData[nValleyRight]) ? nValleyLeft : nValleyRight; } else if (nValleyLeft > 0) { nValleyIdx = nValleyLeft; } else if (nValleyRight > 0) { nValleyIdx = nValleyRight; } // fallback: valley 未找到,使用中间切割法 if (nValleyIdx < 0) { AppendLogLineRichStyled(_T("未找到 valley 跳变点,使用中间位置切割。"), LOG_COLOR_WARNING); nValleyIdx = nMid; } // 应用切割位置,限制边界 int nCutPos = max(1, min(nTotal - 1, nValleyIdx + nValleyMargin)); vecGlass1.assign(vecData.begin(), vecData.begin() + nCutPos); vecGlass2.assign(vecData.begin() + nCutPos, vecData.end()); int nGlass1Count = static_cast(vecGlass1.size()); int nGlass2Count = static_cast(vecGlass2.size()); // 日志输出 strLog.Format(_T("OUT%d: 切割成功,第一片玻璃 %d 点,第二片玻璃 %d 点。"), nOutNo, nGlass1Count, nGlass2Count); AppendLogLineRichStyled(strLog, LOG_COLOR_SUCCESS); if (nGlass1Count < nMinGlassCount) { strLog.Format(_T("OUT%d: 第一片玻璃数据量少于 %d 点,可能影响计算。"), nOutNo, nMinGlassCount); AppendLogLineRichStyled(strLog, LOG_COLOR_WARNING); } if (nGlass2Count < nMinGlassCount) { strLog.Format(_T("OUT%d: 第二片玻璃数据量少于 %d 点,可能影响计算。"), nOutNo, nMinGlassCount); AppendLogLineRichStyled(strLog, LOG_COLOR_WARNING); } AppendLogLineRichStyled(_T("第一片玻璃数据:"), LOG_COLOR_NORMAL); PrintSampleData(nOutNo, vecGlass1); AppendLogLineRichStyled(_T("第二片玻璃数据:"), LOG_COLOR_NORMAL); PrintSampleData(nOutNo, vecGlass2); MEASURE_FUNC_END(); return true; } bool CSGMeasurementDlg::FilterDominantGroup(int nOutNo, const std::vector& vecInput, std::vector& vecOutput) { MEASURE_FUNC_START(); if (vecInput.empty()) { AppendLogLineRichStyled(_T("输入数据为空,无法进行分组筛选。"), LOG_COLOR_WARNING); return false; } // 分组:map> std::map> mapGroup; for (float fVal : vecInput) { int nKey = static_cast(fVal); mapGroup[nKey].push_back(fVal); } // 找出数量最多的那组 size_t nMaxCount = 0; auto itMaxGroup = mapGroup.begin(); for (auto it = mapGroup.begin(); it != mapGroup.end(); ++it) { if (it->second.size() > nMaxCount) { nMaxCount = it->second.size(); itMaxGroup = it; } } if (nMaxCount == 0) { AppendLogLineRichStyled(_T("所有数据都被过滤或为空,无法筛选出主要分组。"), LOG_COLOR_WARNING); return false; } vecOutput = itMaxGroup->second; CString strLog; strLog.Format(_T("成功从 %d 个组中筛选出主要分组:值约为 %d,共 %zu 个点。"), static_cast(mapGroup.size()), itMaxGroup->first, nMaxCount); AppendLogLineRichStyled(strLog, LOG_COLOR_SUCCESS); // 打印筛选后的数据 AppendLogLineRichStyled(_T("筛选后的主要分组数据:"), LOG_COLOR_NORMAL); PrintSampleData(nOutNo, vecOutput); MEASURE_FUNC_END(); return true; } bool CSGMeasurementDlg::ExtractStableRegionFixed(int nOutNo, const std::vector& vecIn, std::vector& vecOut, int nFixedCount/* = 5*/, float fMaxDelta/* = 0.04f*/) { MEASURE_FUNC_START(); const int n = static_cast(vecIn.size()); if (n < nFixedCount) { CString strError; strError.Format(_T("OUT%d: 数据量不足,至少需要 %d 个点才能提取稳定区。"), nOutNo, nFixedCount); return false; } int nBestStart = -1; float fBestRange = FLT_MAX; for (int i = 0; i <= n - nFixedCount; ++i) { auto first = vecIn.begin() + i; auto last = first + nFixedCount; float fMinVal = *std::min_element(first, last); float fMaxVal = *std::max_element(first, last); float fRange = fMaxVal - fMinVal; if (fRange <= fMaxDelta && fRange < fBestRange) { fBestRange = fRange; nBestStart = i; } } if (nBestStart < 0) { return false; } vecOut.assign(vecIn.begin() + nBestStart, vecIn.begin() + nBestStart + nFixedCount); CString strLog; strLog.Format(_T("OUT%d: 提取稳定区成功,起始点索引 %d,范围 %.3f。"), nOutNo, nBestStart, fBestRange); AppendLogLineRichStyled(strLog, LOG_COLOR_SUCCESS); if (vecOut.size() < nFixedCount) { CString strWarning; strWarning.Format(_T("OUT%d: 提取的稳定区数据量不足,可能影响后续处理。"), nOutNo); AppendLogLineRichStyled(strWarning, LOG_COLOR_WARNING); } // 打印提取的稳定区数据 AppendLogLineRichStyled(_T("提取的稳定区数据:"), LOG_COLOR_NORMAL); PrintSampleData(nOutNo, vecOut); MEASURE_FUNC_END(); return true; } bool CSGMeasurementDlg::CalcGlassOffset(const std::vector& vecGlass1, const std::vector& vecGlass2, float& fAvg1, float& fAvg2, float& fOffset) { MEASURE_FUNC_START(); if (vecGlass1.empty() || vecGlass2.empty()) { AppendLogLineRichStyled(_T("稳定区数据为空,无法计算平均值和偏移。"), LOG_COLOR_WARNING); return false; } auto CalcAverage = [](const std::vector& data) -> float { float sum = std::accumulate(data.begin(), data.end(), 0.0f); return sum / static_cast(data.size()); }; fAvg1 = CalcAverage(vecGlass1); fAvg2 = CalcAverage(vecGlass2); fOffset = fAvg2 - fAvg1; // 第二片 - 第一片 //fOffset = std::fabs(fAvg2 - fAvg1); // 第二片 - 第一片 CString strLog; strLog.Format(_T("第一片玻璃平均值: %.3f,第二片玻璃平均值: %.3f,偏移量: %.3f"), fAvg1, fAvg2, fOffset); AppendLogLineRichStyled(strLog, LOG_COLOR_SUCCESS); MEASURE_FUNC_END(); return true; } bool CSGMeasurementDlg::InitDataStorage() { if (!m_bConnected) { AppendLogLineRichStyled(_T("设备未连接,请先连接设备。"), LOG_COLOR_WARNING); return false; } if (m_bSaving) { AppendLogLineRichStyled(_T("数据存储正在进行中,请先停止存储。"), LOG_COLOR_WARNING); return false; } RC nRet = SGIF_DataStorageInit(DeviceID); if (nRet == RC_OK) { AppendLogLineRichStyled(_T("数据存储已清除。"), LOG_COLOR_SUCCESS); return true; } CString strError; strError.Format(_T("清除数据存储失败,错误码:%#X"), nRet); AppendLogLineRichStyled(strError, LOG_COLOR_ERROR); return false; } bool CSGMeasurementDlg::StartDataStorage() { if (m_bSaving) { AppendLogLineRichStyled(_T("数据存储已在进行中,请先停止存储。"), LOG_COLOR_WARNING); return false; } RC nRet = SGIF_DataStorageStart(DeviceID); if (nRet == RC_OK) { m_bSaving = TRUE; AppendLogLineRichStyled(_T("数据存储已开始。"), LOG_COLOR_SUCCESS); return true; } CString strError; strError.Format(_T("开始数据存储失败,错误码:%#X"), nRet); AppendLogLineRichStyled(strError, LOG_COLOR_ERROR); return false; } bool CSGMeasurementDlg::StopDataStorage() { if (!m_bSaving) { AppendLogLineRichStyled(_T("数据存储未在进行中,请先开始存储。"), LOG_COLOR_WARNING); return false; } RC nRet = SGIF_DataStorageStop(DeviceID); if (nRet == RC_OK) { m_bSaving = FALSE; AppendLogLineRichStyled(_T("数据存储已停止。"), LOG_COLOR_SUCCESS); return true; } CString strError; strError.Format(_T("停止数据存储失败,错误码:%#X"), nRet); AppendLogLineRichStyled(strError, LOG_COLOR_ERROR); return false; } float CSGMeasurementDlg::AnalyzeStoredData(int nOutNo) { MEASURE_FUNC_START(); UpdateData(TRUE); if (m_nUseTrigger) { UpdateControlStatus(m_bConnected, m_bSaving); AfxMessageBox(_T("当前是硬触发模式,请检查触发器状态。"), MB_ICONINFORMATION); return 0xFF; } if (!m_bConnected) { AppendLogLineRichStyled(_T("设备未连接,请先连接设备。"), LOG_COLOR_WARNING); return 0xFF; } if (m_bSaving) { AppendLogLineRichStyled(_T("数据存储正在进行中,请先停止存储。"), LOG_COLOR_WARNING); return 0xFF; } if (nOutNo < 1 || nOutNo > 4) { AppendLogLineRichStyled(_T("输出端口编号无效,必须在 1 到 4 之间。"), LOG_COLOR_ERROR); return 0xFF; } if (m_nSavePointCount < 0) { AppendLogLineRichStyled(_T("数据点数必须大于 0。"), LOG_COLOR_ERROR); return 0xFF; } std::vector vecBuffer(m_nSavePointCount, 0.0f); int nReceived = 0; RC nRet = SGIF_DataStorageGetData(DeviceID, nOutNo, m_nSavePointCount, vecBuffer.data(), &nReceived); if (nRet != RC_OK) { CString strError; strError.Format(_T("读取 OUT%d 数据失败,错误码:%#X"), nOutNo, nRet); AppendLogLineRichStyled(strError, LOG_COLOR_ERROR); return 0xFF; } vecBuffer.resize(nReceived); CleanInvalidValuesInPlace(nOutNo, vecBuffer); std::vector vecGlass1, vecGlass2; if (!SplitGlassSegments(nOutNo, vecBuffer, vecGlass1, vecGlass2, m_fJumpThreshold, m_nJumpWindow, m_nValleyMargin, m_nMinGlass1Count)) { AppendLogLineRichStyled(_T("未能识别出两片玻璃的数据。"), LOG_COLOR_WARNING); return 0xFF; } std::vector vecGlass1Filtered, vecGlass2Filtered; bool bGlass1Filtered = FilterDominantGroup(nOutNo, vecGlass1, vecGlass1Filtered); bool bGlass2Filtered = FilterDominantGroup(nOutNo, vecGlass2, vecGlass2Filtered); if (!bGlass1Filtered) { AppendLogLineRichStyled(_T("Glass1 分段中未能识别出主数据组,使用原始数据。"), LOG_COLOR_WARNING); vecGlass1Filtered = vecGlass1; } else { AppendLogLineRichStyled(_T("Glass1 主数据组已提取。"), LOG_COLOR_SUCCESS); } if (!bGlass2Filtered) { AppendLogLineRichStyled(_T("Glass2 分段中未能识别出主数据组,使用原始数据。"), LOG_COLOR_WARNING); vecGlass2Filtered = vecGlass2; } else { AppendLogLineRichStyled(_T("Glass2 主数据组已提取。"), LOG_COLOR_SUCCESS); } std::vector vecStable1, vecStable2; bool bStable1 = ExtractStableRegionFixed(nOutNo, vecGlass1Filtered, vecStable1, m_nFixedCount, m_fMaxDelta); bool bStable2 = ExtractStableRegionFixed(nOutNo, vecGlass2Filtered, vecStable2, m_nFixedCount, m_fMaxDelta); float fAvg1 = 0.0f, fAvg2 = 0.0f, fOffset = 0.0f; if (bStable1 && bStable2) { AppendLogLineRichStyled(_T("成功提取到两片玻璃的稳定区数据。"), LOG_COLOR_SUCCESS); CalcGlassOffset(vecStable1, vecStable2, fAvg1, fAvg2, fOffset); } else { AppendLogLineRichStyled(_T("未能提取到稳定区数据,尝试使用原始分段数据计算偏移。"), LOG_COLOR_WARNING); CalcGlassOffset(vecGlass1, vecGlass2, fAvg1, fAvg2, fOffset); } MEASURE_FUNC_END(); return fOffset; } BEGIN_MESSAGE_MAP(CSGMeasurementDlg, CDialogEx) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_WM_TIMER() ON_WM_MEASUREITEM() ON_WM_DRAWITEM() ON_WM_CLOSE() ON_MESSAGE(WM_TRAY_ICON_NOTIFY, &CSGMeasurementDlg::OnTrayIconClick) ON_COMMAND(ID_TRAY_RESTORE, &CSGMeasurementDlg::OnTrayRestore) ON_COMMAND(ID_TRAY_EXIT, &CSGMeasurementDlg::OnTrayExit) ON_BN_CLICKED(IDC_BUTTON_CONNECT, &CSGMeasurementDlg::OnBnClickedButtonConnect) ON_BN_CLICKED(IDC_BUTTON_DISCONNECT, &CSGMeasurementDlg::OnBnClickedButtonDisconnect) ON_BN_CLICKED(IDC_BUTTON_RECEIVE_FROM_CTRL, &CSGMeasurementDlg::OnBnClickedButtonReceiveFromCtrl) ON_BN_CLICKED(IDC_BUTTON_SEND_TO_CTRL, &CSGMeasurementDlg::OnBnClickedButtonSendToCtrl) ON_BN_CLICKED(IDC_BUTTON_CLEAR_STORE, &CSGMeasurementDlg::OnBnClickedButtonClearStore) ON_BN_CLICKED(IDC_BUTTON_START_STORE, &CSGMeasurementDlg::OnBnClickedButtonStartStore) ON_BN_CLICKED(IDC_BUTTON_STOP_STORE, &CSGMeasurementDlg::OnBnClickedButtonStopStore) ON_BN_CLICKED(IDC_BUTTON_CLEAR_LOG, &CSGMeasurementDlg::OnBnClickedButtonClearLog) END_MESSAGE_MAP() BOOL CSGMeasurementDlg::OnInitDialog() { CDialogEx::OnInitDialog(); // 将“关于...”菜单项添加到系统菜单中。 // IDM_ABOUTBOX 必须在系统命令范围内。 ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); ASSERT(IDM_ABOUTBOX < 0xF000); CMenu* pSysMenu = GetSystemMenu(FALSE); if (pSysMenu != nullptr) { BOOL bNameValid; CString strAboutMenu; bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX); ASSERT(bNameValid); if (!strAboutMenu.IsEmpty()) { pSysMenu->AppendMenu(MF_SEPARATOR); pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu); } } // 设置此对话框的图标。 当应用程序主窗口不是对话框时,框架将自动 // 执行此操作 SetIcon(m_hIcon, TRUE); // 设置大图标 SetIcon(m_hIcon, FALSE); // 设置小图标 // TODO: 在此添加额外的初始化代码 UpdateData(FALSE); UpdateControlStatus(FALSE, FALSE); // 托盘图标初始化 m_trayIconData.cbSize = sizeof(NOTIFYICONDATA); // 设置托盘图标数据结构的大小 m_trayIconData.hWnd = m_hWnd; // 设置窗口句柄 m_trayIconData.uID = m_nTrayIconID; // 设置托盘图标 ID m_trayIconData.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; // 设置托盘图标的标志(图标、消息、提示文本) m_trayIconData.uCallbackMessage = WM_TRAY_ICON_NOTIFY; // 设置回调消息 WM_TRAY_ICON_NOTIFY m_trayIconData.hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); // 加载托盘图标 lstrcpy(m_trayIconData.szTip, TRAY_ICON_TOOLTIP_TEXT); // 设置托盘提示文本 // 添加托盘图标 Shell_NotifyIcon(NIM_ADD, &m_trayIconData); m_bTrayIconCreated = TRUE; for (int i = 0; i < 4; i++) { CString str; str.Format(_T("OUT%d"), i + 1); m_comboOutputPort.AddString(str); } m_comboOutputPort.SetCurSel(0); // 初始化IP地址控件,设置为默认IP地址 ((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS))->SetAddress(192, 168, 0, 10); // 设置 PLC 监听器的日志回调函数 m_plcListener.SetLogCallback([this](const CString& msg, int type) { if (type == -1) { AppendLogLineRichStyled(msg, LOG_COLOR_ERROR); } else if (type == 0) { AppendLogLineRichStyled(msg, LOG_COLOR_SUCCESS); } else if (type == 1) { AppendLogLineRichStyled(msg, LOG_COLOR_WARNING); } else if (type == 2) { AppendLogLineRichStyled(msg, LOG_COLOR_NORMAL); } }); // 初始化 PLC 监听器 m_plcListener.Initialize(StationIdentifier(0, 255), 200); // 设置 PLC 监听器的开始采集回调函数 m_plcListener.SetStartCallback([this]() { if (!m_bConnected) { ConnectToDevice(); } if (InitDataStorage()) { StartDataStorage(); UpdateControlStatus(m_bConnected, m_bSaving); } }); // 设置 PLC 监听器的停止采集回调函数 m_plcListener.SetStopCallback([this]() { StopDataStorage(); UpdateControlStatus(m_bConnected, m_bSaving); }); // 设置 PLC 监听器的分析回调函数 m_plcListener.SetAnalyzeCallback([this]() { if (!m_bConnected) { AppendLogLineRichStyled(_T("设备未连接,请先连接设备。"), LOG_COLOR_WARNING); return std::array{ 0xFF, 0xFF, 0xFF, 0xFF }; } std::array result; for (int i = 0; i < 4; ++i) { result[i] = AnalyzeStoredData(i + 1); // OUT1 ~ OUT4 } std::string strProductID; m_plcListener.ReadProductID(strProductID); m_resultStorage.SaveAnalyzeResult(CString(strProductID.c_str()), result); CString strLog; strLog.Format(_T("分析结果:OUT1: %.3f, OUT2: %.3f, OUT3: %.3f, OUT4: %.3f"), result[0], result[1], result[2], result[3]); return result; }); m_plcListener.Start(); // 初始化日志框 AppendLogLineRichStyled(_T("准备就绪..."), LOG_COLOR_SUCCESS); return TRUE; // 除非将焦点设置到控件,否则返回 TRUE } void CSGMeasurementDlg::OnSysCommand(UINT nID, LPARAM lParam) { if ((nID & 0xFFF0) == IDM_ABOUTBOX) { CAboutDlg dlgAbout; dlgAbout.DoModal(); } else { CDialogEx::OnSysCommand(nID, lParam); } } void CSGMeasurementDlg::OnPaint() { if (IsIconic()) { CPaintDC dc(this); SendMessage(WM_ICONERASEBKGND, reinterpret_cast(dc.GetSafeHdc()), 0); // 使图标在工作区矩形中居中 int cxIcon = GetSystemMetrics(SM_CXICON); int cyIcon = GetSystemMetrics(SM_CYICON); CRect rect; GetClientRect(&rect); int x = (rect.Width() - cxIcon + 1) / 2; int y = (rect.Height() - cyIcon + 1) / 2; // 绘制图标 dc.DrawIcon(x, y, m_hIcon); } else { CDialogEx::OnPaint(); } } //当用户拖动最小化窗口时系统调用此函数取得光标显示。 HCURSOR CSGMeasurementDlg::OnQueryDragIcon() { return static_cast(m_hIcon); } void CSGMeasurementDlg::OnTimer(UINT_PTR nIDEvent) { // TODO: 在此添加消息处理程序代码和/或调用默认值 if (nIDEvent == TIMER_ID_OUTPUT_UPDATE) { SGIF_FLOATVALUE_OUT value[4] = { 0 }; RC nRet = SGIF_GetCalcDataALL(DeviceID, value); if (nRet == RC_OK) { for (int i = 0; i < 4; ++i) { double dNew = value[i].Value; if (fabs(m_dOutValues[i] - dNew) > 1e-6) { m_dOutValues[i] = dNew; CString str; str.Format(_T("%.3f"), dNew); switch (i) { case 0: GetDlgItem(IDC_EDIT_OUT1)->SetWindowText(str); break; case 1: GetDlgItem(IDC_EDIT_OUT2)->SetWindowText(str); break; case 2: GetDlgItem(IDC_EDIT_OUT3)->SetWindowText(str); break; case 3: GetDlgItem(IDC_EDIT_OUT4)->SetWindowText(str); break; } } } } else { CString strError; strError.Format(_T("获取测量值失败,错误码:%#X"), nRet); AppendLogLineRichStyled(strError, LOG_COLOR_ERROR); // 断开连接 if (m_bConnected) { DisconnectFromDevice(); } } } CDialogEx::OnTimer(nIDEvent); } void CSGMeasurementDlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct) { if (lpMeasureItemStruct->CtlType == ODT_MENU) { lpMeasureItemStruct->itemHeight = 24; lpMeasureItemStruct->itemWidth = 140; } } void CSGMeasurementDlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) { if (lpDrawItemStruct->CtlType != ODT_MENU) { return; } CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC); CRect rc = lpDrawItemStruct->rcItem; UINT id = lpDrawItemStruct->itemID; // 背景 COLORREF bgColor = (lpDrawItemStruct->itemState & ODS_SELECTED) ? RGB(200, 220, 255) : RGB(255, 255, 255); pDC->FillSolidRect(rc, bgColor); // 图标 HICON hIcon = nullptr; if (id == ID_TRAY_RESTORE) { hIcon = AfxGetApp()->LoadIcon(IDI_ICON_RESTORE); } if (id == ID_TRAY_EXIT) { hIcon = AfxGetApp()->LoadIcon(IDI_ICON_EXIT); } if (hIcon) { DrawIconEx(pDC->GetSafeHdc(), rc.left + 4, rc.top + 4, hIcon, 16, 16, 0, NULL, DI_NORMAL); } // 文本 CString str; if (id == ID_TRAY_RESTORE) { str = _T("恢复界面"); } if (id == ID_TRAY_EXIT) { str = _T("退出程序"); } pDC->SetBkMode(TRANSPARENT); pDC->SetTextColor(RGB(0, 0, 0)); pDC->DrawText(str, CRect(rc.left + 28, rc.top, rc.right, rc.bottom), DT_SINGLELINE | DT_VCENTER | DT_LEFT); } void CSGMeasurementDlg::OnClose() { // TODO: 在此添加消息处理程序代码和/或调用默认值 if (m_bExitingFromTray) { // 从托盘退出流程 ExitApplication(); } else { // 正常关闭按钮 int nResult = AfxMessageBox(_T("是否最小化到托盘?"), MB_YESNO | MB_ICONQUESTION); if (nResult == IDYES) { ShowWindow(SW_HIDE); } else { ExitApplication(); } } } LRESULT CSGMeasurementDlg::OnTrayIconClick(WPARAM wParam, LPARAM lParam) { if (wParam == m_nTrayIconID) { if (LOWORD(lParam) == WM_LBUTTONUP) { // 左键点击恢复窗口 ShowWindow(SW_SHOW); SetForegroundWindow(); } else if (LOWORD(lParam) == WM_RBUTTONUP) { // 右键点击弹出菜单 CMenu menu; menu.CreatePopupMenu(); menu.AppendMenu(MF_OWNERDRAW, ID_TRAY_RESTORE, (LPCTSTR)ID_TRAY_RESTORE); menu.AppendMenu(MF_OWNERDRAW, ID_TRAY_EXIT, (LPCTSTR)ID_TRAY_EXIT); // 加载图标 HICON hIconRestore = (HICON)::LoadImage(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_ICON_RESTORE), IMAGE_ICON, 16, 16, LR_SHARED); HICON hIconExit = (HICON)::LoadImage(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_ICON_EXIT), IMAGE_ICON, 16, 16, LR_SHARED); // 设置图标到菜单项 MENUITEMINFO mii = { sizeof(MENUITEMINFO) }; mii.fMask = MIIM_BITMAP; // 恢复菜单项图标 mii.hbmpItem = HBMMENU_CALLBACK; menu.SetMenuItemInfo(ID_TRAY_RESTORE, &mii); // 退出菜单项图标 mii.hbmpItem = HBMMENU_CALLBACK; menu.SetMenuItemInfo(ID_TRAY_EXIT, &mii); // 获取鼠标当前位置,并显示菜单 POINT pt; GetCursorPos(&pt); SetForegroundWindow(); menu.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, this); } } return 0; } void CSGMeasurementDlg::OnTrayRestore() { ShowWindow(SW_SHOW); // 恢复窗口 SetForegroundWindow(); // 将窗口置于前端 } void CSGMeasurementDlg::OnTrayExit() { // 从托盘图标菜单选择“退出程序” if (AfxMessageBox(_T("确定要退出程序吗?"), MB_YESNO | MB_ICONQUESTION) == IDYES) { m_bExitingFromTray = TRUE; PostMessage(WM_CLOSE); } } void CSGMeasurementDlg::OnBnClickedButtonConnect() { // TODO: 在此添加控件通知处理程序代码 ConnectToDevice(); } void CSGMeasurementDlg::OnBnClickedButtonDisconnect() { // TODO: 在此添加控件通知处理程序代码 DisconnectFromDevice(); } void CSGMeasurementDlg::OnBnClickedButtonReceiveFromCtrl() { // TODO: 在此添加控件通知处理程序代码 if (!m_bConnected) { AppendLogLineRichStyled(_T("设备未连接,请先连接设备。"), LOG_COLOR_WARNING); return; } } void CSGMeasurementDlg::OnBnClickedButtonSendToCtrl() { // TODO: 在此添加控件通知处理程序代码 if (!m_bConnected) { AppendLogLineRichStyled(_T("设备未连接,请先连接设备。"), LOG_COLOR_WARNING); return; } } void CSGMeasurementDlg::OnBnClickedButtonClearStore() { // TODO: 在此添加控件通知处理程序代码 InitDataStorage(); } void CSGMeasurementDlg::OnBnClickedButtonStartStore() { // TODO: 在此添加控件通知处理程序代码 StartDataStorage(); UpdateControlStatus(m_bConnected, m_bSaving); } void CSGMeasurementDlg::OnBnClickedButtonStopStore() { // TODO: 在此添加控件通知处理程序代码 StopDataStorage(); UpdateControlStatus(m_bConnected, m_bSaving); int nSel = m_comboOutputPort.GetCurSel(); if (CB_ERR == nSel) { AppendLogLineRichStyled(_T("请选择一个有效的输出端口。"), LOG_COLOR_WARNING); return; } int nOutNo = nSel + 1; AnalyzeStoredData(nOutNo); } void CSGMeasurementDlg::OnBnClickedButtonClearLog() { // TODO: 在此添加控件通知处理程序代码 if (::IsWindow(m_editLog.GetSafeHwnd())) { m_editLog.SetWindowText(_T("")); } }