基于MFC串口编程和曲线图绘制(visual studio2008,Teechart绘图控件)的程序总结

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weixin_36994635/article/details/76382969

前言


今年刚进入公司按经理的要求为底盘测控机写了一个小小的console。这也是第一次教认真的完成整个程序的编写。程序不大,所用技术比较基础也不前卫,属于初级程序员的练手程序(知识的整理和搬运)。虽然如此,期间也由于一次选用的方案不正确而推掉重做了一次。

要是有什么写的不对的地方请留言赐教,谢谢。

介绍

以下是这次用到的主要知识内容:
1)MSCOMM串口控件编程;
2)Window API函数的串口编程;
3)Teechart绘图控件编程;
4)Windows 函数多线程编程;
5)MFC主窗口和子窗口间的值传递和控件控制;
6)MFC自定义消息函数和子窗口的初始化函数;
7)MFC各种控件的使用:Combo box control,edit control,button control,text control,tab control,check-box control,以及两个外插控件MScomm串口控件和Teechart绘图控件。

MScomm控件串口编程

第一次的方案选用MScomm控件实现串口数据的传输,之后改用windows API串口编程。其是全双工,封装性好,不需要太多的了解内部的实现方式和协议的细节,只需简单的调用相应的API就能实现相应的功能。但其缺点是不够灵活,而且*不支持多线程*(放弃使用的原因),要在其他主机使用该控件编写的程序时需要注册该控件(第三方控件都需要如此)。下面做简单的使用总结,并提及一些个人使用中在意的细节。更详细的使用方式其他博客有很多。

使用方式如下:

创建控件并添加响应函数和控制变量->配置串口参数并打开串口->在响应函数中实现数据接收和发送的处理->关闭串口。
下载好控件并安装。控件下载路径
在VS工具栏中,选择工具(T)->选择工具选项(x)->com组件->选中控件并点确认。选中红色框中的选项

之后工具栏中就会有一个电话的图形控件。MScomm控件

把该控件添加到界面视图,并添加OnComm()响应事件和控制变量。就可以按自己要求实现相应的串口通讯功能了(初始化各属性和参数->在OnComm()响应函数中实现数据处理功能)。
在该程序中使用的函数有:
put_CommPort()//选择串口
put_RThreshold()//设置触发OnComm事件的条件
put_InputMode()//验取数据方式
put_InBufferSize()//输入缓冲区大小
put_OutBufferSize()//输出缓冲区大小
put_Settings()//串口参数设置
put_InputLen()//设置接收区数据长度
put_PortOpen()//打开串口

get_PortOpen()//获取串口状态
get_Input()//获取缓冲区数据
get_CommEvent()//获取串口事件

OnComm()//响应函数

个人的实现代码片段:

//设置-----------------------------------
    if(m_CtrlComm.get_PortOpen())
        m_CtrlComm.put_PortOpen(FALSE);

    m_CtrlComm.put_CommPort(COM_NUM);           //选择串口号
    m_CtrlComm.put_RThreshold(2);               //收到两个字节引发OnComm事件
    m_CtrlComm.put_InputMode(1);                //输入模式选为二进制
    m_CtrlComm.put_InBufferSize(1024);          //设置输入缓冲区的大小,Bytes
    m_CtrlComm.put_OutBufferSize(1024);         //设置输出缓冲区的大小,Bytes
    m_CtrlComm.put_Settings(SerialPortMessage); //设置串口参数,波特率,奇偶校验,数据位,停止位
    m_CtrlComm.put_InputMode(1);                //以二进制方法验取数据
//  m_CtrlComm.put_RThreshold(1);               //每当串口接收缓冲区中有多于或等于1个字符时将引发一个接收数据的OnComm事件
    m_CtrlComm.put_InputLen(0);                 //设置当前接收区数据长度为0
//  m_ctrlComm.put_PortOpen(TRUE);              //打开串口
    if(!m_CtrlComm.get_PortOpen())
        m_CtrlComm.put_PortOpen(TRUE);
    else
        AfxMessageBox("Can not open serial port!");

    m_CtrlComm.get_Input();


//事件处理-------------------------------
void Ccomm12Dlg::OnCommMscomm1()
{
    // TODO: 在此处添加消息处理程序代码
    VARIANT variant_inp;
    COleSafeArray safearray_inp;
    long len,k;
    BYTE rxdata[2048];
    BYTE bt = NULL;
    if(m_CtrlComm.get_CommEvent() == 2)                 //事件值为2表示接收缓冲区内有字符
    {

        //以下你可以根据自己的通信协议加入处理代码
        UpdateData(TRUE);
        variant_inp = m_CtrlComm.get_Input();           //读缓冲区
        safearray_inp = variant_inp;                    //VARIANT型变量转换为ColeSafeArray型变量
        len = safearray_inp.GetOneDimSize();            //得到有效数据长度
        for(k=0;k<len;k++)
            safearray_inp.GetElement(&k,rxdata+k);      //转换为BYTE型数组
        for(k=0;k<len;k++)                              //将数组转换为Cstring型变量
        {
            bt = *(char*)(rxdata+k);                    //字符型
            //strtemp_recive_data.Format("%X",bt);      //将字符送入临时变量strtemp存放 
            m_EditRxData+=bt;                           //加入接收编辑框对应字符串 
            strtemp_recive_data+=bt;
        }
        ............
}

注意事项:控件在安装后有些可能需要注册才能正常使用控件。在接收的数据中,因为其接收的数据类型是VARIANT,需要将其转为ColeSafeArray类型->BYTE类型。特别的,在数据需要以十六进制输出去时要用到CByteArray数据类型!这里给出实现代码:

// 去除空格并压缩数据
int Ccomm12Dlg::String2Hex(CString str, CByteArray& senddata)
{
    int hexdata,lowhexdata;
    int hexdatalen=0;
    int len=str.GetLength();
    senddata.SetSize(len/2);
    for(int i=0;i<len;)
    {
        char lstr,hstr=str[i];
        if(hstr==' ')
        {
            i++;
            continue;
        }
        i++;
        if(i>=len)
            break;
        lstr=str[i];
        hexdata=ConvertHexChar(hstr);
        lowhexdata=ConvertHexChar(lstr);
        if((hexdata==16)||(lowhexdata==16))
            break;
        else
            hexdata=hexdata*16+lowhexdata;
        i++;
        senddata[hexdatalen]=(char)hexdata;
        hexdatalen++;
    }
    senddata.SetSize(hexdatalen);
    return hexdatalen;
}

// 将一个字符转换为相应的十六进制
char Ccomm12Dlg::ConvertHexChar(char ch)
{
    if((ch>='0')&&(ch<='9'))
        return ch-0x30;
    else if((ch>='A')&&(ch<='F'))
        return ch-'A'+10;
    else if((ch>='a')&&(ch<='f'))
        return ch-'a'+10;
    else 
        return ch;
}

Windows API 串口编程

在放弃MScomm串口控件后,使用API串口编程方式。该方式虽然相比MScomm控件函数实现方式比较复杂一点,但其实现功能更加的灵活,功能更加完善,在其他各户机运行编程的程序也方便,重要的是它支持多线程,这就使其数据处理的效率要高,不需要担心因为事件的堵塞而影响其他功能的操作或其他数据的处理。Windows API串口编程主要有*同步通信方式*及*异步通信方式*两种。考虑到多线程方式的实现,当然选着使用异步通信方式。

实现方法:
打开串口->配置串口(设置驱动类型、配置超时结构参数、配置数据块结构参数)->创建监听函数->使用数据读取、数据写入函数->关闭串口。

所使用变量结构和函数:
typedef struct _COMMTIMEOUTS {
    DWORD ReadIntervalTimeout;          /* Maximum time between read chars. */
    DWORD ReadTotalTimeoutMultiplier;   /* Multiplier of characters.        */
    DWORD ReadTotalTimeoutConstant;     /* Constant in milliseconds.        */
    DWORD WriteTotalTimeoutMultiplier;  /* Multiplier of characters.        */
    DWORD WriteTotalTimeoutConstant;    /* Constant in milliseconds.        */
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;//超时配置结构

typedef struct _DCB {
    DWORD DCBlength;      /* sizeof(DCB)                     */
    DWORD BaudRate;       /* Baudrate at which running       */
    DWORD fBinary: 1;     /* Binary Mode (skip EOF check)    */
    DWORD fParity: 1;     /* Enable parity checking          */
    DWORD fOutxCtsFlow:1; /* CTS handshaking on output       */
    DWORD fOutxDsrFlow:1; /* DSR handshaking on output       */
    DWORD fDtrControl:2;  /* DTR Flow control                */
    DWORD fDsrSensitivity:1; /* DSR Sensitivity              */
    DWORD fTXContinueOnXoff: 1; /* Continue TX when Xoff sent */
    DWORD fOutX: 1;       /* Enable output X-ON/X-OFF        */
    DWORD fInX: 1;        /* Enable input X-ON/X-OFF         */
    DWORD fErrorChar: 1;  /* Enable Err Replacement          */
    DWORD fNull: 1;       /* Enable Null stripping           */
    DWORD fRtsControl:2;  /* Rts Flow control                */
    DWORD fAbortOnError:1; /* Abort all reads and writes on Error */
    DWORD fDummy2:17;     /* Reserved                        */
    WORD wReserved;       /* Not currently used              */
    WORD XonLim;          /* Transmit X-ON threshold         */
    WORD XoffLim;         /* Transmit X-OFF threshold        */
    BYTE ByteSize;        /* Number of bits/byte, 4-8        */
    BYTE Parity;          /* 0-4=None,Odd,Even,Mark,Space    */
    BYTE StopBits;        /* 0,1,2 = 1, 1.5, 2               */
    char XonChar;         /* Tx and Rx X-ON character        */
    char XoffChar;        /* Tx and Rx X-OFF character       */
    char ErrorChar;       /* Error replacement char          */
    char EofChar;         /* End of Input character          */
    char EvtChar;         /* Received Event character        */
    WORD wReserved1;      /* Fill for now.                   */
} DCB, *LPDCB;//数据块配置结构,串口参数配置

typedef struct _COMSTAT {
    DWORD fCtsHold : 1;
    DWORD fDsrHold : 1;
    DWORD fRlsdHold : 1;
    DWORD fXoffHold : 1;
    DWORD fXoffSent : 1;
    DWORD fEof : 1;
    DWORD fTxim : 1;
    DWORD fReserved : 25;
    DWORD cbInQue;
    DWORD cbOutQue;
} COMSTAT, *LPCOMSTAT;//包含串口信息的结构

typedef struct _OVERLAPPED {
    ULONG_PTR Internal;
    ULONG_PTR InternalHigh;
    union {
        struct {
            DWORD Offset;
            DWORD OffsetHigh;
        };

        PVOID Pointer;
    };

    HANDLE  hEvent;
} OVERLAPPED, *LPOVERLAPPED;/*包含了用于异步输入输出的信息的结构体,只有异步通信时才使用到*/
HANDLE CreateFile(    __in     LPCSTR lpFileName,
__in     DWORD dwDesiredAccess,
__in     DWORD dwShareMode,
__in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,
__in     DWORD dwCreationDisposition,
__in     DWORD dwFlagsAndAttributes,
__in_opt HANDLE hTemplateFile)//打开串口函数,并配置串口信息

BOOL SetupComm(    __in HANDLE hFile,
__in DWORD dwInQueue,
__in DWORD dwOutQueue)//该函数初始化一个指定的通信设备的通信参数

BOOL PurgeComm(    __in HANDLE hFile,
__in DWORD dwFlags)//清空缓冲区

BOOL SetCommTimeouts(    __in HANDLE hFile,
__in LPCOMMTIMEOUTS lpCommTimeouts)//windows系统利用此函数设定通讯设备读写时的超时参数

BOOL GetCommState(    __in  HANDLE hFile,
__out LPDCB lpDCB)//读取串口设置(波特率,校验,停止位,数据位等)

BOOL SetCommState(    __in HANDLE hFile,
__in LPDCB lpDCB)//设置串口设置(波特率,校验,停止位,数据位等)

BOOL GetCommMask(    __in HANDLE hFile,
__in DWORD dwEvtMask)//设置需要监视的串口事件

HANDLE CreateEvent(    __in_opt LPSECURITY_ATTRIBUTES lpEventAttributes,
__in     BOOL bManualReset,
__in     BOOL bInitialState,
__in_opt LPCSTR lpName)//用来创建或打开一个命名的或无名的事件对象

BOOL ClearCommError(    __in      HANDLE hFile,
__out_opt LPDWORD lpErrors,
__out_opt LPCOMSTAT lpStat)//Windows系统利用此函数清除硬件的通讯错误以及获取通讯设备的当前状态

BOOL WaitCommEvent(    __in        HANDLE hFile,
__inout     LPDWORD lpEvtMask,
__inout_opt LPOVERLAPPED lpOverlapped)//为一个特指的通信设备等待一个事件发生,该函数所监控的事件是与该设备句柄相关联的一系列事件

DWORD GetLastError(VOID)//该函数返回调用线程最近的错误代码值,错误代码以单线程为基础来维护的,多线程不重写各自的错误代码值。用于错误检测

BOOL GetOverlappedResult(    __in  HANDLE hFile,
__in  LPOVERLAPPED lpOverlapped,
__out LPDWORD lpNumberOfBytesTransferred,
__in  BOOL bWait)//判断一个重叠操作当前的状态

BOOL ReadFile(    __in        HANDLE hFile,
__out_bcount_part_opt(nNumberOfBytesToRead, *lpNumberOfBytesRead) __out_data_source(FILE) LPVOID lpBuffer,
__in        DWORD nNumberOfBytesToRead,
__out_opt   LPDWORD lpNumberOfBytesRead,
__inout_opt LPOVERLAPPED lpOverlapped)//从文件指针指向的位置开始将数据读出到一个文件中, 且支持同步和异步操作

BOOL WriteFile(    __in        HANDLE hFile,
__in_bcount_opt(nNumberOfBytesToWrite) LPCVOID lpBuffer,
__in        DWORD nNumberOfBytesToWrite,
__out_opt   LPDWORD lpNumberOfBytesWritten,
__inout_opt LPOVERLAPPED lpOverlapped)//从文件指针指向的位置开始将数据写入到一个文件中, 且支持同步和异步操作

BOOL CloseHandle(    __in HANDLE hObject)//关闭一个内核对象。其中包括文件、文件映射、进程、线程、安全和同步对象等

个人的代码实现片段:

//-------------------打开串口并设置串口
void CConsole10Dlg::OnBnClickedButtonPortopen()
{
    // TODO: 在此添加控件通知处理程序代码

.................

    hCom = CreateFile(strSerialPortText,
        GENERIC_READ|GENERIC_WRITE,
        0,
        NULL,
        OPEN_EXISTING,                                                          //设置产生方式
        FILE_FLAG_OVERLAPPED|FILE_ATTRIBUTE_NORMAL,                             //异步通信
        NULL);
    if(hCom == INVALID_HANDLE_VALUE)                                            //检测打开串口操作是否成功
    {
        AfxMessageBox("串口打开失败!");
    }                           
    //SetCommMask(hCom,EV_RXCHAR|EV_TXEMPTY);                                       //设置事件驱动的类型
    SetupComm(hCom,2048,2048);                                                  //设置输入、输出缓冲区的大小
    PurgeComm(hCom,PURGE_TXABORT|PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);    //清干净输入、输出缓冲区
    COMMTIMEOUTS CommTimeOuts;                                                  //定义超时结构,并填写该结构,总超时=时间系数*要求读/写的字符数+时间常量
    CommTimeOuts.ReadIntervalTimeout = 10;                                      //读间隔超时,无延时因为有WaitCommEvent等待数据
    CommTimeOuts.ReadTotalTimeoutMultiplier = 0;                                //读时间系数
    CommTimeOuts.ReadTotalTimeoutConstant = 0;                                  //读时间常数
    CommTimeOuts.WriteTotalTimeoutMultiplier = 50;                              //写时间系数
    CommTimeOuts.WriteTotalTimeoutConstant = 50;                                //写时间常量 
    SetCommTimeouts(hCom,&CommTimeOuts);                                        //设置读写操作所允许的超时

    DCB dcb;                                                                    //定义数据控制块结构
    GetCommState(hCom,&dcb);                                                    //读串口原来的参数设置
    dcb.BaudRate = atol(strBaudRateText);                                       //波特率
    dcb.ByteSize = atoi(strDataBitsText);                                       //数据位
    dcb.fParity = TRUE;
    BYTE wCheck;
    if(strCheckDigitText == "奇校验")
        wCheck = ODDPARITY;
    else if(strCheckDigitText == "偶校验")
        wCheck = EVENPARITY;
    else if(strCheckDigitText == "无校验")
        wCheck = NOPARITY;
    dcb.Parity = wCheck;                                                        //校验位
    BYTE wStop;
    if(strStopBitText = "1")
        wStop = ONESTOPBIT;
    else if(strStopBitText = "1.5")
        wStop = ONE5STOPBITS;
    else if(strStopBitText = "2")
        wStop = TWOSTOPBITS;
    dcb.StopBits = wStop;                                                       //停止位
    dcb.fBinary = TRUE;

    SetCommState(hCom,&dcb);                                                    //串口参数配置

    if(!SetCommMask(hCom,EV_RXCHAR|EV_TXEMPTY))//设置接收字符和输出缓冲串口事件需要监视
        AfxMessageBox("SetCommMask failed with error!");

    //创建事件对象
    m_ovRead.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
    m_ovWrite.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
    m_ovWait.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);

    if(!(m_Thread =AfxBeginThread((AFX_THREADPROC)CommWatchProc,this)))//创建监听线程
        AfxMessageBox("创建监听线程失败!");
        .........
}

............

//-------------------------------监听线程,实现数据的读
UINT CommWatchProc(LPVOID pParam)
{
    CConsole10Dlg *obj = (CConsole10Dlg*)pParam;

    COMSTAT ComStat = {0};
    DWORD WaitEvent = 0,Bytes = 0;
    BOOL Status = FALSE;
    DWORD Error = 0;
    BYTE lpBuffer[2048];
    if(hCom != NULL)
        ClearCommError(hCom,&Error,&ComStat);

    while(obj->m_IsOpen)
    {
        Status = WaitCommEvent(hCom,&WaitEvent,&obj->m_ovRead);
        if(FALSE == Status&&GetLastError() == ERROR_IO_PENDING)
            Status = GetOverlappedResult(hCom,&obj->m_ovRead,&Bytes,TRUE); //查询,等待重叠操作结束时返回
        ClearCommError(hCom,&Error,&ComStat);
        if(Status == TRUE//等待事件成功  
            && WaitEvent&EV_RXCHAR//缓存中有数据到达  
            && ComStat.cbInQue > 0)//有数据
        {
            memset(lpBuffer,0,sizeof(lpBuffer));

            Status = ReadFile(hCom,lpBuffer,sizeof(lpBuffer),&Bytes,&obj->m_ovRead);
            GetOverlappedResult(hCom,&obj->m_ovRead,&Bytes,TRUE);

            for(DWORD k = 0;k<Bytes;k++)
            {
                obj->m_Edit_Rxdata += lpBuffer[k];
                .........
            }
            ...........
            PurgeComm(hCom, PURGE_RXCLEAR|PURGE_RXABORT);
        }

    }
    return 0;       
}

...........

//-------------------------实现数据的写入
BOOL CConsole10Dlg::CommWrite(CString wBuffer)
{
    CByteArray test;
    int len = String2Hex(wBuffer,test);//转换字符性
    BYTE temp[1024];
    for(int i = 0;i<len;i++)
        temp[i] = test.GetAt(i);
    LPBYTE buffer = (BYTE *)temp;
    BOOL rtn = FALSE;
    DWORD WriteSize = 0;

    PurgeComm(hCom,PURGE_TXCLEAR|PURGE_TXABORT);
    rtn = WriteFile(hCom,buffer,len,&WriteSize,&m_ovWrite);

    len = 0;
    if(FALSE == rtn && GetLastError() == ERROR_IO_PENDING)//后台读取
    {
        //等待数据写入完成
        if(FALSE == GetOverlappedResult(hCom,&m_ovWrite,&WriteSize,TRUE))
            return FALSE;
    }

    len = WriteSize;
    return rtn;
}

注意事项:就如前面所说,Windows API的串口编程比MScomm控件的串口编程要复杂一点。在创建串口时,特别注意CreateFile()中dwFlagsAndAttributes参数设置为异步通信:FILE_FLAG_OVERLAPPED|FILE_ATTRIBUTE_NORMAL。之后也要注意检查DCB结构参数的设置。稍难实现的逻辑部分是,监听函数中的数据读取(检测到有数据到达后,需进行状态检测、错误判断之后才能正确读取数据),这可以写的简单也可以写的复杂但健壮一些(主要是错误信息的处理方式),由于使用异步通信,ReadFile()时会返回ERROR_IO_PENDING错误,需要用GetOverlappedResult()阻塞查询状态等待重叠操作的结束。在数据读取时再次用GetOverlappedResult()阻塞等待数据的读取完成。


Teechart绘图控件编程

Teechart属于第三方控件,同样需要下载并注册再添加进VS的工具箱才能使用。在其他电脑运行编写的程序也需要再次注册才能正常打开。其封装性好,比起MFC自带的绘图功能要简单好用。绘图功能比较丰富,在这里我只是用其2D曲线绘图功能。

使用方式:
下载安装并注册好->把控件添加进vs工具箱->添加控件设置好属性->按需要实现功能。
控件下载连接
工具栏中:工具(T)->选择工具箱项(X)->COM控件中选中Teechart Pro Activex control v5点确定

之后工具箱中出现Teechart控件图标,拖入到程序窗口并添加控件变量实现所需功能。
这里写图片描述

这里还需添加Teechart的类和控制变量才能实现相应的功能:
在”类视图“中右键->添加类->选中“Typelib中的MFC类”点添加
这里写图片描述
可用类库中选择TeeChart Pro Activex Control v5<1.0>,选择需要的接口,(这里只需图中选择的类)并点完成。之后“类视图”中就有了相应的类,“解决方案资源管理器”中也有了相应的头文件。将需要用到Teechart控件函数的.ccp文件中添加这些头文件就可以使用其封装函数了。
这里写图片描述
我的程序里,创建了两个Teechart控件,都是实现实时曲线的绘制,图像在任意点能放大。
其中一个的配置要求是:Y轴固定显示范围,X轴显示25个单元数并随曲线向左移动;
另一个的配置要求是:Y轴固定显示范围,X轴随曲线自动变化并保留整个X轴数据。

这里写图片描述

这里写图片描述

个人的实现代码片段:

//第一个图的绘制函数

..........................
//清空之前的绘图曲线
for(long i = 0;i<m_CtrlTchart1.get_SeriesCount();i++)
    {
        CSeries serDemo = (CSeries)m_CtrlTchart1.Series(i);
        serDemo.Clear();
    }

//重绘X轴(只能代码实现),属性页面只能配置第一次绘图时的属性
    CAxes chartaxis = (CAxes)m_CtrlTchart1.get_Axis();
    CAxis chartaxisbottem = (CAxis)chartaxis.get_Bottom();
    chartaxisbottem.SetMinMax(0,25);

//-------------
    if(!pDlg->CommWrite(ConstData))
        if(!(OnPaint_Thread =AfxBeginThread((AFX_THREADPROC)OnPaintTchart,pDlg)))//创建绘图线程
            AfxMessageBox("创建绘图线程失败!");

...............
}

UINT OnPaintTchart(LPVOID pParam)
{
    CConsole10Dlg *pDlg = (CConsole10Dlg*)pParam;

    pDlg->m_OnPainData.Empty();//开始时清空绘图前的数据
    while(pDlg->m_bOnPaint && !pDlg->m_IsTimeTest)
    {
        .........................

//---------------------绘图函数是以下几行代码
            CSeries serDemo = (CSeries)pDlg->page1.m_CtrlTchart1.Series(0);
            serDemo.AddXY(X,Y,strtempX,0);

//实现X轴的左移
            CAxes chartaxis = (CAxes)pDlg->page1.m_CtrlTchart1.get_Axis();
            CAxis chartaxisbottem = (CAxis)chartaxis.get_Bottom();
            chartaxisbottem.Scroll(1.0,TRUE);

//---------------------------------其余都是数据的处理

.................
    return 0;
}



//------------第二个图的绘制函数
..............
    CSeries serDmo = (CSeries)m_CtrlTchart2.Series(0);
    serDmo.Clear();
    CAxes chartaxis = (CAxes)m_CtrlTchart2.get_Axis();
    CAxis chartaxisbottem = (CAxis)chartaxis.get_Bottom();
    chartaxisbottem.put_Automatic(TRUE);
    ....

    CString Data = ......

        if(!(m_TimeThread = AfxBeginThread((AFX_THREADPROC)OnPaintTimeFunction,pDlg)))
            AfxMessageBox("绘图线程创建失败!");
    pDlg->m_IsTimeTest = true;
    .............
}

UINT OnPaintTimeFunction(LPVOID pParam)
{
    CConsole10Dlg *pDlg = (CConsole10Dlg*)pParam;

    pDlg->m_OnPainData.Empty();//开始时清空绘图前的数据
    while((!pDlg->m_bOnPaint)&&pDlg->m_IsTimeTest)
    {
        .................
//----------------------------------图像的绘制是以下两行代码
            CSeries serDemo = (CSeries)pDlg->page2.m_CtrlTchart2.Series(0);
            serDemo.AddXY(X,Y,strtempX,0);
//----------------------------------其余都是接收数据的处理

......................
    return 0;
}

注意事项:步骤中没有给出控件控制变量的添加。在曲线的二次绘制时需要清除之前绘制的图像,轴的属性设置也需要代码重新设置一次。其实现还是很简单的。


Windows函数的多线程编程

多线程的实现还是有好几种函数方式的(CreateThread(),_BeginThread(),_beginThreadex()),而在MFC中有两种线程:用户界面线程和辅助线程(AfxBeginThread())。 用户界面线程通常用于处理用户输入及响应用户生成的事件和消息。 辅助线程通常用于完成不需要用户输入的任务(如重新计算)。 Win32 API 不区分线程类型;它只需要了解线程的起始地址以开始执行线程。 MFC 为用户界面中的事件提供消息泵,从而对用户界面线程进行专门处理。线程还涉及线程间的数据传输、同步、安全,线程优先级,线程状态,互斥和临界区,线程池等。线程的编程和调试是BUG的常发区。我在此用辅助线程和使用了一个互斥对象确保数据的安全和同步问题。

使用的类和函数有:
class CMutex : public CSyncObject
{
    DECLARE_DYNAMIC(CMutex)

// Constructor
public:
    /* explicit */ CMutex(BOOL bInitiallyOwn = FALSE, LPCTSTR lpszName = NULL,
        LPSECURITY_ATTRIBUTES lpsaAttribute = NULL);

// Implementation
public:
    virtual ~CMutex();
    BOOL Unlock();
};//互斥对象

    BOOL Lock(DWORD dwTimeout)//锁函数
    BOOL Unlock(void)//解锁函数

    HANDLE AfxBeginThread(AFX_THREADPROC pfnThreadProc,
     LPVOID pParam,
     UINT nStackSize = 0,
     DWORD dwCreateFlags = 0,
     LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL)/*创建线程函数,比CreateThread()安全*/

    UINT CommWatchProc(LPVOID pParam);//线程函数,遵循UINT FunctionName(LPVOID pParam)形式
    UINT OnPaintTchart(LPVOID pParam);
    UINT OnPaintTimeFunction(LPVOID pParam);

    CloseHandle(Handle)

个人的代码片段:

//声明全局函数(要在类外,线程函数不能属于类)
 UINT CommWatchProc(LPVOID pParam);
 UINT OnPaintTchart(LPVOID pParam);
 UINT OnPaintTimeFunction(LPVOID pParam);

//生成全局互斥类对象
CMutex hmutex(NULL,FALSE,NULL);     //全局互斥对象

//创建线程函数,并传入窗口类的this指针参数,方便窗口类的变量和函数的使用
m_Thread =AfxBeginThread((AFX_THREADPROC)CommWatchProc,this)//创建监听线程

UINT CommWatchProc(LPVOID pParam)
{
    CConsole10Dlg *obj = (CConsole10Dlg*)pParam;

    功能.....
    hmutex.Lock(INFINITE);//锁定互斥对象
    *要保护数据*所在代码段
    hmutex.Unlock();//释放互斥对象

    return 0;
}

//关闭线程
    if(NULL != m_Thread)
    {
        WaitForSingleObject(m_Thread,1000);//等待线程结束
        CloseHandle(m_Thread);
        m_Thread = NULL;
    }

//其余两个线程类似

注意事项:线程创建运行完后要记得销毁,数据要初始化防止不必要的内存泄露。线程中要尽量少用互斥和临界区或使用较高效率的临界函数,提高运行效率。


MFC主窗口和子窗口之间的变量传递和控件使用

在编程时,遇到两个窗口间的通信问题。其实也是两个类之间的变量和函数的使用问题。要解决也很简单:

主窗口类:CConsole10Dlg(有变量(pubilc):bool m_bOnPaint;
函数:void CommWrite(CString Data);

子窗口类:CDlg1(有变量(public):int m_Edit_ConstData;

主窗口获取子窗口变量和函数的使用:
在主窗口类的头文件(Console1.0Dlg.h)中添加子窗口头文件,并定义子窗口类对象。通过该对象就可以调用子类窗口的变量和函数了!

//Console1.0Dlg.h中
#include "Dlg1.h"

CDlg1 page1;

//Console1.0Dlg.cpp中
page1.m_Edit_ConstData = xx;
page1.成员函数名(参数,..);

子窗口获取主窗口变量和函数的使用:
在子窗口.cpp文件中加入主窗口头文件,再直接使用GetParent()函数获取主窗口句柄,根据该句柄就可以访问主窗口变量和函数了!

//Dlg1.cpp中
#include "Console1.0Dlg.h"

CConsole10Dlg *pDlg = (CConsole10Dlg*)GetParent()->GetParent();

pDlg->CommWrite(Data);
pDlg->m_bOnPaint = false;

注意事项:头文件的包含不能相互包含


MFC自定义消息函数和子窗口的初始化函数

自定义消息函数要记住宏”WM_USER”,该宏是Windows定义的宏,该宏值以内的消息变量都是系统自定义的消息变量,为了自定义消息变量不与系统消息变量发生冲突,只需在该宏上加一个正数值就行,之后要自己定义消息函数,并手动添加函数映射:

//.h文件中
#define WM_MESSAGE  WM_USER + 100  //自定义消息ID

afx_msg LRESULT OnChangeEdit(WPARAM wParam, LPARAM lParam);//自定义消息函数

//.cpp文件中
BEGIN_MESSAGE_MAP(CConsole10Dlg, CDialog)
...
ON_MESSAGE(WM_MESSAGE, &CConsole10Dlg::OnChangeEdit)//添加消息映射
...
END_MESSAGE_MAP()

//在需要触发自定义消息函数的地方调用PostMessage()函数,实现自定义消息函数的执行

PostMessage(obj->m_hWnd,WM_MESSAGE,0,0);

//函数的实现
LRESULT CConsole10Dlg::OnChangeEdit(WPARAM wParam, LPARAM lParam)
{
    所需功能
    return 0;
}

再来,是子窗口的初始化函数的编写。创建的子窗口是没有初始化函数的,不过初始化函数是虚函数,子窗口类和主窗口类同是CDialog的派生类。因此,只需将主窗口的初始化函数复制到子窗口的实现文件里(.cpp)就行了!

BOOL CDlg1::OnInitDialog()
{
    CDialog::OnInitDialog();

    初始化功能

    return TRUE;
}

注意事项:子窗口的初始化函数中要将类名改为所在类的类名。


MFC各种控件的使用

控件的使用挑选在编写中比较在意的知识点:
关于Edit control 的内容显示,一是让滚动条停在实时内容上,二是消除显示时的闪烁问题。另外,该控件定义的CString 变量是有存储容量的,要记得消除过多的内容。有以下实现代码:

//滚动到当前内容
CEdit* pedit = (CEdit*)GetDlgItem(IDC_EDIT_RXDATA);
    pedit->LineScroll(pedit->GetLineCount());
    if(pedit->GetLineCount()>50)
    {
        m_Edit_Rxdata = "";
        UpdateData(FALSE);
    }

//用SendMessage()代替UpdateData()函数消除闪烁
GetDlgItem(IDC_EDIT_RXDATA)->SendMessage(EM_SETSEL,-1,0);
        GetDlgItem(IDC_EDIT_RXDATA)->SendMessage(EM_REPLACESEL,NULL,(LPARAM)(LPTSTR)(LPCTSTR)m_Edit_Rxdata);
        m_Edit_Rxdata.Empty();

关于Static control 控件的内容动态显示:

this->GetDlgItem(IDC_STATIC_1)->SetWindowTextA(m_Static1);

总结

内容积累的太多,没写细,后面的内容也没认真整理。程序的要求功能基本完成。自己写完后回去看,还真不乍样,第一次写,就这样吧。之后要出差一段时间,隔太久来写,记忆的效率就打折扣了。

展开阅读全文

没有更多推荐了,返回首页