编写一个串口助手(Win32, C++)

封装串口常用操作, 使用Win32 api对话框做了简洁的UI界面

1.支持Unicode, UTF8, ANSI的字符串编码与十六进制收发数据

2.支持自定义自动发送数据, 可调整发送间隔时间(最低10毫秒)

3.自定义串口打开配置与超时配置

4.源码类简单封装window消息机制, 命令消息操作, 可以向MFC那样添加控件ID与事件, 使得win32编写窗口更加简洁, 像这样:

BEGIN_DLG_MESSAGE_MAP(CDialogSerialPort, CDialogBase)  

    ON_DLG_MESSAGE(WM_INITDIALOG, &CDialogSerialPort::OnInitDialog) 
    ON_DLG_MESSAGE(WM_TRAYICON, &CDialogSerialPort::OnTrayIcon) 
    ON_DLG_MESSAGE(WM_CLOSE, &CDialogSerialPort::OnClose) 

    ON_DLG_COMMAND(ID_FILE_EXIT, &CDialogSerialPort::OnCommandExit)
    ON_DLG_COMMAND(ID_SETTINGS_SHOW, &CDialogSerialPort::OnCommandShow)
    ON_DLG_COMMAND(ID_SETTINGS_HIDE, &CDialogSerialPort::OnCommandHide)
    ON_DLG_COMMAND(IDC_CHECK_READ_HEX, &CDialogSerialPort::OnCommandReadHexShow)
    ON_DLG_COMMAND(IDC_CHECK_WRITE_HEX, &CDialogSerialPort::OnCommandWriteHexShow)
    ON_DLG_COMMAND(IDC_BUTTON_CLEAR, &CDialogSerialPort::OnCommandReadClear)

END_DLG_MESSAGE_MAP()

不必写一堆switch和if else来检查常用消息或者处理WM_COMMAND消息.

 

1113d5b6d16548f28a24afd89bcc97e8.png

 

首先对串口操作做了一些封装:

助手类声明如下

CSerialPort.h

#pragma once

#include <string>
#include <windows.h>
#include <tchar.h>

#ifdef _UNICODE
using _tstring = std::wstring;
#else
using _tstring = std::string;
#endif

class CSerialPort
{
public:
    CSerialPort();
    CSerialPort(const CSerialPort& r) = delete;
    ~CSerialPort();

    // 打开串口
    bool Open(
        int nComm,                      //串口号
        DWORD dwBaudRate = CBR_115200,  //波特率
        BYTE bByteSize = 8,             //数据位
        BYTE bParity = NOPARITY,        //校验位
        BYTE bStopBits = ONESTOPBIT     //停止位
    );

    // 打开串口
    bool Open(
        const _tstring& strName = _T("COM1"),        //串口名
        DWORD dwBaudRate = CBR_115200,  //波特率
        BYTE bByteSize = 8,             //数据位
        BYTE bParity = NOPARITY,        //校验位
        BYTE bStopBits = ONESTOPBIT     //停止位
    );

    // 检测是否已经打开
    bool IsOpen() const;

    // 设置状态配置
    bool SetState(
        DWORD dwBaudRate = CBR_115200,  //波特率
        BYTE bByteSize = 8,             //数据位
        BYTE bParity = NOPARITY,        //校验位
        BYTE bStopBits = ONESTOPBIT     //停止位
    );

    // 设置状态配置
    bool SetState(const LPDCB lpDcb);

    // 设置缓冲区大小
    bool SetupComm(
        DWORD dwInQueue = 14,   // 输入缓冲区 1 - 14
        DWORD dwOutQueue = 16   // 输出缓冲区 1 - 16
    );

    // 设置超时配置
    bool SetTimeOut(
        DWORD ReadInterval = 50,          // 读间隔超时
        DWORD ReadTotalMultiplier = 5,   // 读时间系数
        DWORD ReadTotalConstant = 1000,            // 读时间常量
        DWORD WriteTotalMultiplier = 10,         // 写时间系数
        DWORD WriteTotalConstant = 200            // 写时间常量
    );

    // 设置超时配置
    bool SetTimeOut(const LPCOMMTIMEOUTS lpTimeOuts);

    // 清空缓冲区
    bool Purge(
        DWORD  dwFlags = PURGE_TXCLEAR | PURGE_RXCLEAR
    );

    // 清除错误
    bool ClearError();

    // 关闭串口
    void Close();

    // 发送数据
    bool Write(
        LPCVOID lpData, 
        DWORD cbSize,
        LPDWORD lpBytesWritten = nullptr,
        DWORD nTimeOut = 1000
    );

    // 接收数据
    bool Read(
        LPVOID lpData, 
        DWORD cbSize,
        LPDWORD lpBytesRead = nullptr,
        DWORD nTimeOut = 3000
    );

private:

    // 打开串口
    bool OpenComm(int nComm);

    // 打开串口
    bool OpenComm(const _tstring& strName);

    // 等待重叠操作完成
    bool WaitForOverlapped(
        HANDLE hFile, 
        LPOVERLAPPED lpOv, 
        LPDWORD lpBytesTransferred, 
        DWORD nTimeOut
    );

private:

    HANDLE m_hComm;             // 串口句柄
    HANDLE m_hReadEvent;        // 重叠读事件句柄
    HANDLE m_hWriteEvent;       // 重叠写事件句柄
    bool m_bExit;               // 退出标记
};

串口操作具体实现部分如下, 其中读写采用异步方式, 方便退出, 避免读写时卡住

CSerialPort.cpp

#include "CSerialPort.h"
#include <strsafe.h>

CSerialPort::CSerialPort()
    :
    m_hComm(INVALID_HANDLE_VALUE),
    m_hReadEvent(nullptr),
    m_hWriteEvent(nullptr),
    m_bExit(false)
{

}

CSerialPort::~CSerialPort()
{
    Close();
}

// 打开串口
bool CSerialPort::Open(
    int nComm,
    DWORD dwBaudRate/* = CBR_115200*/,
    BYTE bByteSize/* = 8*/,
    BYTE bParity/* = NOPARITY*/,
    BYTE bStopBits/* = ONESTOPBIT*/
)
{
    if (!OpenComm(nComm))
    {
        return false;
    }

    if (!SetState(dwBaudRate, bByteSize, bParity, bStopBits))
    {
        this->Close();
        return false;
    }

    m_bExit = false;
    return true;
}

// 打开串口
bool CSerialPort::Open(
    const _tstring& strName/* = _T("COM1")*/,               //串口名
    DWORD dwBaudRate/* = CBR_115200*/,
    BYTE bByteSize/* = 8*/,
    BYTE bParity/* = NOPARITY*/,
    BYTE bStopBits/* = ONESTOPBIT*/
)
{
    if (!OpenComm(strName))
    {
        return false;
    }

    if (!SetState(dwBaudRate, bByteSize, bParity, bStopBits))
    {
        this->Close();
        return false;
    }

    m_bExit = false;
    return true;
}

bool CSerialPort::IsOpen() const
{
    return INVALID_HANDLE_VALUE != m_hComm;
}

bool CSerialPort::SetState(
    DWORD dwBaudRate/* = CBR_115200*/,
    BYTE bByteSize/* = 8*/,
    BYTE bParity/* = NOPARITY*/,
    BYTE bStopBits/* = ONESTOPBIT*/
)
{
    if (INVALID_HANDLE_VALUE == m_hComm)
    {
        return false;
    }

    DCB dcb = { 0 };
    dcb.DCBlength = sizeof(DCB);

    if (!::GetCommState(m_hComm, &dcb))
    {
        return false;
    }

    dcb.DCBlength = sizeof(DCB);
    dcb.BaudRate = dwBaudRate;
    dcb.ByteSize = bByteSize;
    dcb.Parity = bParity;
    dcb.StopBits = bStopBits;

    return ::SetCommState(m_hComm, &dcb);
}

bool CSerialPort::SetState(const LPDCB lpDcb)
{
    if (nullptr == lpDcb)
    {
        return false;
    }

    return ::SetCommState(m_hComm, lpDcb);
}

// 设置缓冲区大小
bool CSerialPort::SetupComm(
    DWORD dwInQueue/* = 14*/,
    DWORD dwOutQueue/* = 16*/
)
{
    if (INVALID_HANDLE_VALUE == m_hComm)
    {
        return false;
    }

    return ::SetupComm(m_hComm, dwInQueue, dwOutQueue);
}

bool CSerialPort::SetTimeOut(
    DWORD ReadInterval/* = 50*/,
    DWORD ReadTotalMultiplier/* = 50*/,
    DWORD ReadTotalConstant/* = 100*/,
    DWORD WriteTotalMultiplier/* = 50*/,
    DWORD WriteTotalConstant/* = 200*/
)
{
    if (INVALID_HANDLE_VALUE == m_hComm)
    {
        return false;
    }

    COMMTIMEOUTS comTimeOuts = { 0 };

    if (!::GetCommTimeouts(m_hComm, &comTimeOuts))
    {
        return false;
    }

    comTimeOuts.ReadIntervalTimeout = ReadInterval;
    comTimeOuts.ReadTotalTimeoutMultiplier = ReadTotalMultiplier;
    comTimeOuts.ReadTotalTimeoutConstant = ReadTotalConstant;
    comTimeOuts.WriteTotalTimeoutMultiplier = WriteTotalMultiplier;
    comTimeOuts.WriteTotalTimeoutConstant = WriteTotalConstant;
    return ::SetCommTimeouts(m_hComm, &comTimeOuts);
}

bool CSerialPort::SetTimeOut(const LPCOMMTIMEOUTS lpTimeOuts)
{
    if (nullptr == lpTimeOuts)
    {
        return false;
    }

    return ::SetCommTimeouts(m_hComm, lpTimeOuts);
}

bool CSerialPort::Purge(
    DWORD  dwFlags/* = PURGE_TXCLEAR | PURGE_RXCLEAR*/
)
{
    if (INVALID_HANDLE_VALUE == m_hComm)
    {
        return false;
    }

    return ::PurgeComm(m_hComm, dwFlags);
}

bool CSerialPort::ClearError()
{
    if (INVALID_HANDLE_VALUE == m_hComm)
    {
        return false;
    }

    DWORD dwError = 0;
    COMSTAT comStat = { 0 };

    return ::ClearCommError(m_hComm, &dwError, &comStat);
}

void CSerialPort::Close()
{
    m_bExit = true;
    if (nullptr != m_hReadEvent)
    {
        ::SetEvent(m_hReadEvent);
        ::CloseHandle(m_hReadEvent);
        m_hReadEvent = nullptr;
    }

    if (nullptr != m_hWriteEvent)
    {
        ::SetEvent(m_hWriteEvent);
        ::CloseHandle(m_hWriteEvent);
        m_hWriteEvent = nullptr;
    }

    if (INVALID_HANDLE_VALUE != m_hComm)
    {
        ::CancelIoEx(m_hComm, nullptr);
        ::CloseHandle(m_hComm);
        m_hComm = INVALID_HANDLE_VALUE;
    }
}

bool CSerialPort::OpenComm(int nComm)
{
    TCHAR szBuf[MAX_PATH];
    (void)::StringCchPrintf(szBuf, _countof(szBuf), _T(R"(\\.\COM%d)"), nComm);

    // 关闭前一个已打开的串口
    this->Close();

    m_hComm = ::CreateFile(
        szBuf,
        GENERIC_READ | GENERIC_WRITE,   //读写串口
        0,                              //独占打开
        nullptr,
        OPEN_EXISTING,                  //必须存在
        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,           //重叠操作
        nullptr
    );

    if (INVALID_HANDLE_VALUE == m_hComm)
    {
        return false;
    }

    m_hReadEvent = CreateEvent(nullptr, false, false, nullptr);
    if (nullptr == m_hReadEvent)
    {
        this->Close();
        return false;
    }

    m_hWriteEvent = CreateEvent(nullptr, false, false, nullptr);
    if (nullptr == m_hWriteEvent)
    {
        this->Close();
        return false;
    }

    return true;
}

bool CSerialPort::OpenComm(const _tstring& strName)
{
    TCHAR szBuf[MAX_PATH];
    (void)::StringCchPrintf(szBuf, _countof(szBuf), _T(R"(\\.\%s)"), strName.c_str());

    // 关闭前一个已打开的串口
    this->Close();

    m_hComm = ::CreateFile(
        szBuf,
        GENERIC_READ | GENERIC_WRITE,   //读写串口
        0,                              //独占打开
        nullptr,
        OPEN_EXISTING,                  //必须存在
        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,           //重叠操作
        nullptr
    );

    if (INVALID_HANDLE_VALUE == m_hComm)
    {
        return false;
    }

    m_hReadEvent = CreateEvent(nullptr, false, false, nullptr);
    if (nullptr == m_hReadEvent)
    {
        this->Close();
        return false;
    }

    m_hWriteEvent = CreateEvent(nullptr, false, false, nullptr);
    if (nullptr == m_hWriteEvent)
    {
        this->Close();
        return false;
    }

    return true;
}

bool CSerialPort::WaitForOverlapped(
    HANDLE hFile,
    LPOVERLAPPED lpOv,
    LPDWORD lpBytesTransferred,
    DWORD nTimeOut
)
{
    bool bResult = false;
    DWORD dwBytesTransferred = 0;
    DWORD dwWait = ::WaitForSingleObject(lpOv->hEvent, nTimeOut);

    switch (dwWait)
    {
    case WAIT_OBJECT_0:
    {
        if (::GetOverlappedResult(hFile,
            lpOv,
            &dwBytesTransferred,
            true
        ))
        {
            bResult = true;
        }
        else
        {
            bResult = false;
        }

        if (lpBytesTransferred)
        {
            *lpBytesTransferred = dwBytesTransferred;
        }
    }
    break;
    case WAIT_TIMEOUT:
        break;
    default:
        break;
    }

    if (!bResult && !m_bExit)
    {
        ::CancelIoEx(hFile, lpOv);
    }

    return bResult;
}

bool CSerialPort::Write(
    LPCVOID lpData,
    DWORD cbSize,
    LPDWORD lpBytesWritten,
    DWORD nTimeOut/* = 1000*/
)
{
    if (INVALID_HANDLE_VALUE == m_hComm || 
        nullptr == lpData || 
        0 == cbSize ||
        m_bExit)
    {
        return false;
    }

    OVERLAPPED ov = { 0 };
    BOOL bResult = FALSE;
    ov.hEvent = m_hWriteEvent;

    bResult = ::WriteFile(
        m_hComm,
        lpData,
        cbSize,
        nullptr,
        &ov
    );

    if (bResult)
    {
        goto L_Cleanup;
    }

    if (ERROR_IO_PENDING != ::GetLastError())
    {
        goto L_Cleanup;
    }

    bResult = WaitForOverlapped(m_hComm, &ov, lpBytesWritten, nTimeOut);

L_Cleanup:

    return bResult;
}

bool CSerialPort::Read(
    LPVOID lpData,
    DWORD cbSize,
    LPDWORD lpBytesRead/* = nullptr*/,
    DWORD nTimeOut/* = 3000*/
)
{
    if (INVALID_HANDLE_VALUE == m_hComm ||
        nullptr == lpData ||
        0 == cbSize || 
        m_bExit)
    {
        return false;
    }

    OVERLAPPED ov = { 0 };
    BOOL bResult = FALSE;
    ov.hEvent = m_hReadEvent;

    bResult = ::ReadFile(
        m_hComm,
        lpData,
        cbSize,
        nullptr,
        &ov
    );

    if (bResult)
    {
        goto L_Cleanup;
    }

    if (ERROR_IO_PENDING != ::GetLastError())
    {
        goto L_Cleanup;
    }

    bResult = WaitForOverlapped(m_hComm, &ov, lpBytesRead, nTimeOut);
    if (!bResult)
    {
        return true;
    }

L_Cleanup:

    return bResult;
}

接着就是编写Win32 窗口界面, 这里我使用的时对话框, 下面对对话框进行了一些封装, 采取MFC的消息映射方式封装, 方便后期消息映射成员函数, 极大简化编码工作量.

对话框基类声明如下:

CDialogBase.h

#pragma once

#include <Windows.h>
#include <tchar.h>
#include <string>

#ifdef UNICODE
using _tstring = std::wstring;
#else
using _tstring = std::string;
#endif

class CDialogBase;

typedef LRESULT(CDialogBase::* pMapFn)(WPARAM wParam, LPARAM lParam);

struct WND_DLG_MSGMAP_ENTRY
{
    pMapFn m_pfn;
    UINT m_nMessage;
    UINT m_nID;
    WND_DLG_MSGMAP_ENTRY(UINT uMsg, pMapFn pfn) : m_nMessage(uMsg), m_pfn(pfn), m_nID(0) {}
    WND_DLG_MSGMAP_ENTRY(UINT uMsg, UINT nID, pMapFn pfn) : m_nMessage(uMsg), m_nID(nID), m_pfn(pfn) {}
};

struct WND_DLG_MSGMAP
{
    const WND_DLG_MSGMAP* (*pfnGetBaseMap)();
    const WND_DLG_MSGMAP_ENTRY* lpEntries;
};

#ifndef dlg_msg
#define dlg_msg
#endif

#define DECLARE_DLG_MESSAGE_MAP()                                   \
protected:                                                      \
static const WND_DLG_MSGMAP* GetThisMessageMap();                   \
virtual const WND_DLG_MSGMAP* GetMessageMap() const;                \
                                                                
#define BEGIN_DLG_MESSAGE_MAP(theClass, baseClass)                  \
const WND_DLG_MSGMAP* theClass::GetMessageMap() const               \
{                                                               \
    return GetThisMessageMap();                                 \
}                                                               \
const WND_DLG_MSGMAP* theClass::GetThisMessageMap()                 \
{                                                               \
    typedef baseClass TheBaseClass;					            \
static const WND_DLG_MSGMAP_ENTRY _messageEntries[] =               \
{                                                               \
                                                                
#define ON_DLG_MESSAGE(message, memberFxn)                          \
WND_DLG_MSGMAP_ENTRY(message, static_cast< LRESULT (CDialogBase::*)(WPARAM, LPARAM) >(memberFxn)),

#define ON_DLG_COMMAND(_id, memberFxn)                          \
WND_DLG_MSGMAP_ENTRY(WM_COMMAND, _id, static_cast< LRESULT (CDialogBase::*)(WPARAM, LPARAM) >(memberFxn)),

#define END_DLG_MESSAGE_MAP()  WND_DLG_MSGMAP_ENTRY(0, NULL) };         \
static const WND_DLG_MSGMAP messageMap =                            \
{&TheBaseClass::GetThisMessageMap , &_messageEntries[0] };      \
return &messageMap;                                             \
}                                                               \

#define DECLARE_DLG_BASE_MESSAGE_MAP()                              \
protected:                                                      \
static const WND_DLG_MSGMAP* GetThisMessageMap();                   \
virtual const WND_DLG_MSGMAP* GetMessageMap() const;                \
static const WND_DLG_MSGMAP* GetBaseMessageMap();                   \

#define BEGIN_DLG_BASE_MESSAGE_MAP(theClass)                        \
typedef theClass TheThisClass;					                \
const WND_DLG_MSGMAP* TheThisClass::GetMessageMap() const           \
{                                                               \
    return GetThisMessageMap();                                 \
}                                                               \
const WND_DLG_MSGMAP* TheThisClass::GetBaseMessageMap()             \
{                                                               \
    return NULL;                                                \
}                                                               \
const WND_DLG_MSGMAP* TheThisClass::GetThisMessageMap()             \
{                                                               \
static const WND_DLG_MSGMAP_ENTRY _messageEntries[] =               \
{                                                               \

#define END_DLG_BASE_MESSAGE_MAP()  WND_DLG_MSGMAP_ENTRY(0, NULL) };    \
static const WND_DLG_MSGMAP messageMap =                            \
{&TheThisClass::GetBaseMessageMap, &_messageEntries[0] };       \
return &messageMap;                                             \
}                                                               \

class CDialogBase
{
public:

    CDialogBase();
    CDialogBase(const CDialogBase& r) = delete;
    ~CDialogBase();

    dlg_msg INT_PTR DoModal(HWND hWndParent, bool bCenter = true);
    dlg_msg INT_PTR DoModal(UINT resourceID, HWND hWndParent, bool bCenter = true);
    dlg_msg INT_PTR DoModalEx(UINT resourceID, HWND hWndParent, BOOL isShow = TRUE, bool bCenter = true);
    dlg_msg HWND DoDialog(UINT resourceID, HWND hWndParent, BOOL isShow = TRUE, bool bCenter = true);
    dlg_msg LRESULT OnClose(WPARAM wParam, LPARAM lParam);
    dlg_msg LRESULT OnLanguageChange(WPARAM wParam, LPARAM lParam);

    VOID SetResourceID(UINT resourceID);
    VOID ShowWindow(int cmdShow = SW_SHOW);
    BOOL IsVisiable() const;
    VOID MoveToCenter(HWND hParent);
    _tstring LoadString(UINT resourceID);
    VOID ModifyMenuText(HMENU hMnu, UINT uPosition, LPCTSTR lpNewItem, BOOL fByPos = TRUE);
    VOID ModifyMenuText(HMENU hMnu, UINT uPosition, UINT resourceID, BOOL fByPos = TRUE);
    HWND GetWndHandle() const;
    bool EndDialog(INT_PTR nResult);

    operator HWND() const
    {
        return m_hWnd;
    }

private:

    //假模态对话框
    dlg_msg LRESULT DoFakeModal(UINT resourceID, HWND hWndParent, BOOL isShow = TRUE);
    static INT_PTR WINAPI DialogProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
    INT_PTR ThisDialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam);

protected:

    virtual BOOL PreTranslateMessage(LPMSG pMsg);

protected:
    HWND m_hWnd;
    UINT m_uResID;
    BOOL m_bModel;
    BOOL m_bFakeModel;
    BOOL m_bCenter;
    DECLARE_DLG_BASE_MESSAGE_MAP()
};

具体实现部分如下:

CDialogBase.cpp

#include "CDialogBase.h"
#include <Windows.h>
#include <strsafe.h>

#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")

BEGIN_DLG_BASE_MESSAGE_MAP(CDialogBase)
    ON_DLG_MESSAGE(WM_CLOSE, &CDialogBase::OnClose)
END_DLG_BASE_MESSAGE_MAP()

CDialogBase::CDialogBase()
    :
    m_hWnd(NULL),
    m_uResID(FALSE),
    m_bModel(FALSE),
    m_bFakeModel(FALSE),
    m_bCenter(FALSE)
{

}

CDialogBase::~CDialogBase()
{
    if (NULL != m_hWnd)
    {
        ::DestroyWindow(m_hWnd);
    }
}

HWND CDialogBase::DoDialog(UINT resourceID, HWND hWndParent, BOOL isShow, bool bCenter/* = true*/)
{
    m_bCenter = bCenter;
    m_bModel = FALSE;
    m_uResID = resourceID;
    HWND hWnd = ::CreateDialogParam(GetModuleHandle(NULL), MAKEINTRESOURCE(resourceID), hWndParent, DialogProc, (LPARAM)this);
    m_hWnd = hWnd;

    if (isShow)
    {
        ::ShowWindow(m_hWnd, SW_SHOW);
    }

    return m_hWnd;
}

INT_PTR CDialogBase::DoModalEx(UINT resourceID, HWND hWndParent, BOOL isShow, bool bCenter/* = true*/)
{
    m_bCenter = bCenter;
    m_bModel = FALSE;
    return DoFakeModal(resourceID, hWndParent, isShow);
}

INT_PTR CDialogBase::DoModal(HWND hWndParent, bool bCenter/* = true*/)
{
    m_bCenter = bCenter;
    m_bModel = TRUE;
    return ::DialogBoxParam(GetModuleHandle(NULL), MAKEINTRESOURCE(m_uResID), hWndParent, DialogProc, (LPARAM)this);
}

INT_PTR CDialogBase::DoModal(UINT resourceID, HWND hWndParent, bool bCenter/* = true*/)
{
    m_bCenter = bCenter;
    m_uResID = resourceID;
    m_bModel = TRUE;
    return ::DialogBoxParam(GetModuleHandle(NULL), MAKEINTRESOURCE(m_uResID), hWndParent, DialogProc, (LPARAM)this);
}

BOOL CDialogBase::PreTranslateMessage(LPMSG pMsg)
{
    return FALSE;
}

bool CDialogBase::EndDialog(INT_PTR nResult)
{
    if (NULL == m_hWnd)
    {
        return false;
    }

    if (m_bFakeModel)
    {
        //启用父窗口
        HWND hParent = ::GetParent(m_hWnd);
        if (hParent)
        {
            ::EnableWindow(hParent, TRUE);
        }

        // 投递 WM_QUIT 消息退出消息环 (窗口句柄指定为NULL时, 
        // 该函数的行为类似于对 PostThreadMessage 的调用, 
        // 其中 dwThreadId 参数设置为当前线程的标识符。)
        ::PostMessage(NULL, WM_QUIT, nResult, 0);
    }

    if (m_bModel)
    {
        ::EndDialog(m_hWnd, nResult);
        m_hWnd = NULL;
    }
    else
    {
        ::DestroyWindow(m_hWnd);
        m_hWnd = NULL;
    }

    return TRUE;
}

LRESULT CDialogBase::OnClose(WPARAM wParam, LPARAM lParam)
{
    return EndDialog(wParam);
}

LRESULT CDialogBase::DoFakeModal(UINT resourceID, HWND hWndParent, BOOL isShow)
{
    m_bModel = FALSE;
    m_uResID = resourceID;
    m_bFakeModel = TRUE;

    m_hWnd = ::CreateDialogParam(GetModuleHandle(NULL), MAKEINTRESOURCE(resourceID), hWndParent, DialogProc, (LPARAM)this);
    if (NULL == m_hWnd)
    {
        return -1;
    }

    if (isShow)
    {
        ::ShowWindow(m_hWnd, SW_SHOW);
    }

    //禁用父窗口
    HWND hParent = GetParent(m_hWnd);
    if (hParent)
    {
        ::EnableWindow(hParent, FALSE);
        ::SetForegroundWindow(hParent);
    }

    MSG msg = { 0 };
    BOOL bRet = FALSE;

    //如果 hWnd 为 NULL, 则 GetMessage 将检索属于当前线程的任何窗口的消息,以及当前线程的消息队列上 hwnd 值为 NULL 的任何消息,
    //因此,如果 hWnd 为 NULL,则同时处理窗口消息和线程消息。
    //如果 hWnd 为 - 1,则 GetMessage 仅检索当前线程的消息队列中 hwnd 值为 NULL 的消息,
    //即当 hWnd 参数为 NULL 或 PostThreadMessage 时,PostMessage 发布的线程消息。
    while (0 != (bRet = GetMessage(&msg, NULL, 0, 0)))
    {
        if (-1 == bRet)
        {
            break;
        }

        //类捕获消息处理与对话框 TAB 按键处理
        if (PreTranslateMessage(&msg) || ::IsDialogMessage(m_hWnd, &msg))
        {
            continue;
        }

        ::TranslateMessage(&msg);
        ::DispatchMessage(&msg);
    }

    return msg.wParam;
}

INT_PTR WINAPI CDialogBase::DialogProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    CDialogBase* pThis = (CDialogBase*)GetProp(hWnd, _T("this"));
    //CDialogBase* pThis = (CDialogBase*)::GetWindowLongPtr(hWnd, GWLP_USERDATA);
    if (pThis)
    {
        return pThis->ThisDialogProc(uMsg, wParam, lParam);
    }

    if (WM_INITDIALOG == uMsg)
    {
        pThis = (CDialogBase*)lParam;
        if (pThis)
        {
            pThis->m_hWnd = hWnd;
            ::SetProp(hWnd, _T("this"), pThis);
            //::SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)pThis);
            pThis->ThisDialogProc(uMsg, wParam, lParam);
            if (pThis->m_bCenter)
            {
                pThis->MoveToCenter(::GetParent(hWnd));
            }
        }

        return (INT_PTR)TRUE;
    }

    return (INT_PTR)FALSE;
}

INT_PTR CDialogBase::ThisDialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    const WND_DLG_MSGMAP* pEntry = GetMessageMap();
    LRESULT lResult = 0;

    while (pEntry)
    {
        const WND_DLG_MSGMAP_ENTRY* lpEntries = pEntry->lpEntries;
        while (lpEntries->m_pfn)
        {
            if (uMsg == lpEntries->m_nMessage)
            {
                //处理 WM_COMMAND 消息
                if (WM_COMMAND == uMsg)
                {
                    WORD wID = LOWORD(wParam);
                    WORD wNotify = HIWORD(wParam);

                    if (wID == lpEntries->m_nID)
                    {
                        lResult = (this->*lpEntries->m_pfn)(wParam, lParam);
                        break;
                    }
                }
                else
                {
                    lResult = (this->*lpEntries->m_pfn)(wParam, lParam);
                    break;
                }
            }

            lpEntries++;
        }

        //消息已经处理
        if (lResult)
        {
            ::SetWindowLongPtr(m_hWnd, DWLP_MSGRESULT, lResult);
            return lResult;
        }

        //获取基类的消息映射
        pEntry = pEntry->pfnGetBaseMap();
    }

    return FALSE;
}

VOID CDialogBase::SetResourceID(UINT resourceID)
{
    m_uResID = resourceID;
}

VOID CDialogBase::ShowWindow(int cmdShow)
{
    ::ShowWindow(m_hWnd, cmdShow);
}

BOOL CDialogBase::IsVisiable() const
{
    return ::IsWindowVisible(m_hWnd);
}

void CDialogBase::MoveToCenter(HWND hParent)
{
    RECT rectParent = { 0 };
    RECT rectChild = { 0 };
    LONG dwParentW = 0;
    LONG dwParentH = 0;
    LONG dwChildW = 0;
    LONG dwChildH = 0;

    if (NULL == hParent)
    {
        hParent = ::GetDesktopWindow();
    }

    ::GetWindowRect(m_hWnd, &rectChild);
    ::GetWindowRect(hParent, &rectParent);

    dwParentW = rectParent.right - rectParent.left;
    dwParentH = rectParent.bottom - rectParent.top;
    dwChildW = rectChild.right - rectChild.left;
    dwChildH = rectChild.bottom - rectChild.top;

    ::MoveWindow(
        m_hWnd, 
        rectParent.left + (dwParentW - dwChildW) / 2,
        rectParent.top + (dwParentH - dwChildH) / 2, 
        dwChildW, 
        dwChildH, 
        TRUE
    );
}

_tstring CDialogBase::LoadString(UINT uID)
{
    static TCHAR szBuf[1024] = { 0 };
    ::LoadString(GetModuleHandle(NULL), uID, szBuf, _countof(szBuf));
    return szBuf;
}

LRESULT CDialogBase::OnLanguageChange(WPARAM wParam, LPARAM lParam)
{
    return TRUE;
}

VOID CDialogBase::ModifyMenuText(HMENU hMnu, UINT uPosition, LPCTSTR lpNewItem, BOOL fByPos)
{
    MENUITEMINFO mii = { 0 };
    TCHAR szBuf[MAX_PATH] = { 0 };
    mii.cbSize = sizeof(mii);
    mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_DATA;
    mii.fType = MFT_STRING;
    mii.dwTypeData = szBuf;
    mii.cch = _countof(szBuf);

    GetMenuItemInfo(hMnu, uPosition, fByPos, &mii);
    StringCchCopy(szBuf, _countof(szBuf), lpNewItem);
    SetMenuItemInfo(hMnu, uPosition, fByPos, &mii);
}

VOID CDialogBase::ModifyMenuText(HMENU hMnu, UINT uPosition, UINT resourceID, BOOL fByPos)
{
    ModifyMenuText(hMnu, uPosition, LoadString(resourceID).c_str(), fByPos);
}

HWND CDialogBase::GetWndHandle() const
{
    if (::IsWindow(m_hWnd))
    {
        return m_hWnd;
    }

    return NULL;
}

最基础的部分就这两部分, 接下来编写UI界面:

对话框设计如下, 功能简洁, 初期就先这样吧.

83be65e711234e9e914302bbd7658d2f.png

 资源对应的声明如下:

resource.h

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ 生成的包含文件。
// 供 CDialogSerialPort.rc 使用
//
#define IDI_ICON                        101
#define IDR_MENU                        102
#define IDS_FILE_EXIT                   103
#define IDS_SETTINGS_SHOW               104
#define IDS_SETTINGS_HIDE               105
#define IDD_DIALOG_SERIAL_PORT          108
#define IDD_DIALOG_ABOUT                110
#define IDC_BUTTON_SEND                 1001
#define IDC_EDIT_READ                   1002
#define IDC_EDIT_WRITE                  1003
#define IDC_COMBO_PORT                  1004
#define IDC_COMBO_SPEED                 1005
#define IDC_COMBO_DATA                  1006
#define IDC_COMBO_PARITY                1007
#define IDC_COMBO_STOP                  1008
#define IDC_BUTTON_OPEN                 1009
#define IDC_CHECK_READ_HEX              1010
#define IDC_BUTTON_CLEAR                1011
#define IDC_CHECK_READ_HEX2             1012
#define IDC_CHECK_WRITE_HEX             1012
#define IDC_EDIT_READ_INTERVAL          1016
#define IDC_EDIT_READ_TOTAL_MULTIPLIER  1017
#define IDC_EDIT_READ_TOTAL_CONSTANT    1018
#define IDC_EDIT_WRITE_TOTAL_MULTIPLIER 1019
#define IDC_EDIT_WRITE_TOTAL_CONSTANT   1020
#define IDC_BUTTON_TIMEOUT              1021
#define IDC_STATIC_TEXT                 1022
#define IDC_SYSLINK_ADDR                1023
#define IDC_EDIT_AUTO_TIME              1024
#define IDC_CHECK_AUTO_SEND             1025
#define ID_FILE_EXIT                    40001
#define ID_SETTINGS_SHOW                40002
#define ID_SETTINGS_HIDE                40003
#define ID_STR_CODE_UNICODE             40008
#define ID_SETTINGS_CODE_UNICODE        40009
#define ID_SETTINGS_CODE_ANSI           40010
#define ID_SETTINGS_CODE_UTF_8          40011
#define ID_HELP_ABOUT                   40013
#define ID_SETTINGS_TOPMOST             40015

// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        112
#define _APS_NEXT_COMMAND_VALUE         40016
#define _APS_NEXT_CONTROL_VALUE         1026
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif

CDialogSerialPort.rc

// Microsoft Visual C++ generated resource script.
//
#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
/
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"

/
#undef APSTUDIO_READONLY_SYMBOLS

/
// 中文(简体,中国) resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
#pragma code_page(936)

#ifdef APSTUDIO_INVOKED
/
//
// TEXTINCLUDE
//

1 TEXTINCLUDE 
BEGIN
    "resource.h\0"
END

2 TEXTINCLUDE 
BEGIN
    "#include ""winres.h""\r\n"
    "\0"
END

3 TEXTINCLUDE 
BEGIN
    "\r\n"
    "\0"
END

#endif    // APSTUDIO_INVOKED


/
//
// Icon
//

// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_ICON                ICON                    "icon.ico"


/
//
// Version
//

VS_VERSION_INFO VERSIONINFO
 FILEVERSION 1,0,0,1
 PRODUCTVERSION 1,0,0,1
 FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
 FILEFLAGS 0x1L
#else
 FILEFLAGS 0x0L
#endif
 FILEOS 0x40004L
 FILETYPE 0x1L
 FILESUBTYPE 0x0L
BEGIN
    BLOCK "StringFileInfo"
    BEGIN
        BLOCK "080404b0"
        BEGIN
            VALUE "CompanyName", "TODO: <公司名>"
            VALUE "FileDescription", "TODO: <文件说明>"
            VALUE "FileVersion", "1.0.0.1"
            VALUE "InternalName", "CWindow.exe"
            VALUE "LegalCopyright", "Copyright (C) 2023"
            VALUE "OriginalFilename", "CWindow.exe"
            VALUE "ProductName", "TODO: <产品名>"
            VALUE "ProductVersion", "1.0.0.1"
        END
    END
    BLOCK "VarFileInfo"
    BEGIN
        VALUE "Translation", 0x804, 1200
    END
END


/
//
// Menu
//

IDR_MENU MENU
BEGIN
    POPUP "文件(&F)"
    BEGIN
        MENUITEM "退出(&X)",                      ID_FILE_EXIT
    END
    POPUP "设置(&S)"
    BEGIN
        MENUITEM "最前端显示(&T)",                   ID_SETTINGS_TOPMOST
        POPUP "文本编码(&C)"
        BEGIN
            MENUITEM "UNICODE",                     ID_SETTINGS_CODE_UNICODE, CHECKED
            MENUITEM "ANSI",                        ID_SETTINGS_CODE_ANSI
            MENUITEM "UTF-8",                       ID_SETTINGS_CODE_UTF_8
        END
        MENUITEM "显示(&S)",                      ID_SETTINGS_SHOW
        MENUITEM "隐藏(&H)",                      ID_SETTINGS_HIDE
    END
    POPUP "帮助(&H)"
    BEGIN
        MENUITEM "关于(&A)",                      ID_HELP_ABOUT
    END
END


/
//
// Dialog
//

IDD_DIALOG_SERIAL_PORT DIALOGEX 0, 0, 509, 301
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "串口助手 By FlameCyclone"
MENU IDR_MENU
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
    GROUPBOX        "串口配置",IDC_STATIC,6,4,121,290
    GROUPBOX        "状态配置",IDC_STATIC,13,19,103,110
    LTEXT           "串口号:",IDC_STATIC,21,33,28,8
    LTEXT           "波特率:",IDC_STATIC,21,52,28,8
    LTEXT           "数据位:",IDC_STATIC,21,71,28,8
    LTEXT           "校验位:",IDC_STATIC,21,90,28,8
    LTEXT           "停止位:",IDC_STATIC,21,109,28,8
    COMBOBOX        IDC_COMBO_PORT,58,31,48,30,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP
    COMBOBOX        IDC_COMBO_SPEED,58,50,48,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
    COMBOBOX        IDC_COMBO_DATA,58,69,48,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
    COMBOBOX        IDC_COMBO_PARITY,58,88,48,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
    COMBOBOX        IDC_COMBO_STOP,58,107,48,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
    GROUPBOX        "超时配置",IDC_STATIC,13,137,103,103
    LTEXT           "读间隔超时:",IDC_STATIC,21,153,44,8
    LTEXT           "读时间系数:",IDC_STATIC,21,170,44,8
    LTEXT           "读时间常量:",IDC_STATIC,21,187,44,8
    LTEXT           "写时间系数:",IDC_STATIC,21,204,44,8
    LTEXT           "写时间常量:",IDC_STATIC,21,221,44,8
    EDITTEXT        IDC_EDIT_READ_INTERVAL,71,150,35,14,ES_NUMBER
    EDITTEXT        IDC_EDIT_READ_TOTAL_MULTIPLIER,71,167,35,14,ES_NUMBER
    EDITTEXT        IDC_EDIT_READ_TOTAL_CONSTANT,71,184,35,14,ES_NUMBER
    EDITTEXT        IDC_EDIT_WRITE_TOTAL_MULTIPLIER,71,201,35,14,ES_NUMBER
    EDITTEXT        IDC_EDIT_WRITE_TOTAL_CONSTANT,71,218,35,14,ES_NUMBER
    PUSHBUTTON      "打开串口",IDC_BUTTON_OPEN,13,264,103,21
    GROUPBOX        "接收",IDC_STATIC,134,4,368,192
    CONTROL         "HEX",IDC_CHECK_READ_HEX,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,464,19,29,10
    EDITTEXT        IDC_EDIT_READ,142,19,316,168,ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY | WS_VSCROLL | WS_HSCROLL
    PUSHBUTTON      "清空",IDC_BUTTON_CLEAR,462,170,32,17
    GROUPBOX        "发送",IDC_STATIC,134,203,368,92
    CONTROL         "HEX",IDC_CHECK_WRITE_HEX,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,464,217,29,10
    EDITTEXT        IDC_EDIT_WRITE,142,217,316,72,ES_MULTILINE | ES_AUTOHSCROLL | ES_WANTRETURN | WS_VSCROLL | WS_HSCROLL
    PUSHBUTTON      "发送",IDC_BUTTON_SEND,462,270,32,17
    EDITTEXT        IDC_EDIT_AUTO_TIME,68,244,30,14,ES_NUMBER
    CONTROL         "自动发送",IDC_CHECK_AUTO_SEND,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,246,48,10
    LTEXT           "ms",IDC_STATIC,101,247,10,8
END

IDD_DIALOG_ABOUT DIALOGEX 0, 0, 149, 116
STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "关于"
FONT 12, "Microsoft Sans Serif", 400, 0, 0x0
BEGIN
    PUSHBUTTON      "关闭",IDOK,58,96,31,14
    CTEXT           "FC串口助手\r\n\r\nFlameCyclone\r\n\r\n2023.5.29\r\n\r\n版权我有,  盗版难究",IDC_STATIC_TEXT,17,10,113,70,NOT WS_GROUP
    CONTROL         "<a href=""http://flamecyclone.ysepan.com/"">访问 FlameCyclone 的网盘主页</a>",IDC_SYSLINK_ADDR,
                    "SysLink",0x0,21,82,106,15
END


/
//
// DESIGNINFO
//

#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO
BEGIN
    IDD_DIALOG_SERIAL_PORT, DIALOG
    BEGIN
    END

    IDD_DIALOG_ABOUT, DIALOG
    BEGIN
        LEFTMARGIN, 7
        TOPMARGIN, 7
    END
END
#endif    // APSTUDIO_INVOKED


/
//
// AFX_DIALOG_LAYOUT
//

IDD_DIALOG_SERIAL_PORT AFX_DIALOG_LAYOUT
BEGIN
    0
END

IDD_DIALOG_ABOUT AFX_DIALOG_LAYOUT
BEGIN
    0
END


/
//
// String Table
//

STRINGTABLE
BEGIN
    IDS_FILE_EXIT           "退出(&X)"
    IDS_SETTINGS_SHOW       "显示(&S)"
    IDS_SETTINGS_HIDE       "隐藏(&H)"
END

#endif    // 中文(简体,中国) resources
/



#ifndef APSTUDIO_INVOKED
/
//
// Generated from the TEXTINCLUDE 3 resource.
//


/
#endif    // not APSTUDIO_INVOKED

接着实现对话框的消息:

主对话框声明:

CDialogSerialPort.h

#pragma once
#include "CWindow/CDialogBase.h"
#include <vector>
#include "Win32Utils/CSerialPort.h"
#include <imm.h>
#include "CAboutDialog.h"

#ifdef UNICODE
using _tstring = std::wstring;
#else
using _tstring = std::string;
#endif

class CDialogSerialPort :
	public CDialogBase
{
public:


protected:

    dlg_msg LRESULT OnInitDialog(WPARAM wParam, LPARAM lParam);
	dlg_msg LRESULT OnClose(WPARAM wParam, LPARAM lParam);
    dlg_msg LRESULT OnTrayIcon(WPARAM wParam, LPARAM lParam);
    dlg_msg LRESULT OnRButtonUp(WPARAM wParam, LPARAM lParam); 
    dlg_msg LRESULT OnColor(WPARAM wParam, LPARAM lParam);
    dlg_msg LRESULT OnTimer(WPARAM wParam, LPARAM lParam);

    dlg_msg LRESULT OnCommandExit(WPARAM wParam, LPARAM lParam);
    dlg_msg LRESULT OnCommandShow(WPARAM wParam, LPARAM lParam);
    dlg_msg LRESULT OnCommandHide(WPARAM wParam, LPARAM lParam);
    dlg_msg LRESULT OnCommandReadClear(WPARAM wParam, LPARAM lParam);
    dlg_msg LRESULT OnCommandReadHexShow(WPARAM wParam, LPARAM lParam);
    dlg_msg LRESULT OnCommandWriteHexShow(WPARAM wParam, LPARAM lParam);
    dlg_msg LRESULT OnCommandUnicode(WPARAM wParam, LPARAM lParam);
    dlg_msg LRESULT OnCommandUtf8(WPARAM wParam, LPARAM lParam);
    dlg_msg LRESULT OnCommandAnsi(WPARAM wParam, LPARAM lParam);
    dlg_msg LRESULT OnCommandTopmost(WPARAM wParam, LPARAM lParam);

    dlg_msg LRESULT OnStatePort(WPARAM wParam, LPARAM lParam);
    dlg_msg LRESULT OnStateSpeed(WPARAM wParam, LPARAM lParam);
    dlg_msg LRESULT OnStateData(WPARAM wParam, LPARAM lParam);
    dlg_msg LRESULT OnStateParity(WPARAM wParam, LPARAM lParam);
    dlg_msg LRESULT OnStateStop(WPARAM wParam, LPARAM lParam);
    dlg_msg LRESULT OnCommOpen(WPARAM wParam, LPARAM lParam);
    dlg_msg LRESULT OnCommSend(WPARAM wParam, LPARAM lParam);
    dlg_msg LRESULT OnApplyTime(WPARAM wParam, LPARAM lParam);
    dlg_msg LRESULT OnCommandAbout(WPARAM wParam, LPARAM lParam);
    dlg_msg LRESULT OnCommandAutoSend(WPARAM wParam, LPARAM lParam);

protected:

    virtual BOOL PreTranslateMessage(LPMSG pMsg);

private:
    void ShowTrayIcon();
    void DeleteTrayIcon();
    VOID CreatePopMenu(int xPos, int yPos, bool isTop);
    bool ShowForeground(HWND hWnd);
    int GetAllSerials(std::vector<_tstring>& CommList);
    void UpdateSerialDcb();
    void ApplyTimeOut();

    _tstring HexToStr(LPCBYTE lpData, size_t nSize);
    int StrToHex(const _tstring& str, LPBYTE lpData, size_t nSize);

private:
    HMENU m_hPopMenu = NULL;
    HMENU m_hMainMenu = NULL;
    HWND m_hHideWnd = NULL;
    CSerialPort m_SerialPort;
    DCB m_SerialDcb;
    _tstring m_strRead;
    bool m_bReadHex = false;
    bool m_bWriteHex = false;
    size_t m_nReadCount = 0;
    HIMC m_hImc = nullptr;
    HWND m_hWndSend = nullptr;
    HBRUSH m_hBrush = nullptr;
    CAboutDialog m_dlgAbout;
    int m_nStrCode = 0;
    UINT m_uTimer = 0;

	DECLARE_DLG_MESSAGE_MAP()
};

主对话框实现:

CDialogSerialPort.cpp

#include "CDialogSerialPort.h"
#include "Win32Utils/CStrUtils.h"
#include <Windows.h>
#include <strsafe.h>
#include <thread>
#include "resource.h"
#pragma comment (lib ,"imm32.lib") 

#define WM_TRAYICON                 (WM_USER + 10)

BEGIN_DLG_MESSAGE_MAP(CDialogSerialPort, CDialogBase)
    ON_DLG_MESSAGE(WM_INITDIALOG, &CDialogSerialPort::OnInitDialog)
    ON_DLG_MESSAGE(WM_TRAYICON, &CDialogSerialPort::OnTrayIcon)
    ON_DLG_MESSAGE(WM_CLOSE, &CDialogSerialPort::OnClose)
    ON_DLG_MESSAGE(WM_RBUTTONUP, &CDialogSerialPort::OnRButtonUp)
    ON_DLG_MESSAGE(WM_CTLCOLORSTATIC, &CDialogSerialPort::OnColor)
    //ON_DLG_MESSAGE(WM_CTLCOLOREDIT, &CDialogSerialPort::OnColor)
    ON_DLG_MESSAGE(WM_CTLCOLORBTN, &CDialogSerialPort::OnColor)
    ON_DLG_MESSAGE(WM_CTLCOLORDLG, &CDialogSerialPort::OnColor)
    ON_DLG_MESSAGE(WM_TIMER, &CDialogSerialPort::OnTimer)

    ON_DLG_COMMAND(ID_FILE_EXIT, &CDialogSerialPort::OnCommandExit)
    ON_DLG_COMMAND(ID_SETTINGS_SHOW, &CDialogSerialPort::OnCommandShow)
    ON_DLG_COMMAND(ID_SETTINGS_HIDE, &CDialogSerialPort::OnCommandHide)
    ON_DLG_COMMAND(IDC_CHECK_READ_HEX, &CDialogSerialPort::OnCommandReadHexShow)
    ON_DLG_COMMAND(IDC_CHECK_WRITE_HEX, &CDialogSerialPort::OnCommandWriteHexShow)
    ON_DLG_COMMAND(IDC_BUTTON_CLEAR, &CDialogSerialPort::OnCommandReadClear)

    ON_DLG_COMMAND(IDC_COMBO_PORT, &CDialogSerialPort::OnStatePort)
    ON_DLG_COMMAND(IDC_COMBO_SPEED, &CDialogSerialPort::OnStateSpeed)
    ON_DLG_COMMAND(IDC_COMBO_DATA, &CDialogSerialPort::OnStateData)
    ON_DLG_COMMAND(IDC_COMBO_PARITY, &CDialogSerialPort::OnStateParity)
    ON_DLG_COMMAND(IDC_COMBO_STOP, &CDialogSerialPort::OnStateStop)
    ON_DLG_COMMAND(IDC_BUTTON_OPEN, &CDialogSerialPort::OnCommOpen)
    ON_DLG_COMMAND(IDC_BUTTON_SEND, &CDialogSerialPort::OnCommSend)
    ON_DLG_COMMAND(IDC_BUTTON_TIMEOUT, &CDialogSerialPort::OnApplyTime)
    ON_DLG_COMMAND(ID_HELP_ABOUT, &CDialogSerialPort::OnCommandAbout)
    ON_DLG_COMMAND(ID_SETTINGS_CODE_UNICODE, &CDialogSerialPort::OnCommandUnicode)
    ON_DLG_COMMAND(ID_SETTINGS_CODE_UTF_8, &CDialogSerialPort::OnCommandUtf8)
    ON_DLG_COMMAND(ID_SETTINGS_CODE_ANSI, &CDialogSerialPort::OnCommandAnsi)
    ON_DLG_COMMAND(IDC_CHECK_AUTO_SEND, &CDialogSerialPort::OnCommandAutoSend)
    ON_DLG_COMMAND(ID_SETTINGS_TOPMOST, &CDialogSerialPort::OnCommandTopmost)
    
END_DLG_MESSAGE_MAP()

BOOL CDialogSerialPort::PreTranslateMessage(LPMSG pMsg)
{
    if (WM_KEYDOWN == pMsg->message && pMsg->hwnd == m_hWndSend && m_bWriteHex)
    {
        TCHAR ch = (TCHAR)pMsg->wParam;
        SHORT ctrlState = GetAsyncKeyState(VK_CONTROL) & 0x8000;
        if (ctrlState)
        {
            if (ch == _T('C') ||
                ch == _T('V') ||
                ch == _T('X') ||
                ch == _T('A')
                )
            {
                return FALSE;
            }
            else
            {
                return TRUE;
            }
        }

        if (VK_BACK == ch || VK_SPACE == ch || VK_TAB == ch || VK_RETURN == ch)
        {
            return FALSE;
        }

        if ((ch >= _T('0') && ch <= _T('9')) ||
            (ch >= _T('A') && ch <= _T('F'))
            )
        {
            return FALSE;
        }

        return TRUE;
    }

    return FALSE;
}

int CDialogSerialPort::GetAllSerials(std::vector<_tstring>& CommList)
{
    HKEY hKey = nullptr;
    int nretval = 0;
    nretval = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE,
        _T("Hardware\\DeviceMap\\SerialComm"),
        NULL, KEY_READ, &hKey);

    int i = 0;
    if (nretval == ERROR_SUCCESS)
    {
        TCHAR szPortName[MAX_PATH];

        DWORD dwLong, dwSize;
        while (true)
        {
            TCHAR szCommName[MAX_PATH] = { 0 };
            dwLong = MAX_PATH;
            dwSize = MAX_PATH;
            nretval = RegEnumValue(hKey, i, szPortName, &dwLong,
                NULL, NULL, (PUCHAR)szCommName, &dwSize);
            if (nretval != ERROR_NO_MORE_ITEMS)
            {
                CommList.push_back(szCommName);
            }
            else
            {
                break;
            }
            i++;
        }
        RegCloseKey(hKey);
    }

    return i;
}

void CDialogSerialPort::UpdateSerialDcb()
{
    // 波特率
    {
        TCHAR szBuf[MAX_PATH] = { 0 };
        HWND hWndSpeed = GetDlgItem(m_hWnd, IDC_COMBO_SPEED);
        LRESULT nCurSel = SendMessage(hWndSpeed, CB_GETCURSEL, 0, 0);
        if (CB_ERR != nCurSel)
        {
            SendMessage(hWndSpeed, CB_GETLBTEXT, nCurSel, (LPARAM)szBuf);
            m_SerialDcb.BaudRate = _tcstoul(szBuf, nullptr, 10);
        }
    }

    // 数据位
    {
        HWND hWndSpeed = GetDlgItem(m_hWnd, IDC_COMBO_DATA);
        LRESULT nCurSel = SendMessage(hWndSpeed, CB_GETCURSEL, 0, 0);
        if (0 <= nCurSel && nCurSel <= 3)
        {
            m_SerialDcb.ByteSize = (BYTE)(5 + nCurSel);
        }
        else
        {
            m_SerialDcb.ByteSize = 8;
        }
    }

    // 校验位
    {
        HWND hWndParity = GetDlgItem(m_hWnd, IDC_COMBO_PARITY);
        LRESULT nCurSel = SendMessage(hWndParity, CB_GETCURSEL, 0, 0);

        if (NOPARITY <= nCurSel && nCurSel <= SPACEPARITY)
        {
            m_SerialDcb.Parity = (BYTE)nCurSel;
        }
        else
        {
            m_SerialDcb.Parity = NOPARITY;
        }
    }

    // 停止位
    {
        HWND hWndStop = GetDlgItem(m_hWnd, IDC_COMBO_STOP);
        LRESULT nCurSel = SendMessage(hWndStop, CB_GETCURSEL, 0, 0);
        if (ONESTOPBIT <= nCurSel && nCurSel <= TWOSTOPBITS)
        {
            m_SerialDcb.StopBits = (BYTE)nCurSel;
        }
        else
        {
            m_SerialDcb.StopBits = ONESTOPBIT;
        }
    }

    m_SerialPort.SetState(&m_SerialDcb);
}

void CDialogSerialPort::ApplyTimeOut()
{
    if (m_SerialPort.IsOpen())
    {
        DWORD ReadInterval = ::GetDlgItemInt(m_hWnd, IDC_EDIT_READ_INTERVAL, nullptr, false);
        DWORD ReadTotalMultiplier = ::GetDlgItemInt(m_hWnd, IDC_EDIT_READ_TOTAL_MULTIPLIER, nullptr, false);
        DWORD ReadTotalConstant = ::GetDlgItemInt(m_hWnd, IDC_EDIT_READ_TOTAL_CONSTANT, nullptr, false);
        DWORD WriteTotalMultiplier = ::GetDlgItemInt(m_hWnd, IDC_EDIT_WRITE_TOTAL_MULTIPLIER, nullptr, false);
        DWORD WriteTotalConstant = ::GetDlgItemInt(m_hWnd, IDC_EDIT_WRITE_TOTAL_CONSTANT, nullptr, false);

        m_SerialPort.Purge(PURGE_TXCLEAR | 
            PURGE_RXCLEAR | 
            PURGE_TXABORT | 
            PURGE_RXABORT);

        m_SerialPort.ClearError();
        m_SerialPort.SetTimeOut(ReadInterval,
            ReadTotalMultiplier,
            ReadTotalConstant,
            WriteTotalMultiplier,
            WriteTotalConstant);
    }
}

LRESULT CDialogSerialPort::OnInitDialog(WPARAM wParam, LPARAM lParam)
{
    // 设置图标
    HICON hIcon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON));
    if (hIcon)
    {
        ::SendMessage(m_hWnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
        ::SendMessage(m_hWnd, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
    }

    m_hHideWnd = CreateWindow(
        _T("Static"), _T("Hide"), WS_CHILDWINDOW, 0, 0, 0, 0,
        m_hWnd, NULL, GetModuleHandle(NULL), NULL);
    if (m_hHideWnd)
    {
        ::SetParent(m_hHideWnd, NULL);
    }

    m_hMainMenu = ::GetMenu(m_hWnd);
    if (nullptr == m_hMainMenu)
    {
        m_hMainMenu = ::LoadMenu(GetModuleHandle(NULL), MAKEINTRESOURCE(IDR_MENU));
    }
    if (m_hMainMenu)
    {
        ::SetMenu(m_hWnd, m_hMainMenu);
        ShowTrayIcon();
    }

    {
        HWND hWndPort = GetDlgItem(m_hWnd, IDC_COMBO_PORT);
        std::vector<_tstring> CommList;
        GetAllSerials(CommList);
        for (auto item : CommList)
        {
            SendMessage(hWndPort, CB_ADDSTRING, 0, (LPARAM)item.c_str());
        }

        if (!CommList.empty())
        {
            SendMessage(hWndPort, CB_SETCURSEL, 0, 0);
        }
    }

    {
        std::vector<int> vSpeed = {
        75,
        110,
        134,
        150,
        300,
        600,
        1200,
        1800,
        2400,
        4800,
        7200,
        9600,
        14400,
        19200,
        38400,
        56000,
        57600,
        115200,
        128000,
        256000,
        };

        HWND hWndSpeed = GetDlgItem(m_hWnd, IDC_COMBO_SPEED);
        for (auto item : vSpeed)
        {
            TCHAR szBuf[MAX_PATH] = { 0 };
            _ultot_s(item, szBuf, 10);
            SendMessage(hWndSpeed, CB_ADDSTRING, 0, (LPARAM)szBuf);
        }

        if (!vSpeed.empty())
        {
            SendMessage(hWndSpeed, CB_SETCURSEL, vSpeed.size() - 3, 0);
        }
    }

    {
        std::vector<int> vData = { 5,6,7,8 };
        HWND hWndData = GetDlgItem(m_hWnd, IDC_COMBO_DATA);
        for (auto item : vData)
        {
            TCHAR szBuf[MAX_PATH] = { 0 };
            _ultot_s(item, szBuf, 10);
            SendMessage(hWndData, CB_ADDSTRING, 0, (LPARAM)szBuf);
        }

        if (!vData.empty())
        {
            SendMessage(hWndData, CB_SETCURSEL, 3, 0);
        }
    }

    {
        std::vector<_tstring> vParity = {
        _T("None"),
        _T("Odd"),
        _T("Even"),
        _T("Mark"),
        _T("Space")

        };
        HWND hWndParity = GetDlgItem(m_hWnd, IDC_COMBO_PARITY);
        for (auto item : vParity)
        {
            SendMessage(hWndParity, CB_ADDSTRING, 0, (LPARAM)item.c_str());
        }

        if (!vParity.empty())
        {
            SendMessage(hWndParity, CB_SETCURSEL, 0, 0);
        }
    }

    {
        std::vector<_tstring> vParity = {
        _T("1"),
        _T("1.5"),
        _T("2"),

        };
        HWND hWndStop = GetDlgItem(m_hWnd, IDC_COMBO_STOP);
        for (auto item : vParity)
        {
            SendMessage(hWndStop, CB_ADDSTRING, 0, (LPARAM)item.c_str());
        }

        if (!vParity.empty())
        {
            SendMessage(hWndStop, CB_SETCURSEL, 0, 0);
        }
    }

    CheckDlgButton(m_hWnd, IDC_CHECK_READ_HEX, BST_CHECKED);
    m_bReadHex = true;
    CheckDlgButton(m_hWnd, IDC_CHECK_WRITE_HEX, BST_CHECKED);
    m_bWriteHex = true;
    m_hWndSend = ::GetDlgItem(m_hWnd, IDC_EDIT_WRITE);
    ::SetDlgItemText(m_hWnd, IDC_EDIT_AUTO_TIME, _T("1000"));
    m_hImc = ImmAssociateContext(m_hWndSend, nullptr);

    UpdateSerialDcb();

    ::SetDlgItemText(m_hWnd, IDC_EDIT_WRITE, _T("E7 00 00 22 02 00 00 00 0F 33 ED"));

    ::SetDlgItemInt(m_hWnd, IDC_EDIT_READ_INTERVAL, 50, false);
    ::SetDlgItemInt(m_hWnd, IDC_EDIT_READ_TOTAL_MULTIPLIER, 5, false);
    ::SetDlgItemInt(m_hWnd, IDC_EDIT_READ_TOTAL_CONSTANT, 500, false);
    ::SetDlgItemInt(m_hWnd, IDC_EDIT_WRITE_TOTAL_MULTIPLIER, 0, false);
    ::SetDlgItemInt(m_hWnd, IDC_EDIT_WRITE_TOTAL_CONSTANT, 0, false);

    return TRUE;
}

LRESULT CDialogSerialPort::OnClose(WPARAM wParam, LPARAM lParam)
{
    if (m_hBrush)
    {
        ::DeleteObject(m_hBrush);
    }

    DeleteTrayIcon();
    return (LRESULT)FALSE;
}

LRESULT CDialogSerialPort::OnCommandExit(WPARAM wParam, LPARAM lParam)
{
    m_dlgAbout.EndDialog(0);
    EndDialog(IDOK);
    return (LRESULT)TRUE;
}

LRESULT CDialogSerialPort::OnCommandShow(WPARAM wParam, LPARAM lParam)
{
    ShowWindow(SW_SHOW);
    return (LRESULT)TRUE;
}

LRESULT CDialogSerialPort::OnCommandHide(WPARAM wParam, LPARAM lParam)
{
    ShowWindow(SW_HIDE);
    return (LRESULT)TRUE;
}

LRESULT CDialogSerialPort::OnCommandReadHexShow(WPARAM wParam, LPARAM lParam)
{
    HWND hReadHex = ::GetDlgItem(m_hWnd, IDC_CHECK_READ_HEX);
    LRESULT hr = ::SendMessage(hReadHex, BM_GETCHECK, 0, 0);

    if (BST_CHECKED == hr)
    {
        m_bReadHex = true;
    }
    else
    {
        m_bReadHex = false;
    }

    return (LRESULT)TRUE;
}

_tstring CDialogSerialPort::HexToStr(LPCBYTE lpData, size_t nSize)
{
    _tstring strRes;
    TCHAR szBuf[MAX_PATH] = { 0 };

    for (int i = 0; i < nSize; i++)
    {
        if (i + 1 < nSize)
        {
            StringCchPrintf(szBuf, _countof(szBuf), _T("%0.2X "), lpData[i]);
        }
        else
        {
            StringCchPrintf(szBuf, _countof(szBuf), _T("%0.2X"), lpData[i]);
        }
        strRes += szBuf;
    }

    return strRes;
}

int CDialogSerialPort::StrToHex(const _tstring& str, LPBYTE lpData, size_t nSize)
{
    _tstring strWide;
    std::string strMulti;
    int nDataIndex = 0;

    strWide = str;
    _tstring strHex;
    int nIndex = 0;
    for (int i = 0; i < strWide.size(); i++)
    {
        if (0 == nIndex)
        {
            strHex.clear();
        }

        if (str[i] == _T(' ') || str[i] == _T('\r') || str[i] == _T('\n'))
        {
            continue;
        }

        strHex += str[i];
        nIndex++;

        if (2 == nIndex && nDataIndex < nSize)
        {
            nIndex = 0;
            lpData[nDataIndex++] = (BYTE)_tcstoul(strHex.c_str(), nullptr, 16);
        }
    }

    return nDataIndex;
}

LRESULT CDialogSerialPort::OnApplyTime(WPARAM wParam, LPARAM lParam)
{
    ApplyTimeOut();
    return TRUE;
}

LRESULT CDialogSerialPort::OnCommandAbout(WPARAM wParam, LPARAM lParam)
{
    m_dlgAbout.DoModalEx(IDD_DIALOG_ABOUT, m_hWnd);
    return TRUE;
}

LRESULT CDialogSerialPort::OnCommandAutoSend(WPARAM wParam, LPARAM lParam)
{
    LRESULT ret = SendMessage(
        ::GetDlgItem(m_hWnd, IDC_CHECK_AUTO_SEND), 
        BM_GETCHECK, 
        0, 
        0);

    if (BST_CHECKED == ret)
    {
        UINT uTime = ::GetDlgItemInt(m_hWnd, IDC_EDIT_AUTO_TIME, nullptr, false);
        if (uTime < USER_TIMER_MINIMUM)
        {
            uTime = USER_TIMER_MINIMUM;
            ::SetDlgItemInt(m_hWnd, IDC_EDIT_AUTO_TIME, uTime, false);
        }
        m_uTimer = ::SetTimer(m_hWnd, 0x200, uTime, nullptr);
        ::EnableWindow(::GetDlgItem(m_hWnd, IDC_EDIT_AUTO_TIME), false);
    }
    else
    {
        ::KillTimer(m_hWnd, m_uTimer);
        ::EnableWindow(::GetDlgItem(m_hWnd, IDC_EDIT_AUTO_TIME), true);
    }

    return TRUE;
}

LRESULT CDialogSerialPort::OnCommandWriteHexShow(WPARAM wParam, LPARAM lParam)
{
    HWND hWriteHex = ::GetDlgItem(m_hWnd, IDC_CHECK_WRITE_HEX);
    LRESULT ret = SendMessage(hWriteHex, BM_GETCHECK, 0, 0);
    static TCHAR szWriteBuf[32768] = { 0 };

    if (BST_CHECKED == ret)
    {
        m_bWriteHex = true;
        UINT uLen = ::GetDlgItemText(m_hWnd, IDC_EDIT_WRITE, szWriteBuf, _countof(szWriteBuf));
        _tstring strText;

        if (0 == m_nStrCode)
        {
            _tstring strDest = szWriteBuf;
            strText = HexToStr((LPBYTE)strDest.c_str(), uLen * sizeof(TCHAR));
        }
        else if (1 == m_nStrCode)
        {
            std::string strDest = CStrUtils::TStrToAStr(szWriteBuf);
            strText = HexToStr((LPBYTE)strDest.c_str(), strDest.size());
        }
        else if (2 == m_nStrCode)
        {
            std::string strDest = CStrUtils::TStrToU8Str(szWriteBuf);
            strText = HexToStr((LPBYTE)strDest.c_str(), strDest.size());
        }

        ::SetDlgItemText(m_hWnd, IDC_EDIT_WRITE, strText.c_str());
        HWND hEditSend = ::GetDlgItem(m_hWnd, IDC_EDIT_WRITE);
        m_hImc = ::ImmAssociateContext(hEditSend, nullptr);
    }
    else
    {
        m_bWriteHex = false;
        ::GetDlgItemText(m_hWnd, IDC_EDIT_WRITE, szWriteBuf, _countof(szWriteBuf));
        static BYTE szCharBuf[32768] = { 0 };
        ::ZeroMemory(szCharBuf, sizeof(szCharBuf));
        StrToHex(szWriteBuf, szCharBuf, sizeof(szCharBuf));

        if (0 == m_nStrCode)
        {
            ::SetDlgItemText(m_hWnd, IDC_EDIT_WRITE, (LPCTSTR)szCharBuf);
        }
        else if (1 == m_nStrCode)
        {
            ::SetDlgItemText(m_hWnd, IDC_EDIT_WRITE, CStrUtils::AStrToTStr((LPCSTR)szCharBuf).c_str());
        }

        else if (2 == m_nStrCode)
        {
            ::SetDlgItemText(m_hWnd, IDC_EDIT_WRITE, CStrUtils::U8StrToTStr((LPCSTR)szCharBuf).c_str());
        }

        HWND hEditSend = ::GetDlgItem(m_hWnd, IDC_EDIT_WRITE);
        ::ImmAssociateContext(hEditSend, m_hImc);
    }

    return (LRESULT)TRUE;
}

LRESULT CDialogSerialPort::OnCommandUnicode(WPARAM wParam, LPARAM lParam)
{
    m_nStrCode = 0;
    for (int i = ID_SETTINGS_CODE_UNICODE; i <= ID_SETTINGS_CODE_UTF_8; i++)
    {
        ::CheckMenuItem(::GetMenu(m_hWnd), i, MF_BYCOMMAND | MF_UNCHECKED);
    }
    ::CheckMenuItem(::GetMenu(m_hWnd), wParam, MF_BYCOMMAND | MF_CHECKED);
    return (LRESULT)TRUE;
}

LRESULT CDialogSerialPort::OnCommandAnsi(WPARAM wParam, LPARAM lParam)
{
    m_nStrCode = 1;
    for (int i = ID_SETTINGS_CODE_UNICODE; i <= ID_SETTINGS_CODE_UTF_8; i++)
    {
        ::CheckMenuItem(::GetMenu(m_hWnd), i, MF_BYCOMMAND | MF_UNCHECKED);
    }
    ::CheckMenuItem(::GetMenu(m_hWnd), wParam, MF_BYCOMMAND | MF_CHECKED);
    return (LRESULT)TRUE;
}

LRESULT CDialogSerialPort::OnCommandTopmost(WPARAM wParam, LPARAM lParam)
{
    DWORD dwState = ::GetMenuState(GetMenu(m_hWnd), wParam, MF_BYCOMMAND | MF_CHECKED);
    ::SetWindowPos(m_hWnd, 
        MF_CHECKED & dwState ? HWND_NOTOPMOST : HWND_TOPMOST,
        0, 0, 0, 0, 
        SWP_NOSIZE | SWP_NOMOVE);
    ::CheckMenuItem(GetMenu(m_hWnd), wParam, MF_BYCOMMAND | (MF_CHECKED & dwState ? MF_UNCHECKED : MF_CHECKED));
    return (LRESULT)TRUE;
}

LRESULT CDialogSerialPort::OnCommandUtf8(WPARAM wParam, LPARAM lParam)
{
    m_nStrCode = 2;
    for (int i = ID_SETTINGS_CODE_UNICODE; i <= ID_SETTINGS_CODE_UTF_8; i++)
    {
        ::CheckMenuItem(::GetMenu(m_hWnd), i, MF_BYCOMMAND | MF_UNCHECKED);
    }
    ::CheckMenuItem(::GetMenu(m_hWnd), wParam, MF_BYCOMMAND | MF_CHECKED);
    return (LRESULT)TRUE;
}

LRESULT CDialogSerialPort::OnCommandReadClear(WPARAM wParam, LPARAM lParam)
{
    m_strRead.clear();

    HWND hWndRead = ::GetDlgItem(m_hWnd, IDC_EDIT_READ);
    ::SetWindowText(hWndRead, _T(""));
    m_nReadCount = 0;
    return TRUE;
}

LRESULT CDialogSerialPort::OnStatePort(WPARAM wParam, LPARAM lParam)
{
    if (CBN_SELCHANGE == HIWORD(wParam))
    {
        UpdateSerialDcb();
        if (m_SerialPort.IsOpen())
        {
            m_SerialPort.Close();
            ::SetDlgItemText(m_hWnd, IDC_BUTTON_OPEN, _T("打开串口"));
            ::PostMessage(m_hWnd, WM_COMMAND, IDC_BUTTON_OPEN, 0);
        }
    }

    return TRUE;
}

LRESULT CDialogSerialPort::OnStateSpeed(WPARAM wParam, LPARAM lParam)
{
    if (CBN_SELCHANGE == HIWORD(wParam))
    {
        UpdateSerialDcb();
    }
    return TRUE;
}

LRESULT CDialogSerialPort::OnStateData(WPARAM wParam, LPARAM lParam)
{
    if (CBN_SELCHANGE == HIWORD(wParam))
    {
        UpdateSerialDcb();
    }
    return TRUE;
}

LRESULT CDialogSerialPort::OnStateParity(WPARAM wParam, LPARAM lParam)
{
    if (CBN_SELCHANGE == HIWORD(wParam))
    {
        UpdateSerialDcb();
    }
    return TRUE;
}

LRESULT CDialogSerialPort::OnStateStop(WPARAM wParam, LPARAM lParam)
{
    if (CBN_SELCHANGE == HIWORD(wParam))
    {
        UpdateSerialDcb();
    }
    return TRUE;
}

LRESULT CDialogSerialPort::OnCommOpen(WPARAM wParam, LPARAM lParam)
{
    if (m_SerialPort.IsOpen())
    {
        m_SerialPort.Close();
        ::SetDlgItemText(m_hWnd, IDC_BUTTON_OPEN, _T("打开串口"));

        ::EnableWindow(GetDlgItem(m_hWnd, IDC_EDIT_READ_INTERVAL), TRUE);
        ::EnableWindow(GetDlgItem(m_hWnd, IDC_EDIT_READ_TOTAL_MULTIPLIER), TRUE);
        ::EnableWindow(GetDlgItem(m_hWnd, IDC_EDIT_READ_TOTAL_CONSTANT), TRUE);
        ::EnableWindow(GetDlgItem(m_hWnd, IDC_EDIT_WRITE_TOTAL_MULTIPLIER), TRUE);
        ::EnableWindow(GetDlgItem(m_hWnd, IDC_EDIT_WRITE_TOTAL_CONSTANT), TRUE);
    }
    else
    {
        TCHAR szBuf[MAX_PATH] = { 0 };
        HWND hWndPort = GetDlgItem(m_hWnd, IDC_COMBO_PORT);
        LRESULT nCurSel = SendMessage(hWndPort, CB_GETCURSEL, 0, 0);
        bool bOpen = false;
        if (CB_ERR != nCurSel)
        {
            SendMessage(hWndPort, CB_GETLBTEXT, nCurSel, (LPARAM)szBuf);
            bOpen = m_SerialPort.Open(
                szBuf, 
                m_SerialDcb.BaudRate,
                m_SerialDcb.ByteSize,
                m_SerialDcb.Parity,
                m_SerialDcb.StopBits);

            ApplyTimeOut();
        }

        if (!bOpen)
        {
            MessageBox(m_hWnd, _T("打开串口失败!"), _T("提示"), MB_OK | MB_ICONERROR);
            return TRUE;
        }

        ::SetDlgItemText(m_hWnd, IDC_BUTTON_OPEN, _T("关闭串口"));
        ::EnableWindow(GetDlgItem(m_hWnd, IDC_EDIT_READ_INTERVAL), FALSE);
        ::EnableWindow(GetDlgItem(m_hWnd, IDC_EDIT_READ_TOTAL_MULTIPLIER), FALSE);
        ::EnableWindow(GetDlgItem(m_hWnd, IDC_EDIT_READ_TOTAL_CONSTANT), FALSE);
        ::EnableWindow(GetDlgItem(m_hWnd, IDC_EDIT_WRITE_TOTAL_MULTIPLIER), FALSE);
        ::EnableWindow(GetDlgItem(m_hWnd, IDC_EDIT_WRITE_TOTAL_CONSTANT), FALSE);

        std::thread([this]()->void {
            static BYTE szReadBuf[32768] = { 0 };
            bool bSuccess = false;
            DWORD dwReadSize = 0;

            HWND hWndRead = GetDlgItem(m_hWnd, IDC_EDIT_READ);
            while (m_SerialPort.IsOpen())
            {
                m_SerialPort.Purge(PURGE_RXCLEAR);
                bSuccess = m_SerialPort.Read(szReadBuf,
                    sizeof(szReadBuf),
                    &dwReadSize,
                    5000);

                if (bSuccess)
                {
                    TCHAR szBuf[MAX_PATH] = { 0 };
                    StringCchPrintf(szBuf, _countof(szBuf), _T("[%0.4d: %d Bytes] "), ++m_nReadCount, dwReadSize);
                    _tstring strText = szBuf;
                    szReadBuf[dwReadSize] = 0;

                    if (m_bReadHex)
                    {
                        szReadBuf[dwReadSize + 1] = 0;
                        strText += HexToStr((LPCBYTE)szReadBuf, dwReadSize);
                    }
                    else
                    {
                        _tstring strContent;
                        if (0 == m_nStrCode)
                        {
                            strContent = (LPCTSTR)szReadBuf;
                        }
                        if (1 == m_nStrCode)
                        {
                            strContent = CStrUtils::AStrToTStr((LPCSTR)szReadBuf);
                        }
                        if (2 == m_nStrCode)
                        {
                            strContent = CStrUtils::U8StrToTStr((LPCSTR)szReadBuf);
                        }

                        strText += strContent;
                    }

                    if (!m_strRead.empty())
                    {
                        m_strRead += _T("\r\n");
                    }

                    m_strRead += strText;
                    ::SendMessage(hWndRead, WM_SETREDRAW, FALSE, 0);
                    ::SetWindowText(hWndRead, m_strRead.c_str());
                    ::SendMessage(hWndRead, EM_SCROLL, SB_BOTTOM, 0);
                    ::SendMessage(hWndRead, WM_SETREDRAW, TRUE, 0);
                }
            }
            
            }).detach();
    }

    return TRUE;
}

LRESULT CDialogSerialPort::OnCommSend(WPARAM wParam, LPARAM lParam)
{
    if (m_SerialPort.IsOpen())
    {
        static TCHAR szBuf[32768] = { 0 };
        HWND hWndEditSend = GetDlgItem(m_hWnd, IDC_EDIT_WRITE);
        ::GetWindowText(hWndEditSend, szBuf, _countof(szBuf));
        _tstring strData = szBuf;

        if (strData.empty())
        {
            return TRUE;
        }

        m_SerialPort.Purge(PURGE_TXCLEAR);
        if (m_bWriteHex)
        {
            static BYTE szByteBuf[32768] = { 0 };
            ZeroMemory(szByteBuf, sizeof(szByteBuf));
            int nSize = StrToHex(strData, szByteBuf, sizeof(szByteBuf));
            m_SerialPort.Write(szByteBuf, nSize, nullptr, 1000);
        }
        else
        {
            if (0 == m_nStrCode)
            {
                size_t nSize = strData.size() * sizeof(TCHAR);
                m_SerialPort.Write(strData.c_str(), (DWORD)nSize, nullptr, (DWORD)1000);
            }
            else if (1 == m_nStrCode)
            {
                std::string strDest = CStrUtils::TStrToAStr(szBuf);
                m_SerialPort.Write(strDest.c_str(), (DWORD)strDest.size(), nullptr, (DWORD)1000);
            }
            else if (2 == m_nStrCode)
            {
                std::string strDest = CStrUtils::TStrToU8Str(szBuf);
                m_SerialPort.Write(strDest.c_str(), (DWORD)strDest.size(), nullptr, (DWORD)1000);
            }
        }
    }

    return TRUE;
}

LRESULT CDialogSerialPort::OnTrayIcon(WPARAM wParam, LPARAM lParam)
{
    if (WM_LBUTTONDBLCLK == lParam)
    {
        ShowForeground(m_hWnd);
    }

    if (NULL != m_hHideWnd)
    {
        ::SetForegroundWindow(m_hHideWnd);
    }

    if (WM_RBUTTONUP == lParam)
    {
        HWND hForegroundWindow = GetForegroundWindow();
        if (hForegroundWindow == m_hHideWnd || hForegroundWindow == m_hWnd)
        {
            POINT pt = { 0 };
            ::GetCursorPos(&pt);
            CreatePopMenu(pt.x, pt.y, false);
        }
    }

    return FALSE;
}

LRESULT CDialogSerialPort::OnRButtonUp(WPARAM wParam, LPARAM lParam)
{
    POINT pt = { 0 };
    ::GetCursorPos(&pt);
    CreatePopMenu(pt.x, pt.y, false);
    return (LRESULT)TRUE;
}

LRESULT CDialogSerialPort::OnColor(WPARAM wParam, LPARAM lParam)
{
    HDC hdc = (HDC)wParam;
    SetBkColor(hdc, RGB(255, 0, 0));

    SetBkMode(hdc, TRANSPARENT);
    if (NULL == m_hBrush)
    {
        m_hBrush = CreateSolidBrush(RGB(192, 224, 255));
    }

    return (LRESULT)m_hBrush;

    return (LRESULT)TRUE;
}

LRESULT CDialogSerialPort::OnTimer(WPARAM wParam, LPARAM lParam)
{
    if (wParam == m_uTimer)
    {
        ::PostMessage(m_hWnd, WM_COMMAND, IDC_BUTTON_SEND, 0);
    }

    return (LRESULT)TRUE;
}

bool CDialogSerialPort::ShowForeground(HWND hWnd)
{
    HWND hForeWnd = ::GetForegroundWindow();
    DWORD dwForeID = ::GetWindowThreadProcessId(hForeWnd, NULL);
    DWORD dwCurID = ::GetCurrentThreadId();
    ::AttachThreadInput(dwCurID, dwForeID, TRUE);
    BOOL isSuc = ::SetForegroundWindow(hWnd);
    ::AttachThreadInput(dwCurID, dwForeID, FALSE);

    if (isSuc)
    {
        //return true;
    }

    if (!::IsWindowVisible(hWnd))
    {
        ::ShowWindow(hWnd, SW_SHOW);
    }

    if (IsIconic(hWnd))
    {
        ::ShowWindow(hWnd, SW_SHOWNORMAL);
    }

    return ::SetForegroundWindow(hWnd);
}

void CDialogSerialPort::ShowTrayIcon()
{
    NOTIFYICONDATA nid = { 0 };
    nid.cbSize = sizeof(NOTIFYICONDATA);
    nid.hWnd = m_hWnd;
    nid.uID = 0;
    nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
    nid.uCallbackMessage = WM_TRAYICON;
    nid.hIcon = ::LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON));
    StringCchCopy(nid.szTip, _countof(nid.szTip), _T("Demo"));
    Shell_NotifyIcon(NIM_ADD, &nid);
}

void CDialogSerialPort::DeleteTrayIcon()
{
    NOTIFYICONDATA nid = { 0 };
    nid.cbSize = sizeof(nid);
    nid.hWnd = m_hWnd;
    nid.uID = 0;
    nid.uFlags = 0;
    Shell_NotifyIcon(NIM_DELETE, &nid);
}

VOID CDialogSerialPort::CreatePopMenu(int xPos, int yPos, bool isTop)
{
    MENUITEMINFO mii = { 0 };
    TCHAR szBuf[MAX_PATH] = { 0 };

    mii.cbSize = sizeof(mii);
    mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_SUBMENU | MIIM_ID;
    mii.fType = MFT_STRING | MFT_OWNERDRAW;
    mii.dwTypeData = szBuf;

    if (!m_hWnd)
    {
        return;
    }

    {
        HMENU hMenu = m_hMainMenu;
        ModifyMenuText(hMenu, ID_FILE_EXIT, IDS_FILE_EXIT, FALSE);
        ModifyMenuText(hMenu, ID_SETTINGS_SHOW, IDS_SETTINGS_SHOW, FALSE);
        ModifyMenuText(hMenu, ID_SETTINGS_HIDE, IDS_SETTINGS_HIDE, FALSE);
    }

    int nIndex = 0;

    if (NULL == m_hPopMenu)
    {
        m_hPopMenu = CreatePopupMenu();
        for (int i = 0; i < 4; i++)
        {
            mii.cch = _countof(szBuf);
            ::GetMenuItemInfo(GetSubMenu(m_hMainMenu, 1), i, TRUE, &mii);
            ::InsertMenuItem(m_hPopMenu, nIndex++, TRUE, &mii);
        }
        mii.cch = _countof(szBuf);
        ::GetMenuItemInfo(GetSubMenu(m_hMainMenu, 0), 0, TRUE, &mii);
        ::InsertMenuItem(m_hPopMenu, nIndex++, TRUE, &mii);
    }
    else
    {
        for (int i = 0; i < 4; i++)
        {
            mii.cch = _countof(szBuf);
            ::GetMenuItemInfo(GetSubMenu(m_hMainMenu, 1), i, TRUE, &mii);
            ::SetMenuItemInfo(m_hPopMenu, nIndex++, TRUE, &mii);
        }
        mii.cch = _countof(szBuf);
        ::GetMenuItemInfo(GetSubMenu(m_hMainMenu, 0), 0, TRUE, &mii);
        ::SetMenuItemInfo(m_hPopMenu, nIndex++, TRUE, &mii);
    }

    TrackPopupMenuEx(m_hPopMenu, TPM_LEFTALIGN | (isTop ? TPM_BOTTOMALIGN : TPM_TOPALIGN),
        xPos, yPos, m_hWnd, NULL);
}

主体编码就算完成了, 资源视图如下:

98fb42b069c84d4a997235134ddae959.png

 解决方案视图如下:

092361b6cead4ee69083cf64bed2d46d.png

运行效果如下:

 0bca1b2bb6b44680b6526a96a7bdd16e.png

 14ffb90c79574ebcb217395e31812b77.png

 资源链接详见: Win32api编写的串口助手源码与二进制资源-CSDN文库

 

 

 

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值