// SGMeasurementDlg.h: 头文件 // #pragma once #include #include #include // CSGMeasurementDlg 对话框 class CSGMeasurementDlg : public CDialogEx { // 构造 public: CSGMeasurementDlg(CWnd* pParent = nullptr); // 标准构造函数 // 对话框数据 #ifdef AFX_DESIGN_TIME enum { IDD = IDD_SGMEASUREMENT_DIALOG }; #endif protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持 // 实现 protected: HICON m_hIcon; // 生成的消息映射函数 virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); afx_msg void OnTimer(UINT_PTR nIDEvent); afx_msg void OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct); afx_msg void OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct); afx_msg void OnClose(); afx_msg LRESULT OnTrayIconClick(WPARAM wParam, LPARAM lParam); afx_msg void OnTrayRestore(); afx_msg void OnTrayExit(); afx_msg void OnBnClickedButtonConnect(); afx_msg void OnBnClickedButtonDisconnect(); afx_msg void OnBnClickedButtonReceiveFromCtrl(); afx_msg void OnBnClickedButtonSendToCtrl(); afx_msg void OnBnClickedButtonClearStore(); afx_msg void OnBnClickedButtonStartStore(); afx_msg void OnBnClickedButtonStopStore(); afx_msg void OnBnClickedButtonClearLog(); DECLARE_MESSAGE_MAP() private: void ExitApplication(); /** * @brief 更新界面控件的可用状态。 * * 根据设备连接状态 `bConnected` 和存储状态 `bStoring` 动态控制界面上按钮、 * 编辑框、下拉框等控件的可用/禁用状态。 * * @param bConnected 设备当前是否已连接,TRUE 表示已连接,FALSE 表示未连接。 * @param bStoring 当前是否正在进行数据存储,TRUE 表示正在存储。 */ void UpdateControlStatus(BOOL bConnected, BOOL bStoring = FALSE); /** * @brief 启动批量日志追加,暂停自动滚动,提高性能。 */ void AppendLogLineBatchBegin(); /** * @brief 结束批量日志追加,恢复自动滚动并刷新显示。 */ void AppendLogLineBatchEnd(); /** * @brief 限制富文本框中的最大行数,避免日志过多导致性能问题。 * * @param maxLines 最大保留的行数,默认值为 100。 */ void TrimRichEditLineLimit(int maxLines = 100); /** * @brief 向日志窗口追加一条带颜色样式的日志行。 * * @param content 日志内容。 * @param color 字体颜色,默认为黑色。 */ void AppendLogLineRichStyled(const CString& content, COLORREF color = RGB(0, 0, 0)); /** * @brief 高亮日志中所有匹配指定字符串的部分。 * * @param strSearch 要匹配的字符串。 * @param clrHighlight 高亮颜色,默认为橙色(RGB(255, 165, 0))。 */ void HighlightAllMatches(const CString& strSearch, COLORREF clrHighlight = RGB(255, 165, 0)); /** * @brief 以格式化方式输出指定通道的测量数据样本到日志。 * * 每行输出固定数量(如 7)个浮点数据,带时间戳、通道编号标识,便于调试查看。 * * @param nOutNo 输出端口编号,用于标识日志来源,如 OUT1、OUT2 等。 * @param vecBuffer 测量数据缓存,将被逐行打印到日志中。 */ void PrintSampleData(int nOutNo, const std::vector& vecBuffer); /** * @brief 尝试连接到测量设备。 * * @return true 表示连接成功,false 表示连接失败。 */ bool ConnectToDevice(); /** * @brief 断开与测量设备的连接。 * * @return true 表示断开成功,false 表示断开失败。 */ bool DisconnectFromDevice(); /** * @brief 原地清除无效值(如 -999.0f)并更新数据。 * * @param nOutNo 输出通道编号,用于日志记录。 * @param vecData 输入输出数据容器,内部将被就地裁剪。 * @param fInvalid 无效值的判断阈值,默认值为 -999.0f。 */ void CleanInvalidValuesInPlace(int nOutNo, std::vector& vecData, float fInvalid = -999.0f); /** * @brief 将有效数据切割成两段玻璃数据(Glass1 与 Glass2)。 * * @param nOutNo 输出通道编号,用于日志记录。 * @param validData 输入的有效数据(应已裁剪边界)。 * @param vecGlass1 输出第一段玻璃数据。 * @param vecGlass2 输出第二段玻璃数据。 * @param fJumpThreshold 跳变阈值,用于检测数据的明显断点。 * @param nWindow 跳变判断的前后窗口宽度。 * @param nValleyMargin valley 点之后多少个点作为实际切割点。 * @param nMinGlass1Count 第一段最少有效点数限制。 * * @return true 表示切割成功,false 表示失败(例如数据不足或无明显跳变)。 */ bool SplitGlassSegments(int nOutNo, const std::vector& validData, std::vector& vecGlass1, std::vector& vecGlass2, float fJumpThreshold = 1.0f, int nWindow = 3, int nValleyMargin = 0, int nMinGlass1Count = 10); /** * @brief 从输入数据中提取一个固定长度的稳定区间。 * * @param nOutNo 输出通道编号,用于日志记录。 * @param vecIn 输入原始数据。 * @param vecOut 输出提取的稳定区域数据。 * @param nFixedCount 稳定区域的点数要求(必须固定数量)。 * @param fMaxDelta 允许的最大波动范围(最大值 - 最小值)。 * * @return true 表示成功找到稳定区间,false 表示未找到。 */ bool ExtractStableRegionFixed(int nOutNo, const std::vector& vecIn, std::vector& vecOut, int nFixedCount = 5, float fMaxDelta = 0.05f); /** * @brief 计算两片玻璃稳定区域的平均值和偏移量。 * * @param vecGlass1 第一段稳定数据。 * @param vecGlass2 第二段稳定数据。 * @param fAvg1 返回第一段的平均值。 * @param fAvg2 返回第二段的平均值。 * @param fOffset 返回两段的偏移值(绝对值差)。 * * @return true 表示计算成功,false 表示输入无效(如空数据)。 */ bool CalcGlassOffset(const std::vector& vecGlass1, const std::vector& vecGlass2, float& fAvg1, float& fAvg2, float& fOffset); bool InitDataStorage(); bool StartDataStorage(); bool StopDataStorage(); /** * @brief 分析指定端口的存储数据,并提取两段玻璃数据与稳定区,计算偏移。 * * @param nOutNo 输出端口编号(1~4) * @return float 成功返回计算出的偏移量,失败返回 -1.0f */ float AnalyzeStoredData(int nOutNo); // === 系统状态与运行数据 === /** * @brief 当前是否已连接到传感器控制器 */ bool m_bConnected; /** * @brief 当前是否正在进行数据存储 */ bool m_bSaving; /** * @brief 四个输出端口的当前计算结果(如偏移量或厚度等) */ double m_dOutValues[4]; // === 存储设置相关 === /** * @brief 是否使用硬件触发信号(0=否,1=是) */ int m_nUseTrigger; /** * @brief 每次采集的数据点数量 */ int m_nSavePointCount; /** * @brief 输出端口选择下拉框控件(用于设置采集端口) */ CComboBox m_comboOutputPort; // === 跳变检测与玻璃识别参数(SplitGlassSegments 使用) === /** * @brief 跳变阈值(例如 0.2mm),用于检测两片玻璃之间的高度跳变 */ float m_fJumpThreshold; /** * @brief 跳变检测窗口半宽度(用于差分计算) */ int m_nJumpWindow; /** * @brief valley 点右移的偏移量,用于最终确定分割点 */ int m_nValleyMargin; /** * @brief 第一片玻璃最少所需的数据点数 */ int m_nMinGlass1Count; // === 稳定区域提取参数(ExtractStableRegionFixed 使用) === /** * @brief 每段稳定区域要提取的固定数据点数 */ int m_nFixedCount; /** * @brief 最大允许波动范围(例如 0.05mm),判断是否为“稳定”区域 */ float m_fMaxDelta; // === 日志控件 === /** * @brief 富文本控件,用于输出带颜色的日志信息 */ CRichEditCtrl m_editLog; // === 托盘图标管理 === /** * @brief 托盘图标相关数据结构(NOTIFYICONDATA) */ NOTIFYICONDATA m_trayIconData; /** * @brief 托盘图标的唯一 ID */ UINT m_nTrayIconID; /** * @brief 标记托盘图标是否已成功创建 */ BOOL m_bTrayIconCreated; /** * @brief 标记程序是否通过托盘图标退出 */ BOOL m_bExitingFromTray; };