MFC/C++运用多媒体定时器实现文件录入和文件格式化显示

一、有关多媒体定时器的相关知识和函数解析:

1. 多媒体定时器:

        多媒体定时器是一种精度较高的定时器,其与won32传统的定时器有着一定程度上的区别,在工业生产中需要及时响应且要满足较高精度需求时常常会用到这种定时器,多媒体定时器不依赖于消息机制(也就是win32中的定时器,使用Settimer函数创建一个对象,通过消息WM_TIMER添加一个ontimer的消息响应函数,每过一个时间间隔时,系统就会调用这个ontimer函数中的功能),多媒体定时器则是另外开辟一个独立线程来执行定时器回调函数,相比于win32定时器响应更快,容错率更高,精度也更高。

2.多媒体定时器相关API

        本次例程只使用了timeBeginPeriod函数、timeEndPeriod函数、timeSetEvent函数、timeKillEvent函数、以及回调函数。

MMRESULT timeBeginPeriod( UINT uperiod);
//函数功能:设置定时器设备最小时间分辨率
//参数uperiod:最小时间分辨率,以毫秒为单位

MMRESULT timeEndPeriod( UINT uperiod);
//函数功能:清楚之前对定时器设备的设置
//参数uperiod:函数中指定的最小分辨率

//注意:在一个多媒体定时器的使用过程中,上面的两个函数一定要配对出现,并且两个函数中参数的值一定要相同

MMRESULT timeSetEvent(UINT uDelay, UINT uResolution, LPTIMECALLBACK IP TimeProc, DWORD_PTR dwUser, UINT fuEvent);
//函数功能:创建并初始化定时器事件,给定时器回调函数的入口地址
//参数:
//uDelay:定时器触发时间间隔,以毫秒为单位
//uResolution:定时器设备精度,以毫秒为单位,应大于或等于timeBeginPeriod中设置的值,默认为1ms,精度越高,系统在定时器上的负载就越大,通常选择适合应用程序的最大值
//LpTimeProc:定时器出发时间的回调函数的地址
//dwUser:传递给回调函数的数据
//fuEvent:定时类型,TIME_ONSHOT表示uDelay毫秒后只产生一次事件,TIME_PERIOFIC表示每隔uDelay毫秒周期性的产生事件

MMRESULT timeKillEvent(UINT uTimerID);
//函数功能:删除一个指定的定时器事件
//参数uTimeID:指向要删除的定时器事件的ID

void CALLBACK TimeProc(UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2);
//函数功能:回调函数(注意:函数名TimeProc可以自己定义,只需要保证和timeSetEvent中TimeProc位置上的参数名称保持一致即可)
//参数:
//uID:多媒体定时器的ID,ID值由timeSetEvent创建定时器事件时返回
//uMsg:保留 不使用
//dwUser:由timeSetEvent传递的用户数据
//dw1,dw2:保留 不使用

二、例程实现

1. 编辑对话框(Dialog文件)

(1)点击资源视图-->找到Dialog文件夹-->打开结尾是DAILOG的视图(如果找不到资源视图,可以点击VS上面菜单栏中的<视图>中<其他视图>中寻找)

MFC/C++运用多媒体定时器实现文件录入和文件格式化显示_第1张图片

(图 2-1)

2)删除编译器默认为我们生成的控件,删除后我们为对话框添加我们需要的控件:

        分别添加Edit Control控件,Button控件,我们只需要这两种控件就够了,如果为了美观,还可以像我一样添加一个Group Box来稍微规划一下界面。编辑完成的对话框如下图所示:

MFC/C++运用多媒体定时器实现文件录入和文件格式化显示_第2张图片

(图 2-2)

(3)为控件配置属性

        1)为各个控件配置属性可以让我们更好的使用控件和一目了然的了解每个控件的功能。控件的主要属性为:描述文字、ID、还有一些控件的行为属性和为控件添加变量。如果想要对话框做成图2-2中的样子,就必须修改控件的<描述文字>(属性界面一般会显示在屏幕的右下角,如果没找到可以右键点击需要配置属性的控件,在最下面会找到属性的选项)。

       2) 接下来我们来修改每个控件的ID,还是在属性界面,找到选项

        将<开始记录>按钮ID改为:IDC_BUTTON_START;

        将<停止记录>按钮ID改为:IDC_BUTTON_STOP;

        将<字体选择>按钮ID改为:IDC_BUTTON_CHOOSE;

        将<另存为Excel>按钮ID改为:IDC_BUTTON_SAVE;

        将格式化显示里的示例编辑框ID改为:IDC_EDIT_TEXT;

        将<另存为Excel>旁的示例编辑框ID改为:IDC_EDIT_PATH;

       3) 由于该例程需要每过1s记录数值,所以需要显示的数据行数很多,所以我们要让示例编辑框可以显示多行,并且当数据过多时可以通过滑块来查看数据,所以我们要接着配置示例编辑框。还是属性界面,我们找到<多行>,把对应的值False改为True;找到<左侧滚动条>,把对应的值True改为False;找到,把对应的值False改为True;找到,把对应的值False改为True;找到<垂直滚动>,把对应的值False改为True;这样我们的显示框的右侧就出现了滚动条,运行程序时,数据就可以多行显示并且可以滑动滑块查看更多数据了。

        4)为控件添加变量

        右击格式化显示中的示例编辑框(即最大的示例编辑框)选择<添加变量>,我们分别为其添加控件变量和值变量。

MFC/C++运用多媒体定时器实现文件录入和文件格式化显示_第3张图片MFC/C++运用多媒体定时器实现文件录入和文件格式化显示_第4张图片

                                         (图 2-3)                                                                                            (图 2-4)                       

 2. 编写TimerFileDlg.h文件

// TimerFileDlg.h: 头文件
//
//添加多媒体定时器所需要的库文件声明,防止重定义。
#pragma once
#include

#pragma comment(lib,"winmm")


// CTimerFileDlg 对话框
class CTimerFileDlg : public CDialogEx
{
// 构造
public:
	UINT wAccuracy;//自己添加的
	UINT timerID;//自己添加的定时器ID变量
	void DestroyTimer();//自己声明的销毁定时器函数
	UINT CreateTimer();//自己声明的创建定时器函数
	static void CALLBACK TimerFileProc(UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2);//定时器回调函数
	CTimerFileDlg(CWnd* pParent = nullptr);	// 标准构造函数(系统自带)

// 对话框数据
#ifdef AFX_DESIGN_TIME
	enum { IDD = IDD_TIMERFILE_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();
	DECLARE_MESSAGE_MAP()
public:

	CEdit m_EditCtrl;//添加的控件变量
	CString m_edit;//添加的控件的值变量
//--------------------双击控件时编译器自动为我们生成的消息响应函数-------------------//
	afx_msg void OnBnClickedButtonStart();
	afx_msg void OnBnClickedButtonStop();
	afx_msg void OnBnClickedButtonChoose();
	afx_msg void OnBnClickedButtonSave();
//--------------------双击控件时编译器自动为我们生成的消息响应函数-------------------//
	CStdioFile m_file;//自己定义的对文件操作的对象,CStdioFile类(全局的)
	void SaveDataToFile();//自己定义的实现文件记录和数据显示的函数


};

 3. 编写TimerFileDlg.cpp文件

 (1)多媒体定时器部分:

        要想使用多媒体定时的类和函数,必须要在TimerFileDlg.cpp文件中声明一个头文件:mmsystem.h文件。

   TimerFileDlg.cpp的头文件部分:

#include "pch.h"
#include "framework.h"
#include "TimerFile.h"
#include "TimerFileDlg.h"
#include "afxdialogex.h"
#include"mmsystem.h"
#include 

设置定时器函数CreateTimer:

UINT CTimerFileDlg::CreateTimer()
{
	timeBeginPeriod(1);//设置定时器设备的最小时间分辨率
	timerID = timeSetEvent(1000, 1, TimerFileProc, (DWORD)this, TIME_PERIODIC);//TimerFileProc(名称可以自定义,但是必须保持和回调函数名相同)
	return timerID;//返回定时器ID
}

销毁定时器函数DestroyTimer:

void CTimerFileDlg::DestroyTimer()
{
	timeKillEvent(timerID);//销毁定时器
	timeEndPeriod(1);//清除前面对定时器的设置
}

定时器回调函数TimerFileProc:

void CALLBACK CTimerFileDlg::TimerFileProc(UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2)
{
	CTimerFileDlg* pdcpackerdlg = (CTimerFileDlg*)dwUser;
	pdcpackerdlg->SaveDataToFile();

}

(2)控件消息映射函数:

        在MFC编程中,我们如果想要为某个控件写程序,只需要在资源视图编辑的对话框中双击想要为其编写程序的控件就可以直接跳转到相应的函数,而亲编译器会自动在Dlg.h文件中为我们生成相应的函数声明。

双击“开始记录”按钮,跳转到相应函数void CTimerFileDlg::OnBnClickedButtonStart:

void CTimerFileDlg::OnBnClickedButtonStart()
{
	// TODO: 在此添加控件通知处理程序代码
	char* old_locale = _strdup(setlocale(LC_CTYPE, NULL));
	setlocale(LC_CTYPE, "chs");
	CString strFilename, strFilePath, strData;//定义三个CString类的对象
	CString Title;//定义一个CString类的对象,用来在对话框中显示各类数据的名称
	if (m_edit.IsEmpty())
	{
		Title += "\r\n";
	}
	Title += "测试时间\t\t";//设置数据类型的名称
	Title += "测试数据1:\t";//设置数据类型的名称
	Title += "测试数据2:\t";//设置数据类型的名称
	CTime tm = CTime::GetCurrentTime();//获取当前时间给tm
	strFilename.Format(_T("(%d月%d日%d时%d分%d秒)"), tm.GetMonth(), tm.GetDay(), tm.GetHour(), tm.GetMinute(), tm.GetSecond());

//设置文件名	
strFilePath += "WriteFile";
	strFilePath += strFilename;
	strFilePath += ".txt";

//打开文件,CFile::modeCreate(如果没有该文件就新建一个文件),CFile::modeReadWrite(如果有文件就对文件进行读和写操作),CFile::modeNoTruncate(如果文件中有内容,那么不会清除之前的内容)
	m_file.Open(strFilePath, CFile::modeCreate | CFile::modeReadWrite | CFile::modeNoTruncate);
//将文件中数据的格式保存在strData中
	strData += _T("测试时间:\t\t\t");
	strData += _T("测试数据1:\t\t");
	strData += _T("测试数据2:\t\t\t");
	strData += "\n";//添加换行,防止数据显示在一行中
	m_EditCtrl.ReplaceSel(Title);//势力编辑框的显示函数,对显示框的控件变量操作
	m_file.Seek(0, CFile::end);//调用CStdioFile类中的函数seek,寻找定位已有文件的最后一行
	m_file.WriteString(strData);//将数据写入文件中
	CreateTimer();//开启设置定时器
	setlocale(LC_CTYPE, old_locale);
	free(old_locale);


}

双击“停止记录”按钮,跳转到相应函数void CTimerFileDlg::OnBnClickedButtonStop:

void CTimerFileDlg::OnBnClickedButtonStop()
{
	// TODO: 在此添加控件通知处理程序代码
	DestroyTimer();//销毁定时器
	m_file.Close();//关闭文件(注意,在使用m_file.Open函数后,一定要记得使用m_file.Close关闭文件,防止程序报错)
}

数据保存回调函数void CTimerFileDlg::OnBnClickedButtonStart:

void CTimerFileDlg::OnBnClickedButtonStart()
{
	// TODO: 在此添加控件通知处理程序代码
	char* old_locale = _strdup(setlocale(LC_CTYPE, NULL));        //加头文件#include 
	setlocale(LC_CTYPE, "chs");
	CString strFilename, strFilePath, strData;
	CString Title;
	if (m_edit.IsEmpty())
	{
		Title += "\r\n";
	}
	Title += "测试时间\t\t";
	Title += "测试数据1:\t";
	Title += "测试数据2:\t";
	CTime tm = CTime::GetCurrentTime();
	strFilename.Format(_T("(%d月%d日%d时%d分%d秒)"), tm.GetMonth(), tm.GetDay(), tm.GetHour(), tm.GetMinute(), tm.GetSecond());
	strFilePath += "WriteFile";
	strFilePath += strFilename;
	strFilePath += ".txt";

	m_file.Open(strFilePath, CFile::modeCreate | CFile::modeReadWrite | CFile::modeNoTruncate);
	strData += _T("测试时间:\t\t\t");
	strData += _T("测试数据1:\t\t");
	strData += _T("测试数据2:\t\t\t");

	strData += "\n";
	m_EditCtrl.ReplaceSel(Title);
	m_file.Seek(0, CFile::end);
	m_file.WriteString(strData);
	CreateTimer();
	setlocale(LC_CTYPE, old_locale);
	free(old_locale);


}

双击“字体选择”按钮,跳转到相应函数void CTimerFileDlg::OnBnClickedButtonChoose:

void CTimerFileDlg::OnBnClickedButtonChoose()
{
	// TODO: 在此添加控件通知处理程序代码

	LOGFONT lf;
	memset(&lf, 0, sizeof(LOGFONT));
	_tcscpy_s(lf.lfFaceName, LF_FACESIZE, _T("宋体"));//默认选中宋体
	CFontDialog fontDlg(&lf);//CFontDialog:字体对话框的类

	if (fontDlg.DoModal() == IDOK)//fontDlg.DoModal():打开字体对话框,返回值为IDOK时代表用户点击了确定
	{
		//设置字体
		
		CFont font;
		font.CreatePointFontIndirect(fontDlg.m_cf.lpLogFont);//与选中的字体相关联
		GetDlgItem(IDC_EDIT_TEXT)->SetFont(&font);//SetFont设置字体
	}
}

双击“另存为Excel”按钮,跳转到相应函数void CTimerFileDlg::OnBnClickedButtonSave:

void CTimerFileDlg::OnBnClickedButtonSave()
{
	// TODO: 在此添加控件通知处理程序代码

	TCHAR szFilter[] = _T("excel(*.csv)|*.csv|文本文件(*.txt)|*.txt|所有文件(*.*)|*.*||");
	CFileDialog fileDlg(false, _T("doc"), _T("func"), 0, szFilter, this);
	CString strFilePath;
	if (fileDlg.DoModal() == IDOK)
	{
		strFilePath = fileDlg.GetPathName();
		SetDlgItemText(IDC_EDIT_PATH, strFilePath);
		CStdioFile dstFile;
		CFileException ex;
		if (!dstFile.Open(strFilePath, CFile::modeReadWrite | CFile::modeCreate | CFile::modeNoTruncate, &ex))
		{
			TCHAR szError[1024];//定义一个错误数据输出缓冲区
			ex.GetErrorMessage(szError, 1024);
			MessageBox(szError, _T("另存文件"));
			return;
		}

		dstFile.SeekToEnd();//定位到文件结尾。当以追加的方式写文件使,需要先定位到文件的结尾
		if (dstFile.GetLength() == 0)//判断文件是否是新创建的文件或者是否为空文件
		{
			dstFile.WriteString(_T("Time,data1,data2\n"));
		}


		TCHAR buf[4096];//定义字节缓冲区
		int lineCount = m_EditCtrl.GetLineCount();//从控件变量中的成员函数那获取行数
		for (int i = 0; i <= lineCount; i++)
		{
			memset(buf, 0, 4096);//清空缓冲区
			int nRead = m_EditCtrl.GetLine(i, buf, 4096);//i:代表正在读取的行数,buf:文本缓冲区,
			if (nRead == 0)
			{
				dstFile.WriteString(_T("\n"));//如果读取的位置为空行,那么就换行
			}
			else
			{
				wcscat_s(buf, _T("\n"));//自动加换行
				dstFile.WriteString(buf);//将行的内容写入文件中

			}
		}
		dstFile.Close();
	}
}

三、运行结果:

数据显示(一秒记录一组数据)

MFC/C++运用多媒体定时器实现文件录入和文件格式化显示_第5张图片

 (图 3-1)

 字体选择(默认宋体)

MFC/C++运用多媒体定时器实现文件录入和文件格式化显示_第6张图片

 (图 3-2)

文件保存路径显示

MFC/C++运用多媒体定时器实现文件录入和文件格式化显示_第7张图片

 (图 3-3)

Excel格式显示

MFC/C++运用多媒体定时器实现文件录入和文件格式化显示_第8张图片

 (图 3-4)

 .TXT文本文件显示

MFC/C++运用多媒体定时器实现文件录入和文件格式化显示_第9张图片

 (图 3-5)

 四、帮助

        如果对MFC中所用的类和对象等不熟悉,或者忘记了,可以在微软官方去查询MFC中各种类的API网址如下(这里我给出了CStdioFile类的地址)进入网页你就明白怎么查找了):

 CStdioFile 类 | Microsoft Docsicon-default.png?t=L9C2https://docs.microsoft.com/zh-cn/cpp/mfc/reference/cstdiofile-class?view=msvc-160

         作为MFC的小白,又出错的地方还请大佬们指正!!!!

你可能感兴趣的:(MFC,mfc,c++)