MFC实战项目——dongle客户端开发一

设置对话框背景图片

网上下载一张自己喜欢的图片

将图片导入到工程

设置对话框属性

方法一:在初始化函数OnInitDialog中添加如下一行代码即可实现功能(最简单的一种方法)

CDialogEx::SetBackgroundImage(IDB_BITMAP1);// 对话框设置背景图片

方法二(过程比较繁琐,不建议采用)

在主对话框类中添加一个CBrush的变量,用于为对话框添加背景

//添加1所在的位置:变量用于对话框添加背景
// CgydonglepcmaxDlg 对话框
class CgydonglepcmaxDlg : public CDialogEx
{
// 构造
public:
	CgydonglepcmaxDlg(CWnd* pParent = nullptr);	// 标准构造函数
	CBrush m_brush;//1、对话框添加背景
// 对话框数据
#ifdef AFX_DESIGN_TIME
	enum { IDD = IDD_GY_DONGLE_PC_MAX_DIALOG };
#endif
	protected:
	virtual void DoDataExchange(CDataExchange* pDX);	// DDX/DDV 支持

// 实现
protected:
	HICON m_hIcon;
	// 生成的消息映射函数
	virtual BOOL OnInitDialog();
	afx_msg void OnPaint();
	afx_msg HCURSOR OnQueryDragIcon();
	DECLARE_MESSAGE_MAP()
};

在OnInitDialog()函数中为对话框添加背景图片

//1、2、3所在的位置:为对话框添加背景图片
BOOL CgydonglepcmaxDlg::OnInitDialog()
{
	CDialogEx::OnInitDialog();

	// 设置此对话框的图标。  当应用程序主窗口不是对话框时,框架将自动
	//  执行此操作
	SetIcon(m_hIcon, TRUE);			// 设置大图标
	SetIcon(m_hIcon, FALSE);		// 设置小图标

	ShowWindow(SW_MAXIMIZE);

	ShowWindow(SW_MINIMIZE);

	// TODO: 在此添加额外的初始化代码
	CBitmap bmp;//*******************************1
	bmp.LoadBitmap(IDB_BITMAP1);   //IDB_BITMAP1是图片资源ID ,作为对话框背景图片*****2
	m_brush.CreatePatternBrush(&bmp);//************3

	return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
}

打开对话框属性,重载OnCtlColor函数

//添加1、2所在的位置
HBRUSH CgydonglepcmaxDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
	HBRUSH hbr = CDialogEx::OnCtlColor(pDC, pWnd, nCtlColor);

	// TODO:  在此更改 DC 的任何特性

	// TODO:  如果默认的不是所需画笔,则返回另一个画笔
	if (pWnd == this)//**********************1
	{
		return (HBRUSH)m_brush;//对话框添加背景***********2
	}
	return hbr;
}

效果图

给对话框添加“最小化”和“退出”按键

参考博客:https://blog.csdn.net/u011711997/article/details/52551106

准备好“最小化”和“退出”按键的最小图标

以CButton为基类创建新的按钮类

设置按钮属性

CMyButton.h和CMyButton.c文件重新编写(过程略)

给按钮添加成员变量

初始化函数OnInitDialog中设置按钮大小与按键图标大小一致

CWnd *pWnd;
pWnd = GetDlgItem(IDC_BUTTON1); //获取控件指针,IDC_BUTTON1为控件ID号
pWnd->SetWindowPos(NULL, 0, 0, 25, 25, SWP_NOZORDER | SWP_NOMOVE); //调节按钮大小为25*25,使其与按钮图片大小一致
pWnd = GetDlgItem(IDC_BUTTON2); //获取控件指针,IDC_BUTTON2为控件ID号
pWnd->SetWindowPos(NULL, 0, 0, 25, 25, SWP_NOZORDER | SWP_NOMOVE);//调节按钮大小为25*25,使其与按钮图片大小一致

小知识: 

用CWnd类的函数SetWindowPos()可以改变控件的大小和位置

BOOL SetWindowPos(const CWnd* pWndInsertAfter,int x,int y,int cx,int cy,UINT nFlags);

第一个参数我不会用,一般设为NULL;
x、y控件位置;cx、cy控件宽度和高度;
nFlags常用取值:
SWP_NOZORDER:忽略第一个参数;
SWP_NOMOVE:忽略x、y,维持位置不变;
SWP_NOSIZE:忽略cx、cy,维持大小不变;

CWnd *pWnd;  
pWnd = GetDlgItem( IDC_BUTTON1 ); //获取控件指针,IDC_BUTTON1为控件ID号  
pWnd->SetWindowPos( NULL,50,80,0,0,SWP_NOZORDER | SWP_NOSIZE ); //把按钮移到窗口的(50,80)处  
pWnd = GetDlgItem( IDC_EDIT1 );  
pWnd->SetWindowPos( NULL,0,0,100,80,SWP_NOZORDER | SWP_NOMOVE ); //把编辑控件的大小设为(100,80),位置不变  
pWnd = GetDlgItem( IDC_EDIT1 );  
pWnd->SetWindowPos( NULL,0,0,100,80,SWP_NOZORDER ); //编辑控件的大小和位置都改变

 初始化函数OnInitDialog中添加按钮相关图片代码

m_btnMin.SetImagePath(_T("./res/BKCOLOR_4.png"), _T("./res/BKCOLOR_5.png"), _T("./res/BKCOLOR_5.png"), _T("./res/BKCOLOR_1.png"));
m_btnMin.InitMyButton();
m_btnClose.SetImagePath(_T("./res/BKCOLOR_2.png"), _T("./res/BKCOLOR_3.png"), _T("./res/BKCOLOR_3.png"), _T("./res/BKCOLOR_1.png"));
m_btnClose.InitMyButton();

效果图(注意:按钮大小必须小于等于按钮图片大小,不然会出现按钮变黑色的情况)

“最小化”和“退出”按键功能实现

void CgydonglepcmaxDlg::OnBnClickedButton1()
{
	// TODO: 在此添加控件通知处理程序代码
	PostMessage(WM_SYSCOMMAND, SC_MINIMIZE, 0);//最小化窗口
}


void CgydonglepcmaxDlg::OnBnClickedButton2()
{
	// TODO: 在此添加控件通知处理程序代码
	AfxGetMainWnd()->SendMessage(WM_CLOSE);//退出程序
}

点击任务栏图标可以最小化窗口

在初始化函数OnInitDialog中添加如下一行代码即可实现功能

ModifyStyle(0, WS_MINIMIZEBOX);//点击任务栏的图标可以最小化窗口

无标题栏的情况下,鼠标左键移动对话框功能实现

重载WM_LBUTTONDOWN

在OnLButtonDown中添加:

void CgydonglepcmaxDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值
	PostMessage(WM_NCLBUTTONDOWN, HTCAPTION, MAKELPARAM(point.x, point.y));   //无标题栏的情况下,鼠标左键移动对话框
	CDialogEx::OnLButtonDown(nFlags, point);
}

对话框中显示透明图片

导入自己想要添加的位图图片(黑色部分实际为透明)(ID: IDB_BITMAP2)

添加一个picture conctrol控件,设置控件属性

为使位图图片透明背景不被着色,在初始化函数OnInitDialog中添加如下一行代码(该行代码在为对话框添加背景图片时使用过)

CDialogEx::SetBackgroundImage(IDB_BITMAP1);// 设置背景图片

效果图

添加自动查询串口的功能(两种方法,可配合使用,优先采用方法二)

添加控件combobox,并添加变量:m_CombolPort

方法一:通过遍历设备列表中的所有串口0-255来实现检测(遍历每个串口花费约15ms时间,一次完整遍历有稍许延时,但是不存在权限、兼容性和适配性问题)

对话框类中添加与串口有关的公有成员函数和变量

CUIntArray ports/*所有存在串口*/, portse/*可用串口*/, portsu/*已占用串口*/;
void AddCom(void);//向组合框中添加串口设备 (采用遍历的方法) 

xxDlg.cpp文件中添加AddCom函数的具体实现内容:

//向组合框中添加串口设备 (采用遍历的方法) 
void CdonglepcDlg::AddCom(void)
{
	m_CombolPort.ResetContent();//清空组合框的所有数据
	//清空数组内容  
	ports.RemoveAll();//所有存在串口  
	portse.RemoveAll();//可用串口
	portsu.RemoveAll();//已占用串口  
	//因为至多有255个串口,所以依次检查各串口是否存在
	//如果能打开某一串口,或打开串口不成功,但返回的是 ERROR_ACCESS_DENIED错误信息,
	//都认为串口存在,只不过后者表明串口已经被占用
	//否则串口不存在
	for (int i = 1; i < 256; i++)
	{
		//形成串口名称
		CString sPort;
		sPort.Format(_T("\\\\.\\COM%d"), i);
		//尝试打开串口  
		BOOL bSuccess = FALSE;
		HANDLE hPort = ::CreateFile(sPort, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);
		if (hPort == INVALID_HANDLE_VALUE)
		{
			DWORD dwError = GetLastError();
			if (dwError == ERROR_ACCESS_DENIED)
			{
				bSuccess = TRUE;
				portsu.Add(i);       //已占用的串口
			}
		}
		else
		{
			//The port was opened successfully
			bSuccess = TRUE;
			portse.Add(i);      ////可用的串口
			//Don't forget to close the port, since we are going to do nothing with it anyway
			CloseHandle(hPort);
		}
		//Add the port number to the array which will be returned
		if (bSuccess)
			ports.Add(i);   //所有存在的串口
	}
	unsigned short uicounter;
	unsigned short uisetcom;
	CString str;
	//获取可用串口个数  
	uicounter = portse.GetSize();
	//如果个数大于0  
	if (uicounter > 0)
	{
		//初始化串口列表框  
		for (int i = 0; i < uicounter; i++)
		{
			uisetcom = portse.ElementAt(i);
			str.Format(_T("COM%d "), uisetcom);
			m_CombolPort.AddString(str);
		}
	}
}

在初始化函数OnInitDialog中添加串口相关代码

AddCom();//向组合框中添加串口设备(采用遍历的方法)     
//m_CombolPort.SetCurSel(0);//显示组合框中的第一行内容

方法二:读取注册表来实现检测(该方法响应速度很快,可增强用户体验感,但存在一定的权限、兼容性和适配性问题,并不是所有的windows系统均可采用该方法)

通过设备管理器我们可以看到可用串口号的列表,windows肯定有自己管理各种设备的方法,那就是大家所熟悉的注册表,注册表中记录各种设备信息以及其他重要信息。在HKEY_LOCAL_MACHINE下逐级展开到Hardware\\DeviceMap\\SerialComm,这里记录的就是串口信息。只要通过简单的注册表读取操作我们就可以得到串口列表

xxDlg.h头文件添加枚举变量

//函数GetCom返回值,该函数读取注册表的方式检测串口,在下面即将介绍
enum {//如果使用注册表的方式检测串口成功,则返回traversal_com,失败则返回red_register_com
	red_register_com = 0,//读取注册表的方式检测串口
	traversal_com,//采用遍历的方式检测串口
};

对话框类中添加与串口有关的公有成员函数声明

//向组合框中添加串口设备(读取注册表的方法,该方法可能存在权限、兼容性 和 适配性的问题,所以最好能有一个返回值判断串口检测是否成功)
int GetCom(void);

xxDlg.cpp文件中添加GetCom函数的具体实现内容:

int CdonglepcDlg::GetCom(void)//向组合框中添加串口设备(读取注册表的方法)
{
	HKEY   hKey;

	if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("Hardware\\DeviceMap\\SerialComm"), NULL, KEY_READ, &hKey) == ERROR_SUCCESS)
	{
		m_CombolPort.ResetContent();//清空组合框的所有数据
		TCHAR       szPortName[256], szComName[256];
		DWORD       dwLong, dwSize;
		int         nCount = 0;
		while (true)
		{
			dwLong = dwSize = 256;
			if (RegEnumValue(hKey, nCount, szPortName, &dwLong, NULL, NULL, (PUCHAR)szComName, &dwSize) == ERROR_NO_MORE_ITEMS)
				break;
			CString str;
			/*str.Format(_T("%d"), nCount);//nCount表示读到的第几个串口
			m_CombolPort.AddString(str);*/
			str.Format(_T("%s "), szComName);
			m_CombolPort.AddString(str);
			nCount++;
		}
		RegCloseKey(hKey);
	}
	else
	{
		return traversal_com;//如果读取注册表的方法检测串口失败,则返回该值,表示程序运行时使用遍历的方法检测串口
	}
	return red_register_com;//如果读取注册表的方法检测串口成功,则返回该值,表示程序运行时使用注册表的方法检测串口
}

在初始化函数OnInitDialog中添加串口相关代码

GetCom();//向组合框中添加串口设备(读取注册表的方法)

方法一和方法二 配合使用时,可优先考虑方法二,当方法二不起作用时,再采用方法一

对话框类中添加公有成员变量flag_com,运行程序使用哪种方法检测串口的标志位

int flag_com;//运行程序使用哪种方法检测串口的标志位,取值:red_register_com、traversal_com

 初始化函数中检测串口的相关代码更改成如下形式

/**********************串口相关代码***********************/
flag_com = red_register_com;//设置优先使用注册表的方法检测串口
if (flag_com == red_register_com)
{
	flag_com = GetCom();//向组合框中添加串口设备(读取注册表的方法)
}
if (flag_com == traversal_com)//如果使用注册表的方法检测串口失败,则使用遍历的方法检测串口
{
	AddCom();//向组合框中添加串口设备   
}

运行程序:

串口热拔插时检测串口(根据实测,该方法如果使用遍历的方式检测串口,会有最高10秒钟的延时,需要优化,不建议使用,如果使用注册表的方式检测串口,则不会出现延时问题。该项目并没有用到热插拔检测串口功能,这里只是给出方法供大家参考)

xxDlg.cpp文件中添加头文件#include <Dbt.h>,因为DEV_BROADCAST_DEVICEINTERFACE,DBT_DEVICEREMOVECOMPLETE,DBT_DEVICEARRIVAL这几个东东在头文件Dbt.h中定义的

#include <Dbt.h>

在消息映射BEGIN_MESSAGE_MAP(Ctbox_debug_viewDlg, CDialogEx)中添加:

ON_WM_DEVICECHANGE()

在头文件中添加公有成员函数声明:

afx_msg BOOL OnDeviceChange(UINT nEventType, DWORD dwData);

xxDlg.cpp文件中添加OnDeviceChange函数的具体实现内容:

//检测移动设备的函数
BOOL CdonglepcDlg::OnDeviceChange(UINT nEventType, DWORD dwData)
{
	DEV_BROADCAST_DEVICEINTERFACE* dbd = (DEV_BROADCAST_DEVICEINTERFACE*) dwData;
	switch (nEventType)
	{
	case DBT_DEVICEREMOVECOMPLETE://移除设备

	case DBT_DEVICEARRIVAL://添加设备
		if (flag_com == red_register_com)//如果标志位设置为读取注册表的方式检测串口
		{
			GetCom();//向组合框中添加串口设备(读取注册表的方法)
		}
		else//如果标志位设置为遍历的方式检测串口
		{
			AddCom();//向组合框中添加串口设备(采用遍历的方法)
		}
		break;
	default:
		break;
	}
	return TRUE;
}

运行结果:

 

打开一个串口,使其具有发送和接收数据的功能

新建GY_File.h文件和GY_File.cpp文件,用于声明和定义全局函数、全局变量以及宏定义

GY_File.h文件中添加一个结构体用于存储串口相关变量

/****************************************************串口相关变量*****************************************************/
typedef struct {
	HANDLE hCom;//串口句柄
	BOOL com_flag;//串口是否可以正常使用的标志位当重新选择串口时,必须置零,当串口正常打开之后置1
	CString hname;//串口名字
	CWinThread * uart_recv_pThread;//接收串口数据的线程
	void *main_dlg;//记录主对话框对象
	BYTE uart_data[CMD_DATA_NUMBER];//存储一个完整的串口命令数据
	int count;//记录uart_data数组中已经存储的数据数量
	CString mac;//记录dongle的mac地址
	CString vers;//记录dongle的版本号
	CString mesh_id;//记录mesh网络id密钥
	CString dongle_state;//记录dongle网络状态
}GY_COMX;
extern GY_COMX gy_comx;//GY_File.cpp文件中定义,这里属于外部声明
/**********************************************************************************************************************/

 对话框中添加若干控件并且给控件设置变量,用于显示串口接收的相关数据

编辑控件属性设置

添加全局函数uart_init,初始化串口相关事宜(包括串口变量初始化、多线程函数执行)(初始化函数OnInitDialog中调用)

void uart_init(void *Dlg)//初始化串口相关事宜(包括串口变量初始化、多线程函数执行)(初始化函数OnInitDialog中调用)
{
	gy_comx.main_dlg = Dlg;//初始化gy_comx.main_dlg为主对话框句柄(该值一直保持到所有程序结束,其他全局函数中可能也会用到该值)
	gy_comx.hCom = NULL;
	gy_comx.com_flag = 0;//串口是否可以正常使用的标志位当重新选择串口时,必须置零,当串口正常打开之后置1
	gy_comx.hname = _T("");
	gy_comx.count = 0;//记录uart_data数组中已经存储的数据数量

	CdonglepcDlg *main_dlg = (CdonglepcDlg*)gy_comx.main_dlg;//用于全局变量控制某一对话框的控件,详情见博客收藏
	main_dlg->flag_com = red_register_com;//设置优先使用注册表的方法检测串口
	if (main_dlg->flag_com == red_register_com)
	{
		main_dlg->flag_com = main_dlg->GetCom();//向组合框中添加串口设备(读取注册表的方法)
	}
	if (main_dlg->flag_com == traversal_com)//如果使用注册表的方法检测串口失败,则使用遍历的方法检测串口
	{
		main_dlg->AddCom();//向组合框中添加串口设备   
	}

	create_uart_recv_pThread();//创建线程,用于串口接收数据使用(函数具体定义下面即将讲解)
}

 初始化函数中提到了线程创建函数create_uart_recv_pThread,该函数单独创建一个线程用于串口接收数据使用,具体定义如下


int create_uart_recv_pThread(void)//创建线程,用于串口接收数据使用
{
	CdonglepcDlg *main_dlg = (CdonglepcDlg*)gy_comx.main_dlg;//用于全局变量控制某一对话框的控件,详情见博客收藏
	gy_comx.uart_recv_pThread = new CWinThread();//创建线程
	gy_comx.uart_recv_pThread->m_bAutoDelete = false;//设置是否自动删除为false
    //启动线程,uart_recv_pThread_func函数接收并处理串口发送给程序的数据,下面即将讲解
	gy_comx.uart_recv_pThread = AfxBeginThread(uart_recv_pThread_func, NULL);
	if (gy_comx.uart_recv_pThread == NULL)
	{
		main_dlg->MessageBox(_T("串口接收数据线程启动失败!"));
		exit(-1);
	}
	return 0;
}

 上面启动线程之后调用函数uart_recv_pThread_func,用来接收并处理串口发送给程序的数据,具体定义:

UINT uart_recv_pThread_func(LPVOID pParam)//串口数据接收线程执行函数(每隔100毫秒检测是否有串口数据,如果有数据,则做相应的处理)
{
	CdonglepcDlg *main_dlg = (CdonglepcDlg*)gy_comx.main_dlg;//用于全局变量控制某一对话框的控件,详情见博客收藏

	while (1)
	{
		//Sleep(100);//屏蔽掉该处的延时,因为串口通信为异步通信,一次接收的数据量越大,出错的概率也就越大
		if (gy_comx.com_flag)//如果标志位为TRUE,表示串口正常,可正常读取串口数据,串口如果还没有被打开,则不会执行里面的内容
		{
			//gy_set_timer_clock();//设置定时控灯demo,临时测试添加,纯属娱乐,娱乐之后请注释掉该代码
			//main_dlg->OnBnClickedallopen();//无线开灯关灯,测试时使用,测试完毕请注释掉该代码

			BYTE str[UART_READ_NUMBER];
			memset(str, '\0', UART_READ_NUMBER);
			DWORD wCount = UART_READ_NUMBER;//读取的字节数
			BOOL bReadStat;
			bReadStat = ReadFile(gy_comx.hCom, str, wCount, &wCount, NULL);
			if (!bReadStat && gy_comx.com_flag == TRUE)//表示dongle被拔出
			{
				main_dlg->MessageBox(_T("dongle可能被拔出!!!\n读串口失败!!!"), _T("错误"));
				if (main_dlg->flag_com == red_register_com)
				{
					main_dlg->flag_com = main_dlg->GetCom();//向组合框中添加串口设备(读取注册表的方法)
				}
				if (main_dlg->flag_com == traversal_com)//如果使用注册表的方法检测串口失败,则使用遍历的方法检测串口
				{
					main_dlg->AddCom();//向组合框中添加串口设备   
				}
				edit_clean();//清空编辑控件的内容(暂时没用到,可以不考虑该函数)
				gy_comx.com_flag = FALSE;//串口是否可以正常使用的标志位当重新选择串口时,必须置零,当串口正常打开之后置1

				//exit(-1);
			}
			//注释掉清空串口缓存函数,因为这里不应该用到
                        /*PurgeComm(gy_comx.hCom, PURGE_TXABORT |
				PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);*/
			uart_data_operation(str, wCount);//处理串口接收的缓存数据,下面即将具体讲解
		}
	}
	return 0;
}

上面无限循环接收串口数据,如果接收到串口数据,则将这些数据交给函数uart_data_operation来做进一步的拆分操作,最终将其拆分成一条条玩玩整整的指令

//重点注意1位置的static和2位置的cc_flag = 0(细节问题)
int uart_data_operation(BYTE *str, DWORD wCount)//处理串口接收的缓存数据
{
        /*注意:一定要加static,如果一条完整命令末尾的两个0xCC分别由第一个数据包的末尾和
        第二条数据包的开头发过来的,则末尾接收到0xCC时先将cc_flag置1,接收到第二个数据包
        再次调用该函数时,必须记住上一个数据包末尾已经出现过一次数据0xCC,这次接收的的数
        据包头只要有一个0xCC即可满足一条完整指令的要求(细节问题)*/
	static DWORD i, cc_flag = 0;//cc_flag 用来记录0xCC出现的次数*********1
	for (i = 0; i < wCount; i++)
	{
		if (str[i] == 0xCC && cc_flag == 0)
		{
			cc_flag = 1;
			gy_comx.uart_data[gy_comx.count] = str[i];
			gy_comx.count++;//该值在uart_init函数中已经初始化,所以第一次使用时可以大胆使用
		}
		else if (str[i] == 0xCC && cc_flag == 1)
		{
			cc_flag = 0;
			gy_comx.uart_data[gy_comx.count] = str[i];
                        //将剥离出来的一条完整指令做最终的解析和操作相应的功能
			cmd_operation(gy_comx.uart_data, gy_comx.count + 1);
			memset(gy_comx.uart_data, 0, CMD_DATA_NUMBER);
			gy_comx.count = 0;
		}
		else
		{
			gy_comx.uart_data[gy_comx.count] = str[i];
			gy_comx.count++;
            cc_flag = 0;//这个地方必须添加,不然指令数据会乱掉(细节问题)**********2
		}
	}
	return 0;
}

 上面函数将一条完完整整的指令从接收到的数据中分离出来,然后通过cmd_operation函数对命令做具体的解读并执行相关功能,具体定义如下: 

//有些指令需要将信息显示在对话框中,所以需要添加相应的控件和控件变量,具体过程不再这里赘述,读者并不需要照搬内容,根据自己的需要做相应的处理就好了
int cmd_operation(BYTE *str, int length)//将一个完整的数据命令(0xCC 0xCC结尾)解析并做相应的操作
{
	CdonglepcDlg *main_dlg = (CdonglepcDlg*)gy_comx.main_dlg;//用于全局变量控制某一对话框的控件,详情见博客收藏

	//int i;
	//for (i = 0; i < length; i++)
	{
		if (str[0] == 1 && str[1] == 8 && str[2] == 2 && str[3] == 7 && str[4] == 1 && str[5] == 6 && str[6] == 3 && str[7] == 2 && str[8] == 4 && str[9] == 6 && str[10] == 2)
		{
			switch (str[11])
			{
			case 'M'://MAC地址
			{
				gy_comx.mac.Format(_T("%2x %2x %2x %2x %2x %2x"), str[11 + 6], str[11 + 5], str[11 + 4], str[11 + 3], str[11 + 2], str[11 + 1]);
				main_dlg->m_mac_str = gy_comx.mac;
				main_dlg->SetDlgItemTextW(IDC_EDIT1, gy_comx.mac);//IDC_EDIT1
				break;
			}
			case 'V'://软件版本号
			{
				gy_comx.vers.Format(_T("V%d.%d"), str[11 + 1], str[11 + 2]);
				main_dlg->m_vers_str = gy_comx.vers;
				main_dlg->SetDlgItemTextW(IDC_EDIT2, gy_comx.vers);
				//UpdateData(FALSE);
				break;
			}
			case 'i'://mesh网络id和密钥
			{
				gy_comx.mesh_id.Format(_T("%d %d%d%d%d"), *(DWORD*)&str[11 + 1], str[11 + 5], str[11 + 6], str[11 + 7], str[11 + 8]);
				main_dlg->m_mesh_id_str = gy_comx.mesh_id;
				main_dlg->SetDlgItemTextW(IDC_EDIT3, gy_comx.mesh_id);
				break;
			}
			case 'g'://模拟遥控器时使用的mac地址(dongle 或者 遥控器的 mac)
			{
				CString rc_mac;

				if (str[11 + 1] == 0)//无效的遥控器mac
				{
					rc_mac.Format(_T("dongle mac:%2x %2x %2x %2x %2x %2x(遥控器mac无效)"), str[11 + 1 + 6], str[11 + 1 + 5], str[11 + 1 + 4], str[11 + 1 + 3], str[11 + 1 + 2], str[11 + 1 + 1]);
					//main_dlg->SetDlgItemTextW(IDC_EDIT7, rc_mac);
				}
				else if (str[11 + 1] == 1)//使用dongle的mac模拟遥控器
				{
					rc_mac.Format(_T("dongle mac:%2x %2x %2x %2x %2x %2x(可切换至遥控器mac)"), str[11 + 1 + 6], str[11 + 1 + 5], str[11 + 1 + 4], str[11 + 1 + 3], str[11 + 1 + 2], str[11 + 1 + 1]);
					//main_dlg->SetDlgItemTextW(IDC_EDIT7, rc_mac);
				}
				else if (str[11 + 1] == 2)//使用遥控器的mac模拟遥控器
				{
					rc_mac.Format(_T("遥控器 mac:%2x %2x %2x %2x %2x %2x(可切换至dongle mac)"), str[11 + 1 + 6], str[11 + 1 + 5], str[11 + 1 + 4], str[11 + 1 + 3], str[11 + 1 + 2], str[11 + 1 + 1]);
					//main_dlg->SetDlgItemTextW(IDC_EDIT7, rc_mac);
				}
				main_dlg->SetDlgItemTextW(IDC_EDIT4, rc_mac);
				break;
			}
			case 's'://获取dongle网络状态
			{
				switch (str[11 + 1])
				{
				case 1:
				{
					gy_comx.dongle_state.Format(_T("空闲"));
					break;
				}
				case 2:
				{
					gy_comx.dongle_state.Format(_T("尝试添加mesh网络"));
					break;
				}
				case 3:
				{
					gy_comx.dongle_state.Format(_T("成功加入mesh网络"));
					break;
				}
				case 4:
				{
					gy_comx.dongle_state.Format(_T("附近模组配网"));
					break;
				}
				case 5:
				{
					gy_comx.dongle_state.Format(_T("BUFFOLA_STATE_DELETING"));
					break;
				}
				case 6:
				{
					gy_comx.dongle_state.Format(_T("OTA模式"));
					break;
				}
				case 7:
				{
					gy_comx.dongle_state.Format(_T("发送ID"));
					break;
				}
				case 8:
				{
					gy_comx.dongle_state.Format(_T("接收ID"));
					break;
				}
				}
				main_dlg->m_net_state_str = gy_comx.dongle_state;
				//main_dlg->UpdateData(FALSE);//全局函数中不能使用该函数刷新对话框
				main_dlg->SetDlgItemTextW(IDC_EDIT5, gy_comx.dongle_state);
				break;
			}
			case 'r'://dongle接收的id给上位机
			{
				CString str_1;
				str_1.Format(_T("%d %d%d%d%d"), *((DWORD*)(&str[11 + 1])), str[11 + 1 + 4 + 0] - 0x30, str[11 + 1 + 4 + 1] - 0x30, str[11 + 1 + 4 + 2] - 0x30, str[11 + 1 + 4 + 3] - 0x30);
				main_dlg->SetDlgItemTextW(IDC_EDIT6, str_1);
				break;
			}
			}
		}
	}
	return 0;
}

在OnInitDialog函数中添加串口相关的初始化函数uart_init,初始化串口数据,打开接收串口数据的线程

/**********************串口相关代码***********************/
uart_init(this);//初始化串口相关事宜(包括串口设置、变量初始化、多线程函数执行)(初始化函数OnInitDialog中调用)this可以理解为祝对话框的句柄

 至此,串口初始化已经完成,但是运行程序之后会发现,串口并不起任何作用,线程中也不会执行读串口数据的功能,细心的读者可能已经发现了:我们只是完成了串口的初始化工作,但是代码中并没有提到如何打开串口,所以程序运行之后,没有打开任何串口,导致没有任何反应。现在需要添加打开串口的函数,具体做法如下:

组合框重载函数OnCbnSelchangeCombo1,当组合框控件的选择发生变化时,触发此消息:打开对应串口,发送指令(77 01 01 13 66),接收版本号、MAC地址等数据。在选中组合框的一个串口时,执行该函数,函数中不仅将选中的串口打开、设置OK,还向串口发送了一条数据指令,用于读取串口设备的相关信息,主要验证数据接收线程是否可以正常接收数据。

void CdonglepcDlg::OnCbnSelchangeCombo1()//当组合框控件的选择发生变化时,触发此消息:打开对应串口,发送指令(77 01 01 13 66),接收版本号、MAC地址等数据
{
	// TODO: 在此添加控件通知处理程序代码
	gy_comx.com_flag = FALSE;//置0,供串口读取线程使用
	close_com();//关闭串口

	/*UpdateData(FALSE):将程序中改变的变量的值更新至控件中去;
	UpdateData(TRUE):将控件中输入的值更新到变量中*/
	UpdateData(TRUE);//将控件中输入的值更新到变量中
	gy_comx.hname = m_com_str;//获得串口数据
	if (gy_comx.hname.GetLength() > 4)
	{
		gy_comx.hname = _T("\\\\.\\") + gy_comx.hname;
	}

	gy_comx.hCom = CreateFile(gy_comx.hname,//串口名称
		GENERIC_READ | GENERIC_WRITE,//允许读和写
		0,//独占方式
		NULL,
		OPEN_EXISTING,//打开而不是创建
		0,//同步方式
		NULL);

	if (gy_comx.hCom == (HANDLE)-1)
	{
		gy_comx.hCom = NULL;
		MessageBox(_T("打开 COM 失败!!!\n请确认串口是否选择正确!!"), _T("错误"));
		return;
	}

	SetupComm(gy_comx.hCom, UART_READ_NUMBER, UART_WRITE_NUMBER);//输入缓冲区大小是 9600 输出缓冲区的大小是 100

	COMMTIMEOUTS TimeOuts;
	//设定读超时
	TimeOuts.ReadIntervalTimeout = MAXDWORD;
	TimeOuts.ReadTotalTimeoutMultiplier = 0;
	TimeOuts.ReadTotalTimeoutConstant = 0;
	//在读一次输入缓冲区的内容后读操作就立即返回,
	//而不管是否读入了要求的字符.
	//设定写超时
	TimeOuts.WriteTotalTimeoutMultiplier = 100;
	TimeOuts.WriteTotalTimeoutConstant = 500;
	SetCommTimeouts(gy_comx.hCom, &TimeOuts);//设置超时

	//配置串口
	DCB dcb;
	if (!GetCommState(gy_comx.hCom, &dcb))
	{
		MessageBox(_T("获取串口DCB失败!!!"), _T("错误"));
		close_com();//关闭串口
	}
	dcb.BaudRate = 9600;//波特率为 9600
	dcb.ByteSize = 8;//每个字节有 8 位
	dcb.Parity = NOPARITY;//无奇偶校验位
	dcb.StopBits = ONESTOPBIT;//1个停止位
	if (!SetCommState(gy_comx.hCom, &dcb))
	{
		MessageBox(_T("设置串口DCB失败!!!"), _T("错误"));
		close_com();//关闭串口
	}
	if (!PurgeComm(gy_comx.hCom, PURGE_TXCLEAR | PURGE_RXCLEAR))
	{
		MessageBox(_T("清空串口缓冲区失败!!!"), _T("错误"));
		close_com();//关闭串口
	}

	gy_comx.com_flag = TRUE;//当串口正常打开之后置1,供串口读取线程使用

	BYTE gy_uart_tx[] = { 0x77, 0x01, 0x01, 0x66, 0x66 };
	DWORD dwBytesWrite = sizeof(gy_uart_tx);
	led_control(gy_uart_tx, dwBytesWrite);//通过串口发送灯控命令(具体代码紧跟下文有介绍)
}
//向指定串口发送数据
int led_control(BYTE *gy_uart_tx, DWORD dwBytesWrite)//通过串口发送灯控命令
{
	COMSTAT ComStat;
	DWORD dwErrorFlags;
	BOOL bWriteStat;
	ClearCommError(gy_comx.hCom, &dwErrorFlags, &ComStat);
	bWriteStat = WriteFile(gy_comx.hCom, gy_uart_tx, dwBytesWrite, &dwBytesWrite, NULL);
	if (!bWriteStat)
	{
		MessageBox(NULL, _T("写串口失败!"), _T("错误警告"), MB_OK);
	}
	return 0;
}

 至此,一个串口就可以被正常打开,而且还可以接受串口设备发送的数据,效果图:

写完这些代码之后,还发现了一个BUG:串口检测只在程序初始化函数中执行过一次,也就是说,如果在软件已经被打开的时候,你再去插拔一个串口设备,程序是不会对这个消息做任何处理的,如果刚好你在程序运行的过程中插上一个串口设备,并且想使用他,你会发现,你在组合框中怎么也找不到这个串口设备的串口号,可通过如下方法解决该问题:

组合框重载函数OnCbnDropdownCombo1,当用户要下拉组合框控件的列表框部分中的字符串时,执行该函数。也就是说,当你想打开一个串口设备时,你都会先点开下拉组合框控件的列表,只要一点开这个列表,就会触发重载函数OnCbnDropdownCombo1的执行,所以你只要在这个函数中重新检索串口,就会找到你自己想要的串口设备的串口号,从而打开串口:

void CdonglepcDlg::OnCbnDropdownCombo1()//当用户要下拉组合框控件的列表框部分中的字符串时,执行该函数
{
	// TODO: 在此添加控件通知处理程序代码
	if (flag_com == red_register_com)
	{
		flag_com = GetCom();//向组合框中添加串口设备(读取注册表的方法)
	}
	if (flag_com == traversal_com)//如果使用注册表的方法检测串口失败,则使用遍历的方法检测串口
	{
		AddCom();//向组合框中添加串口设备   
	}
}

添加项目中必要的几个按钮,如图:

先准备需要的按钮背景图片:

跟之前添加最小化按钮和退出程序按钮一样,先添加按钮,设置变量,然后在初始化函数中添加如下代码:

pWnd = GetDlgItem(IDC_BUTTON3); //获取控件指针,IDC_BUTTON1为控件ID号
pWnd->SetWindowPos(NULL, 283, 40, 130, 60, SWP_NOZORDER ); //调节按钮大小为130*60,使其与按钮图片大小一致,并指定位置283*40放置
pWnd = GetDlgItem(IDC_BUTTON4); //获取控件指针,IDC_BUTTON2为控件ID号
pWnd->SetWindowPos(NULL, 431, 40, 169, 60, SWP_NOZORDER);//调节按钮大小为169*60,使其与按钮图片大小一致,并指定位置431*40放置
pWnd = GetDlgItem(IDC_BUTTON5); //获取控件指针,IDC_BUTTON1为控件ID号
pWnd->SetWindowPos(NULL, 621, 40, 88, 60, SWP_NOZORDER); //调节按钮大小为88*60,使其与按钮图片大小一致,并指定位置621*40放置
pWnd = GetDlgItem(IDC_BUTTON6); //获取控件指针,IDC_BUTTON2为控件ID号
pWnd->SetWindowPos(NULL, 736, 40, 164, 60, SWP_NOZORDER);//调节按钮大小为164*60,使其与按钮图片大小一致,并指定位置736*40放置
pWnd = GetDlgItem(IDC_BUTTON7); //获取控件指针,IDC_BUTTON1为控件ID号
pWnd->SetWindowPos(NULL, 923, 40, 161, 60, SWP_NOZORDER); //调节按钮大小为161*60,使其与按钮图片大小一致,并指定位置923*40放置
pWnd = GetDlgItem(IDC_BUTTON8); //获取控件指针,IDC_BUTTON2为控件ID号
pWnd->SetWindowPos(NULL, 1105, 40, 121, 60, SWP_NOZORDER);//调节按钮大小为121*60,使其与按钮图片大小一致,并指定位置1105*40放置

m_dengliebiao.SetImagePath(_T("./res/dengliebiao_1.png"), _T("./res/dengliebiao_2.png"), _T("./res/dengliebiao_3.png"), _T("./res/dengliebiao_beijing.png"));
m_dengliebiao.InitMyButton();
m_changjingliebiao.SetImagePath(_T("./res/changjingliebiao_1.png"), _T("./res/changjingliebiao_2.png"), _T("./res/changjingliebiao_3.png"), _T("./res/changjingliebiao_beijing.png"));
m_changjingliebiao.InitMyButton();
m_dingshi.SetImagePath(_T("./res/dingshi_1.png"), _T("./res/dingshi_2.png"), _T("./res/dingshi_3.png"), _T("./res/dingshi_beijing.png"));
m_dingshi.InitMyButton();
m_otashengji.SetImagePath(_T("./res/otashengji_1.png"), _T("./res/otashengji_2.png"), _T("./res/otashengji_3.png"), _T("./res/otashengji_beijing.png"));
m_otashengji.InitMyButton();
m_wangluozhenduan.SetImagePath(_T("./res/wangluozhenduan_1.png"), _T("./res/wangluozhenduan_2.png"), _T("./res/wangluozhenduan_3.png"), _T("./res/wangluozhenduan_beijing.png"));
m_wangluozhenduan.InitMyButton();
m_layout.SetImagePath(_T("./res/layout_1.png"), _T("./res/layout_2.png"), _T("./res/layout_3.png"), _T("./res/layout_beijing.png"));
m_layout.InitMyButton();

效果图:

使用静态文本框显示串口接收的相关数据

添加四个静态文本框,并且将ID更改为IDC_STATIC1、IDC_STATIC2、IDC_STATIC3、IDC_STATIC4,这四个静态文本分别显示 dongle 的MAC地址、固件版本号、Mesh ID、网络状态

在初始化函数OnInitDialog中设置静态文本框背景透明:

//该函数前面已经添加过,不需要再次添加
CDialogEx::SetBackgroundImage(IDB_BITMAP1);// 对话框设置背景图片

改写cmd_operation函数(前面已经具体讲过,这里不再赘述),该函数是用来解析串口设备发送给程序的一条完整指令,包含串口设备的相关信息,改写之后可通过静态文本框显示出来

int cmd_operation(BYTE *str, int length)//将一个完整的数据命令(0xCC 0xCC结尾)解析并做相应的操作
{
	CdonglepcDlg *main_dlg = (CdonglepcDlg*)gy_comx.main_dlg;//用于全局变量控制某一对话框的控件,详情见博客收藏

	//int i;
	//for (i = 0; i < length; i++)
	{
		if (str[0] == 1 && str[1] == 8 && str[2] == 2 && str[3] == 7 && str[4] == 1 && str[5] == 6 && str[6] == 3 && str[7] == 2 && str[8] == 4 && str[9] == 6 && str[10] == 2)
		{
			switch (str[11])
			{
			case 'M'://MAC地址
			{
				gy_comx.mac.Format(_T("MAC地址:%02x %02x %02x %02x %02x %02x"), str[11 + 6], str[11 + 5], str[11 + 4], str[11 + 3], str[11 + 2], str[11 + 1]);
				main_dlg->m_mac_str = gy_comx.mac;
				main_dlg->GetDlgItem(IDC_STATIC1)->SetWindowText(gy_comx.mac);//向静态文本框中添加文本内容

				//如果静态文本框中已经存在文本内容,我们使用上面的代码向静态文本框中再添加文本内容,则会产生文字重叠
				//如下是解决该问题的方法
				CRect rtlbl;
				main_dlg->GetDlgItem(IDC_STATIC1)->GetWindowRect(&rtlbl);
				main_dlg->ScreenToClient(&rtlbl); //转到客户端界面
				main_dlg->InvalidateRect(&rtlbl);//最后刷新对话框背景 

				break;
			}
			case 'V'://软件版本号
			{
				gy_comx.vers.Format(_T("固件版本:V%d.%d"), str[11 + 1], str[11 + 2]);
				main_dlg->m_vers_str = gy_comx.vers;
				main_dlg->GetDlgItem(IDC_STATIC2)->SetWindowText(gy_comx.vers);//向静态文本框中添加文本内容

				//如果静态文本框中已经存在文本内容,我们使用上面的代码向静态文本框中再添加文本内容,则会产生文字重叠
				//如下是解决该问题的方法
				CRect rtlbl;
				main_dlg->GetDlgItem(IDC_STATIC2)->GetWindowRect(&rtlbl);
				main_dlg->ScreenToClient(&rtlbl); //转到客户端界面
				main_dlg->InvalidateRect(&rtlbl);//最后刷新对话框背景 

				break;
			}
			case 'i'://mesh网络id和密钥
			{
				gy_comx.mesh_id.Format(_T("Mesh ID:%d %d%d%d%d"), *(DWORD*)&str[11 + 1], str[11 + 5], str[11 + 6], str[11 + 7], str[11 + 8]);
				main_dlg->m_mesh_id_str = gy_comx.mesh_id;
				main_dlg->GetDlgItem(IDC_STATIC3)->SetWindowText(gy_comx.mesh_id);//向静态文本框中添加文本内容

				//如果静态文本框中已经存在文本内容,我们使用上面的代码向静态文本框中再添加文本内容,则会产生文字重叠
				//如下是解决该问题的方法
				CRect rtlbl;
				main_dlg->GetDlgItem(IDC_STATIC3)->GetWindowRect(&rtlbl);
				main_dlg->ScreenToClient(&rtlbl); //转到客户端界面
				main_dlg->InvalidateRect(&rtlbl);//最后刷新对话框背景 

				break;
			}
			case 'g'://模拟遥控器时使用的mac地址(dongle 或者 遥控器的 mac)
			{
				CString rc_mac;

				if (str[11 + 1] == 0)//无效的遥控器mac
				{
					rc_mac.Format(_T("dongle mac:%02x %02x %02x %02x %02x %02x(遥控器mac无效)"), str[11 + 1 + 6], str[11 + 1 + 5], str[11 + 1 + 4], str[11 + 1 + 3], str[11 + 1 + 2], str[11 + 1 + 1]);
					//main_dlg->SetDlgItemTextW(IDC_EDIT7, rc_mac);
				}
				else if (str[11 + 1] == 1)//使用dongle的mac模拟遥控器
				{
					rc_mac.Format(_T("dongle mac:%02x %02x %02x %02x %02x %02x(可切换至遥控器mac)"), str[11 + 1 + 6], str[11 + 1 + 5], str[11 + 1 + 4], str[11 + 1 + 3], str[11 + 1 + 2], str[11 + 1 + 1]);
					//main_dlg->SetDlgItemTextW(IDC_EDIT7, rc_mac);
				}
				else if (str[11 + 1] == 2)//使用遥控器的mac模拟遥控器
				{
					rc_mac.Format(_T("遥控器 mac:%02x %02x %02x %02x %02x %02x(可切换至dongle mac)"), str[11 + 1 + 6], str[11 + 1 + 5], str[11 + 1 + 4], str[11 + 1 + 3], str[11 + 1 + 2], str[11 + 1 + 1]);
					//main_dlg->SetDlgItemTextW(IDC_EDIT7, rc_mac);
				}
				break;
			}
			case 's'://获取dongle网络状态
			{
				switch (str[11 + 1])
				{
				case 1:
				{
					gy_comx.dongle_state.Format(_T("网络状态:空闲"));
					break;
				}
				case 2:
				{
					gy_comx.dongle_state.Format(_T("网络状态:尝试添加mesh网络"));
					break;
				}
				case 3:
				{
					gy_comx.dongle_state.Format(_T("网络状态:成功加入mesh网络"));
					break;
				}
				case 4:
				{
					gy_comx.dongle_state.Format(_T("网络状态:附近模组配网"));
					break;
				}
				case 5:
				{
					gy_comx.dongle_state.Format(_T("网络状态:BUFFOLA_STATE_DELETING"));
					break;
				}
				case 6:
				{
					gy_comx.dongle_state.Format(_T("网络状态:OTA模式"));
					break;
				}
				case 7:
				{
					gy_comx.dongle_state.Format(_T("网络状态:发送ID"));
					break;
				}
				case 8:
				{
					gy_comx.dongle_state.Format(_T("网络状态:接收ID"));
					break;
				}
				}
				main_dlg->m_net_state_str = gy_comx.dongle_state;
				//main_dlg->UpdateData(FALSE);//全局函数中不能使用该函数刷新对话框
				main_dlg->GetDlgItem(IDC_STATIC4)->SetWindowText(gy_comx.dongle_state);//向静态文本框中添加文本内容

				//如果静态文本框中已经存在文本内容,我们使用上面的代码向静态文本框中再添加文本内容,则会产生文字重叠
				//如下是解决该问题的方法
				CRect rtlbl;
				main_dlg->GetDlgItem(IDC_STATIC4)->GetWindowRect(&rtlbl);
				main_dlg->ScreenToClient(&rtlbl); //转到客户端界面
				main_dlg->InvalidateRect(&rtlbl);//最后刷新对话框背景 

				break;
			}
			case 'r'://dongle接收的id给上位机
			{
				CString str_1;
				str_1.Format(_T("%d %d%d%d%d"), *((DWORD*)(&str[11 + 1])), str[11 + 1 + 4 + 0] - 0x30, str[11 + 1 + 4 + 1] - 0x30, str[11 + 1 + 4 + 2] - 0x30, str[11 + 1 + 4 + 3] - 0x30);
				break;
			}
			}
		}
	}
	return 0;
}

效果图:

根据实际操作,当重新选择串口时,应当先将之前的串口设备信息清空,然后显示新的串口设备信息

添加清空函数(全局函数)

int edit_clean(void)//清空相关控件的内容
{
	CdonglepcDlg *main_dlg = (CdonglepcDlg*)gy_comx.main_dlg;//用于全局变量控制某一对话框的控件,详情见博客收藏

	main_dlg->GetDlgItem(IDC_STATIC1)->SetWindowText(_T(""));//向静态文本框中添加文本内容
	//如果静态文本框中已经存在文本内容,我们使用上面的代码向静态文本框中再添加文本内容,则会产生文字重叠
	//如下是解决该问题的方法
	CRect rtlbl;
	main_dlg->GetDlgItem(IDC_STATIC1)->GetWindowRect(&rtlbl);
	main_dlg->ScreenToClient(&rtlbl); //转到客户端界面
	main_dlg->InvalidateRect(&rtlbl);//最后刷新对话框背景 

	main_dlg->GetDlgItem(IDC_STATIC2)->SetWindowText(_T(""));//向静态文本框中添加文本内容
	//如果静态文本框中已经存在文本内容,我们使用上面的代码向静态文本框中再添加文本内容,则会产生文字重叠
	//如下是解决该问题的方法
	main_dlg->GetDlgItem(IDC_STATIC2)->GetWindowRect(&rtlbl);
	main_dlg->ScreenToClient(&rtlbl); //转到客户端界面
	main_dlg->InvalidateRect(&rtlbl);//最后刷新对话框背景 

	main_dlg->GetDlgItem(IDC_STATIC3)->SetWindowText(_T(""));//向静态文本框中添加文本内容
	//如果静态文本框中已经存在文本内容,我们使用上面的代码向静态文本框中再添加文本内容,则会产生文字重叠
	//如下是解决该问题的方法
	main_dlg->GetDlgItem(IDC_STATIC3)->GetWindowRect(&rtlbl);
	main_dlg->ScreenToClient(&rtlbl); //转到客户端界面
	main_dlg->InvalidateRect(&rtlbl);//最后刷新对话框背景 

	main_dlg->GetDlgItem(IDC_STATIC4)->SetWindowText(_T(""));//向静态文本框中添加文本内容
	//如果静态文本框中已经存在文本内容,我们使用上面的代码向静态文本框中再添加文本内容,则会产生文字重叠
	//如下是解决该问题的方法
	main_dlg->GetDlgItem(IDC_STATIC4)->GetWindowRect(&rtlbl);
	main_dlg->ScreenToClient(&rtlbl); //转到客户端界面
	main_dlg->InvalidateRect(&rtlbl);//最后刷新对话框背景 
	return 0;
}

在重新选择串口的函数中首先调用清空函数:

void CdonglepcDlg::OnCbnSelchangeCombo1()//当组合框控件的选择发生变化时,触发此消息:打开对应串口,发送指令(77 01 01 13 66),接收版本号、MAC地址等数据
{
	// TODO: 在此添加控件通知处理程序代码
	edit_clean();//清空静态文本控件的内容
	gy_comx.com_flag = FALSE;//置0,供串口读取线程使用
	close_com();//关闭串口,在该函数中调用清空函数
    ......

效果图:(选择串口1,没有任何信息显示,说明该设备不是我们想要打开的设备)

当正在使用的串口设备被意外拔出,也应该清除掉串口设备的相关信息

UINT uart_recv_pThread_func(LPVOID pParam)//串口数据接收线程执行函数
{
    ......
	while (1)
	{
		Sleep(100);
		if (gy_comx.com_flag)//如果标志位为TRUE,表示串口正常,可正常读取串口数据
		{
            ......
			if (!bReadStat && gy_comx.com_flag == TRUE)//串口设备意外被拔掉
			{
				main_dlg->MessageBox(_T("dongle可能被拔出!!!\n读串口失败!!!"), _T("错误"));
				if (main_dlg->flag_com == red_register_com)
				{
					main_dlg->flag_com = main_dlg->GetCom();//向组合框中添加串口设备(读取注册表的方法)
				}
				if (main_dlg->flag_com == traversal_com)//如果使用注册表的方法检测串口失败,则使用遍历的方法检测串口
				{
					main_dlg->AddCom();//向组合框中添加串口设备   
				}
				edit_clean();//清空编辑控件的内容
				gy_comx.com_flag = FALSE;

				//exit(-1);
			}
            ......
}

制作一个数据库表用于存储串口设备上传的数据(这里使用access数据库,具体过程略)

将串口接收到的数据存储到数据库中(Demo)

在函数中添加数据库的封装文件:

向串口设备发送一条ping灯指令,串口设备将灯的相关信息(MAC地址和虚拟地址)发送给上位机程序,程序将数据解析之后保存到数据库中

首先在OnCbnSelchangeCombo1函数末尾处临时添加一个ping灯指令

//当组合框控件的选择发生变化时,触发此消息:打开对应串口,发送指令(77 01 01 13 66),接收版本号、MAC地址等数据
//另外调用函数gy_all_light_ping:通过串口发送ping灯指令,获取灯信息
void CdonglepcDlg::OnCbnSelchangeCombo1()
{
	// TODO: 在此添加控件通知处理程序代码
	edit_clean();//清空静态文本控件的内容
	gy_comx.com_flag = FALSE;//置0,供串口读取线程使用
	close_com();//关闭串口

	/*UpdateData(FALSE):将程序中改变的变量的值更新至控件中去;
	UpdateData(TRUE):将控件中输入的值更新到变量中*/
	UpdateData(TRUE);//将控件中输入的值更新到变量中
	gy_comx.hname = m_com_str;//获得串口数据
	if (gy_comx.hname.GetLength() > 4)
	{
		gy_comx.hname = _T("\\\\.\\") + gy_comx.hname;
	}

	gy_comx.hCom = CreateFile(gy_comx.hname,//串口名称
		GENERIC_READ | GENERIC_WRITE,//允许读和写
		0,//独占方式
		NULL,
		OPEN_EXISTING,//打开而不是创建
		0,//同步方式
		NULL);

	if (gy_comx.hCom == (HANDLE)-1)
	{
		gy_comx.hCom = NULL;
		MessageBox(_T("打开 COM 失败!!!\n请确认串口是否选择正确!!"), _T("错误"));
		return;
	}

	SetupComm(gy_comx.hCom, UART_READ_NUMBER, UART_WRITE_NUMBER);//输入缓冲区大小是 9600 输出缓冲区的大小是 100

	COMMTIMEOUTS TimeOuts;
	//设定读超时
	TimeOuts.ReadIntervalTimeout = MAXDWORD;
	TimeOuts.ReadTotalTimeoutMultiplier = 0;
	TimeOuts.ReadTotalTimeoutConstant = 0;
	//在读一次输入缓冲区的内容后读操作就立即返回,
	//而不管是否读入了要求的字符.
	//设定写超时
	TimeOuts.WriteTotalTimeoutMultiplier = 100;
	TimeOuts.WriteTotalTimeoutConstant = 500;
	SetCommTimeouts(gy_comx.hCom, &TimeOuts);//设置超时

	//配置串口
	DCB dcb;
	if (!GetCommState(gy_comx.hCom, &dcb))
	{
		MessageBox(_T("获取串口DCB失败!!!"), _T("错误"));
		close_com();//关闭串口
	}
	dcb.BaudRate = 9600;//波特率为 9600
	dcb.ByteSize = 8;//每个字节有 8 位
	dcb.Parity = NOPARITY;//无奇偶校验位
	dcb.StopBits = ONESTOPBIT;//1个停止位
	if (!SetCommState(gy_comx.hCom, &dcb))
	{
		MessageBox(_T("设置串口DCB失败!!!"), _T("错误"));
		close_com();//关闭串口
	}
	if (!PurgeComm(gy_comx.hCom, PURGE_TXCLEAR | PURGE_RXCLEAR))
	{
		MessageBox(_T("清空串口缓冲区失败!!!"), _T("错误"));
		close_com();//关闭串口
	}

	gy_comx.com_flag = TRUE;//当串口正常打开之后置1,供串口读取线程使用

	BYTE gy_uart_tx[] = { 0x77, 0x01, 0x01, 0x66, 0x66 };
	DWORD dwBytesWrite = sizeof(gy_uart_tx);
	led_control(gy_uart_tx, dwBytesWrite);//通过串口发送灯控命令(该函数上文有详细讲解)

	gy_all_light_ping();//主动ping灯,获取灯的信息//77 01 01 1B 66(该函数下方即将介绍)
}
void gy_all_light_ping(void)//主动ping灯,获取灯的信息//77 01 01 1B 66
{
	BYTE gy_uart_tx[] = { 0x77, 0x01, 0x01, 0x1B, 0x66 };
	DWORD dwBytesWrite = sizeof(gy_uart_tx);
	led_control(gy_uart_tx, dwBytesWrite);//通过串口发送灯控命令(该函数上文有详细讲解)
}

 在函数cmd_operation末尾添加接收灯信息的处理程序(解析数据,并且添加到数据库)

//末尾处添加:处理接收到的灯控相关信息
int cmd_operation(BYTE *str, int length)//将一个完整的数据命令(0xCC 0xCC结尾)解析并做相应的操作
{
	CdonglepcDlg *main_dlg = (CdonglepcDlg*)gy_comx.main_dlg;//用于全局变量控制某一对话框的控件,详情见博客收藏

	//int i;
	//for (i = 0; i < length; i++)
	{
		if (str[0] == 1 && str[1] == 8 && str[2] == 2 && str[3] == 7 && str[4] == 1 && str[5] == 6 && str[6] == 3 && str[7] == 2 && str[8] == 4 && str[9] == 6 && str[10] == 2)
		{
			switch (str[11])
			{
			case 'M'://MAC地址
			{
				gy_comx.mac.Format(_T("MAC地址:%02x %02x %02x %02x %02x %02x"), str[11 + 6], str[11 + 5], str[11 + 4], str[11 + 3], str[11 + 2], str[11 + 1]);
				main_dlg->m_mac_str = gy_comx.mac;
				main_dlg->GetDlgItem(IDC_STATIC1)->SetWindowText(gy_comx.mac);//向静态文本框中添加文本内容

				//如果静态文本框中已经存在文本内容,我们使用上面的代码向静态文本框中再添加文本内容,则会产生文字重叠
				//如下是解决该问题的方法
				CRect rtlbl;
				main_dlg->GetDlgItem(IDC_STATIC1)->GetWindowRect(&rtlbl);
				main_dlg->ScreenToClient(&rtlbl); //转到客户端界面
				main_dlg->InvalidateRect(&rtlbl);//最后刷新对话框背景 

				break;
			}
			case 'V'://软件版本号
			{
				gy_comx.vers.Format(_T("固件版本:V%d.%d"), str[11 + 1], str[11 + 2]);
				main_dlg->m_vers_str = gy_comx.vers;
				main_dlg->GetDlgItem(IDC_STATIC2)->SetWindowText(gy_comx.vers);//向静态文本框中添加文本内容

				//如果静态文本框中已经存在文本内容,我们使用上面的代码向静态文本框中再添加文本内容,则会产生文字重叠
				//如下是解决该问题的方法
				CRect rtlbl;
				main_dlg->GetDlgItem(IDC_STATIC2)->GetWindowRect(&rtlbl);
				main_dlg->ScreenToClient(&rtlbl); //转到客户端界面
				main_dlg->InvalidateRect(&rtlbl);//最后刷新对话框背景 

				break;
			}
			case 'i'://mesh网络id和密钥
			{
				gy_comx.mesh_id.Format(_T("Mesh ID:%d %d%d%d%d"), *(DWORD*)&str[11 + 1], str[11 + 5], str[11 + 6], str[11 + 7], str[11 + 8]);
				main_dlg->m_mesh_id_str = gy_comx.mesh_id;
				main_dlg->GetDlgItem(IDC_STATIC3)->SetWindowText(gy_comx.mesh_id);//向静态文本框中添加文本内容

				//如果静态文本框中已经存在文本内容,我们使用上面的代码向静态文本框中再添加文本内容,则会产生文字重叠
				//如下是解决该问题的方法
				CRect rtlbl;
				main_dlg->GetDlgItem(IDC_STATIC3)->GetWindowRect(&rtlbl);
				main_dlg->ScreenToClient(&rtlbl); //转到客户端界面
				main_dlg->InvalidateRect(&rtlbl);//最后刷新对话框背景 

				break;
			}
			case 'g'://模拟遥控器时使用的mac地址(dongle 或者 遥控器的 mac)
			{
				CString rc_mac;

				if (str[11 + 1] == 0)//无效的遥控器mac
				{
					rc_mac.Format(_T("dongle mac:%02x %02x %02x %02x %02x %02x(遥控器mac无效)"), str[11 + 1 + 6], str[11 + 1 + 5], str[11 + 1 + 4], str[11 + 1 + 3], str[11 + 1 + 2], str[11 + 1 + 1]);
					//main_dlg->SetDlgItemTextW(IDC_EDIT7, rc_mac);
				}
				else if (str[11 + 1] == 1)//使用dongle的mac模拟遥控器
				{
					rc_mac.Format(_T("dongle mac:%02x %02x %02x %02x %02x %02x(可切换至遥控器mac)"), str[11 + 1 + 6], str[11 + 1 + 5], str[11 + 1 + 4], str[11 + 1 + 3], str[11 + 1 + 2], str[11 + 1 + 1]);
					//main_dlg->SetDlgItemTextW(IDC_EDIT7, rc_mac);
				}
				else if (str[11 + 1] == 2)//使用遥控器的mac模拟遥控器
				{
					rc_mac.Format(_T("遥控器 mac:%02x %02x %02x %02x %02x %02x(可切换至dongle mac)"), str[11 + 1 + 6], str[11 + 1 + 5], str[11 + 1 + 4], str[11 + 1 + 3], str[11 + 1 + 2], str[11 + 1 + 1]);
					//main_dlg->SetDlgItemTextW(IDC_EDIT7, rc_mac);
				}
				break;
			}
			case 's'://获取dongle网络状态
			{
				switch (str[11 + 1])
				{
				case 1:
				{
					gy_comx.dongle_state.Format(_T("网络状态:空闲"));
					break;
				}
				case 2:
				{
					gy_comx.dongle_state.Format(_T("网络状态:尝试添加mesh网络"));
					break;
				}
				case 3:
				{
					gy_comx.dongle_state.Format(_T("网络状态:成功加入mesh网络"));
					break;
				}
				case 4:
				{
					gy_comx.dongle_state.Format(_T("网络状态:附近模组配网"));
					break;
				}
				case 5:
				{
					gy_comx.dongle_state.Format(_T("网络状态:BUFFOLA_STATE_DELETING"));
					break;
				}
				case 6:
				{
					gy_comx.dongle_state.Format(_T("网络状态:OTA模式"));
					break;
				}
				case 7:
				{
					gy_comx.dongle_state.Format(_T("网络状态:发送ID"));
					break;
				}
				case 8:
				{
					gy_comx.dongle_state.Format(_T("网络状态:接收ID"));
					break;
				}
				}
				main_dlg->m_net_state_str = gy_comx.dongle_state;
				//main_dlg->UpdateData(FALSE);//全局函数中不能使用该函数刷新对话框
				main_dlg->GetDlgItem(IDC_STATIC4)->SetWindowText(gy_comx.dongle_state);//向静态文本框中添加文本内容

				//如果静态文本框中已经存在文本内容,我们使用上面的代码向静态文本框中再添加文本内容,则会产生文字重叠
				//如下是解决该问题的方法
				CRect rtlbl;
				main_dlg->GetDlgItem(IDC_STATIC4)->GetWindowRect(&rtlbl);
				main_dlg->ScreenToClient(&rtlbl); //转到客户端界面
				main_dlg->InvalidateRect(&rtlbl);//最后刷新对话框背景 

				break;
			}
			case 'r'://dongle接收的id给上位机
			{
				CString str_1;
				str_1.Format(_T("%d %d%d%d%d"), *((DWORD*)(&str[11 + 1])), str[11 + 1 + 4 + 0] - 0x30, str[11 + 1 + 4 + 1] - 0x30, str[11 + 1 + 4 + 2] - 0x30, str[11 + 1 + 4 + 3] - 0x30);
				break;
			}
			}
		}
		else//处理接收到的灯控相关信息
		{
			while (gy_ado.access_flag);
			gy_ado.access_flag = 1;
			gy_ado_write(str, length);//将数据写入到数据库(后文即将介绍)
			gy_ado.access_flag = 0;
		}
	}
	return 0;
}
BOOL gy_ado_write(BYTE *hex, int length)//将数据写入到数据库
{
	if (hex[0]==0x77 && hex[1]==0x04 && hex[2]==0x0f && hex[3]==0x01)//如果接收到的数据是ping灯返回的数据(MAC地址 和 虚拟地址)
	{
		gy_write_all_light_mac_vir_addr(&hex[4]);//将接收到的灯的 MAC地址 和 虚拟地址 存储到数据库表中(后文即将介绍)
	}
	return TRUE;
}
BOOL gy_write_all_light_mac_vir_addr(BYTE *hex)//将接收到的灯的MAC地址和虚拟地址存储到数据库表中
{
	/*方法一:该方法比较繁琐,但是具有一定的实际意义,在此将代码贴出,方便后续查阅使用
	char mac_char[13], vir_addr_char[9];
	memset(mac_char, 0, sizeof(mac_char));
	memset(vir_addr_char, 0, sizeof(vir_addr_char));
	gy_hex_to_str(mac_char, &hex[0], 6);//将若干2进制数据转换为字符串(该函数下文详细讲解)
	gy_hex_to_str(vir_addr_char, &hex[0 + 6], 4);//将若干2进制数据转换为字符串

	// 转换ANSI字符串到UNICODE字符串
	int len = MultiByteToWideChar(CP_ACP, 0, mac_char, -1, NULL, 0);  // 先取得转换后的UNICODE字符串所需的长度
	wchar_t* mac_wchar = (wchar_t*)calloc(len, sizeof(wchar_t));         // 分配缓冲区
	MultiByteToWideChar(CP_ACP, 0, mac_char, -1, mac_wchar, len);    // 开始转换

	len = MultiByteToWideChar(CP_ACP, 0, vir_addr_char, -1, NULL, 0);  // 先取得转换后的UNICODE字符串所需的长度
	wchar_t* vir_addr_wchar = (wchar_t*)calloc(len, sizeof(wchar_t));         // 分配缓冲区
	MultiByteToWideChar(CP_ACP, 0, vir_addr_char, -1, vir_addr_wchar, len);    // 开始转换

	CString ado_str;
	ado_str.Format(_T("INSERT INTO 10000000(mac_addr,vir_addr) VALUES('%s','%s')"), mac_wchar, vir_addr_wchar);

	if (gy_ado.light_info_ado.ExecSQL(ado_str) < 0)
	{
		ado_str.Format(_T("UPDATE 10000000 SET vir_addr = '%s' WHERE mac_addr = '%s'"), vir_addr_wchar, mac_wchar);
		if (gy_ado.light_info_ado.ExecSQL(ado_str) < 0)
		{
			gy_ado.light_info_ado.m_pRst = NULL;
			AfxMessageBox(_T("添加数据库时修改虚拟地址失败"));
			return FALSE;
		}
	}
        gy_ado.light_info_ado.m_pRst = NULL;
	free(mac_wchar);
	free(vir_addr_wchar);
	*/

	//方法二:该方法比较简洁,作者打算使用这种方法存储数据到数据库
	CString ado_str;
	ado_str.Format(_T("INSERT INTO 10000000(mac_addr,vir_addr) VALUES('%02x %02x %02x %02x %02x %02x','%02x %02x %02x %02x')"), hex[5], hex[4], hex[3], hex[2], hex[1], hex[0], hex[9], hex[8], hex[7], hex[6]);

	if (gy_ado.light_info_ado.ExecSQL(ado_str) < 0)
	{
		ado_str.Format(_T("UPDATE 10000000 SET vir_addr = '%02x %02x %02x %02x' WHERE mac_addr = '%02x %02x %02x %02x %02x %02x'"), hex[9], hex[8], hex[7], hex[6], hex[5], hex[4], hex[3], hex[2], hex[1], hex[0]);
		if (gy_ado.light_info_ado.ExecSQL(ado_str) < 0)
		{
			gy_ado.light_info_ado.m_pRst = NULL;
			AfxMessageBox(_T("添加数据库时修改虚拟地址失败"));
			return FALSE;
		}
	}
        gy_ado.light_info_ado.m_pRst = NULL;
	return TRUE;
}

上面代码中 gy_hex_to_str函数详解(将若干2进制数据转换为字符串)

void gy_hex_to_str(char *str, BYTE *hex, BYTE len)//将若干2进制数据转换为字符串
{
	unsigned char Num2CharTable[] = "0123456789ABCDEF";//16进制数据转换为字符串使用
	BYTE i,j;
	for (i=0,j=0; i<len; i++)
	{
		str[j++] = Num2CharTable[((hex[i] >> 4) & 0x0f)];
		str[j++] = Num2CharTable[(hex[i] & 0x0f)];
	}
}

 运行程序,选择正确的串口设备

 等待若干秒,打开数据库,发现数据库存储了灯的MAC地址和虚拟地址

同样的,也可以分别发送获取groupid和获取灯状态的指令给串口设备,串口设备返回相关数据给上位机程序,程序解析数据并保存到数据库中

首先在OnCbnSelchangeCombo1函数末尾处临时添加相关函数(临时测试,测试时,只保留一个串口下发指令,其他下发指令请注释掉,根据实际情况,必须先执行下发指令函数gy_all_light_ping获取灯的mac地址和虚拟地址,然后gy_get_all_groupid和gy_get_all_status下发指令函数通过虚拟地址获取对应的相关信息)

void CdonglepcDlg::OnCbnSelchangeCombo1()//当组合框控件的选择发生变化时,触发此消息:打开对应串口,发送指令(77 01 01 13 66),接收版本号、MAC地址等数据
{
	// TODO: 在此添加控件通知处理程序代码
	edit_clean();//清空静态文本控件的内容
	gy_comx.com_flag = FALSE;//置0,供串口读取线程使用
	close_com();//关闭串口

	/*UpdateData(FALSE):将程序中改变的变量的值更新至控件中去;
	UpdateData(TRUE):将控件中输入的值更新到变量中*/
	UpdateData(TRUE);//将控件中输入的值更新到变量中
	gy_comx.hname = m_com_str;//获得串口数据
	if (gy_comx.hname.GetLength() > 4)
	{
		gy_comx.hname = _T("\\\\.\\") + gy_comx.hname;
	}

	gy_comx.hCom = CreateFile(gy_comx.hname,//串口名称
		GENERIC_READ | GENERIC_WRITE,//允许读和写
		0,//独占方式
		NULL,
		OPEN_EXISTING,//打开而不是创建
		0,//同步方式
		NULL);

	if (gy_comx.hCom == (HANDLE)-1)
	{
		gy_comx.hCom = NULL;
		MessageBox(_T("打开 COM 失败!!!\n请确认串口是否选择正确!!"), _T("错误"));
		return;
	}

	SetupComm(gy_comx.hCom, UART_READ_NUMBER, UART_WRITE_NUMBER);//输入缓冲区大小是 9600 输出缓冲区的大小是 100

	COMMTIMEOUTS TimeOuts;
	//设定读超时
	TimeOuts.ReadIntervalTimeout = MAXDWORD;
	TimeOuts.ReadTotalTimeoutMultiplier = 0;
	TimeOuts.ReadTotalTimeoutConstant = 0;
	//在读一次输入缓冲区的内容后读操作就立即返回,
	//而不管是否读入了要求的字符.
	//设定写超时
	TimeOuts.WriteTotalTimeoutMultiplier = 100;
	TimeOuts.WriteTotalTimeoutConstant = 500;
	SetCommTimeouts(gy_comx.hCom, &TimeOuts);//设置超时

	//配置串口
	DCB dcb;
	if (!GetCommState(gy_comx.hCom, &dcb))
	{
		MessageBox(_T("获取串口DCB失败!!!"), _T("错误"));
		close_com();//关闭串口
	}
	dcb.BaudRate = 9600;//波特率为 9600
	dcb.ByteSize = 8;//每个字节有 8 位
	dcb.Parity = NOPARITY;//无奇偶校验位
	dcb.StopBits = ONESTOPBIT;//1个停止位
	if (!SetCommState(gy_comx.hCom, &dcb))
	{
		MessageBox(_T("设置串口DCB失败!!!"), _T("错误"));
		close_com();//关闭串口
	}
	if (!PurgeComm(gy_comx.hCom, PURGE_TXCLEAR | PURGE_RXCLEAR))
	{
		MessageBox(_T("清空串口缓冲区失败!!!"), _T("错误"));
		close_com();//关闭串口
	}

	gy_comx.com_flag = TRUE;//当串口正常打开之后置1,供串口读取线程使用

	BYTE gy_uart_tx[] = { 0x77, 0x01, 0x01, 0x66, 0x66 };
	DWORD dwBytesWrite = sizeof(gy_uart_tx);
	led_control(gy_uart_tx, dwBytesWrite);//通过串口发送灯控命令

	//gy_all_light_ping();//主动ping灯,获取灯的信息//77 01 01 1B 66(临时添加,测试,上文已经讲解)

	//gy_get_all_groupid();//获取网络中所有灯虚拟地址对应的groupid//77 01 01 25 66(临时添加,测试)

	gy_get_all_status();//获取所有灯的状态信息//77 01 01 27 66(临时添加,测试)
}

上面代码中提到的函数

void gy_all_light_ping(void)//主动ping灯,获取灯的信息//77 01 01 1B 66
{
	BYTE gy_uart_tx[] = { 0x77, 0x01, 0x01, 0x1B, 0x66 };
	DWORD dwBytesWrite = sizeof(gy_uart_tx);
	led_control(gy_uart_tx, dwBytesWrite);//通过串口发送灯控命令(上文有详解)
}
void gy_get_all_groupid(void)//获取网络中所有灯虚拟地址对应的groupid//77 01 01 25 66
{
	BYTE gy_uart_tx[] = { 0x77, 0x01, 0x01, 0x25, 0x66 };
	DWORD dwBytesWrite = sizeof(gy_uart_tx);
	led_control(gy_uart_tx, dwBytesWrite);//通过串口发送灯控命令(上文有详解)
}
void gy_get_all_status(void)//获取所有灯的状态信息//77 01 01 27 66
{
	BYTE gy_uart_tx[] = { 0x77, 0x01, 0x01, 0x27, 0x66 };
	DWORD dwBytesWrite = sizeof(gy_uart_tx);
	led_control(gy_uart_tx, dwBytesWrite);//通过串口发送灯控命令(上文有详解)
}

给gy_ado_write函数添加内容

BOOL gy_ado_write(BYTE *hex, int length)//将数据写入到数据库
{
	if (hex[0]==0x77 && hex[1]==0x04 && hex[2]==0x0f && hex[3]==0x01)//如果接收到的数据是ping灯返回的数据(MAC地址 和 虚拟地址)
	{
		gy_write_all_light_mac_vir_addr(&hex[4]);//将接收到的灯的 MAC地址 和 虚拟地址 存储到数据库表中(前文有详解)
	}
	if (hex[0]==0x77 && hex[1]==0x04 && hex[2]==0x10 && hex[3]==0x02 && hex[4]==0x24 && hex[9]==0xD7 && hex[10]==0x11)//接收到的数据为所有灯的groupid信息
	{
		gy_write_all_light_groupid(&hex[5]);//将接收到的灯的groupid存储到数据库表中,在gy_ado_write函数中调用(下文讲解)
	}
	if (hex[0]==0x77 && hex[1]==0x04 && hex[2]==0x0F && hex[3]==0x02 && hex[4]==0x27 && hex[9]==0x71)
	{
		gy_write_all_light_status(&hex[5]);//将接收到的所有灯的状态信息存储到数据库表中,在在gy_ado_write函数中调用(针对所有灯)(下文讲解)
	}
	return TRUE;
}
BOOL gy_write_all_light_mac_vir_addr(BYTE *hex)//将接收到的灯的MAC地址和虚拟地址存储到数据库表中
{
	/*方法一:该方法比较繁琐,但是具有一定的实际意义,在此将代码贴出,方便后续查阅使用
	char mac_char[13], vir_addr_char[9];
	memset(mac_char, 0, sizeof(mac_char));
	memset(vir_addr_char, 0, sizeof(vir_addr_char));
	gy_hex_to_str(mac_char, &hex[0], 6);//将若干2进制数据转换为字符串(前文有详解)
	gy_hex_to_str(vir_addr_char, &hex[0 + 6], 4);//将若干2进制数据转换为字符串

	// 转换ANSI字符串到UNICODE字符串
	int len = MultiByteToWideChar(CP_ACP, 0, mac_char, -1, NULL, 0);  // 先取得转换后的UNICODE字符串所需的长度
	wchar_t* mac_wchar = (wchar_t*)calloc(len, sizeof(wchar_t));         // 分配缓冲区
	MultiByteToWideChar(CP_ACP, 0, mac_char, -1, mac_wchar, len);    // 开始转换

	len = MultiByteToWideChar(CP_ACP, 0, vir_addr_char, -1, NULL, 0);  // 先取得转换后的UNICODE字符串所需的长度
	wchar_t* vir_addr_wchar = (wchar_t*)calloc(len, sizeof(wchar_t));         // 分配缓冲区
	MultiByteToWideChar(CP_ACP, 0, vir_addr_char, -1, vir_addr_wchar, len);    // 开始转换

	CString ado_str;
	ado_str.Format(_T("INSERT INTO 10000000(mac_addr,vir_addr) VALUES('%s','%s')"), mac_wchar, vir_addr_wchar);

	if (gy_ado.light_info_ado.ExecSQL(ado_str) < 0)
	{
		ado_str.Format(_T("UPDATE 10000000 SET vir_addr = '%s' WHERE mac_addr = '%s'"), vir_addr_wchar, mac_wchar);
		if (gy_ado.light_info_ado.ExecSQL(ado_str) < 0)
		{
			gy_ado.light_info_ado.m_pRst = NULL;
			AfxMessageBox(_T("添加数据库时修改虚拟地址失败"));
			return FALSE;
		}
	}
        gy_ado.light_info_ado.m_pRst = NULL;
	free(mac_wchar);
	free(vir_addr_wchar);
	*/

	//方法二:该方法比较简洁,作者打算使用这种方法存储数据到数据库
	CString ado_str;
	ado_str.Format(_T("INSERT INTO 10000000(mac_addr,vir_addr) VALUES('%02x %02x %02x %02x %02x %02x','%02x %02x %02x %02x')"), hex[5], hex[4], hex[3], hex[2], hex[1], hex[0], hex[9], hex[8], hex[7], hex[6]);

	if (gy_ado.light_info_ado.ExecSQL(ado_str) < 0)
	{
		ado_str.Format(_T("UPDATE 10000000 SET vir_addr = '%02x %02x %02x %02x' WHERE mac_addr = '%02x %02x %02x %02x %02x %02x'"), hex[9], hex[8], hex[7], hex[6], hex[5], hex[4], hex[3], hex[2], hex[1], hex[0]);
		if (gy_ado.light_info_ado.ExecSQL(ado_str) < 0)
		{
			gy_ado.light_info_ado.m_pRst = NULL;
			AfxMessageBox(_T("添加数据库时修改虚拟地址失败"));
			return FALSE;
		}
	}
        gy_ado.light_info_ado.m_pRst = NULL;
	return TRUE;
}

BOOL gy_write_all_light_groupid(BYTE *hex)//将接收到的灯的groupid存储到数据库表中,在gy_ado_write函数中调用
{
	/*方法一:比较繁琐,此处弃用,保留供参考
	char vir_addr_char[9];
	memset(vir_addr_char, 0, sizeof(vir_addr_char));
	gy_hex_to_str(vir_addr_char, &hex[0 + 0], 4);//将若干2进制数据转换为字符串

	// 转换ANSI字符串到UNICODE字符串
	int len = MultiByteToWideChar(CP_ACP, 0, vir_addr_char, -1, NULL, 0);  // 先取得转换后的UNICODE字符串所需的长度
	wchar_t* vir_addr_wchar = (wchar_t*)calloc(len, sizeof(wchar_t));         // 分配缓冲区
	MultiByteToWideChar(CP_ACP, 0, vir_addr_char, -1, vir_addr_wchar, len);    // 开始转换

	CString ado_str;
	ado_str.Format(_T("UPDATE 10000000 SET groupid = %d WHERE vir_addr = '%s'"), hex[12], vir_addr_wchar);
	if (gy_ado.light_info_ado.ExecSQL(ado_str) < 0)
	{
		gy_ado.light_info_ado.m_pRst = NULL;
		AfxMessageBox(_T("修改groupid失败"));
		return FALSE;
	}
        gy_ado.light_info_ado.m_pRst = NULL;
	free(vir_addr_wchar);
	*/

	//方法二:较为简便
	CString ado_str;
	ado_str.Format(_T("UPDATE 10000000 SET groupid = %d WHERE vir_addr = '%02x %02x %02x %02x'"), hex[12], hex[3], hex[2], hex[1], hex[0]);
	if (gy_ado.light_info_ado.ExecSQL(ado_str) < 0)
	{
		gy_ado.light_info_ado.m_pRst = NULL;
		AfxMessageBox(_T("添加获取的groupid失败"));
		return FALSE;
	}
        gy_ado.light_info_ado.m_pRst = NULL;
	return TRUE;
}

BOOL gy_write_all_light_status(BYTE *hex)//将接收到的所有灯的状态信息存储到数据库表中,在gy_ado_write函数中调用(针对所有灯)
{
	/*方法一:较为繁琐,供参考
	char vir_addr_char[9];
	memset(vir_addr_char, 0, sizeof(vir_addr_char));
	gy_hex_to_str(vir_addr_char, &hex[0 + 0], 4);//将若干2进制数据转换为字符串

	// 转换ANSI字符串到UNICODE字符串
	int len = MultiByteToWideChar(CP_ACP, 0, vir_addr_char, -1, NULL, 0);  // 先取得转换后的UNICODE字符串所需的长度
	wchar_t* vir_addr_wchar = (wchar_t*)calloc(len, sizeof(wchar_t));         // 分配缓冲区
	MultiByteToWideChar(CP_ACP, 0, vir_addr_char, -1, vir_addr_wchar, len);    // 开始转换

	CString ado_str;
	ado_str.Format(_T("UPDATE 10000000 SET x_state = %d, y_state = %d WHERE vir_addr = '%s'"), hex[5], hex[6], vir_addr_wchar);
	if (gy_ado.light_info_ado.ExecSQL(ado_str) < 0)
	{
		gy_ado.light_info_ado.m_pRst = NULL;
		AfxMessageBox(_T("修改灯的状态值失败"));
		return FALSE;
	}
        gy_ado.light_info_ado.m_pRst = NULL;
	free(vir_addr_wchar);
	*/

	//方法二:较为简洁
	CString ado_str;
	ado_str.Format(_T("UPDATE 10000000 SET x_state = %d, y_state = %d WHERE vir_addr = '%02x %02x %02x %02x'"), hex[5], hex[6], hex[3], hex[2], hex[1], hex[0]);
	if (gy_ado.light_info_ado.ExecSQL(ado_str) < 0)
	{
		gy_ado.light_info_ado.m_pRst = NULL;
		AfxMessageBox(_T("修改灯的状态值失败"));
		return FALSE;
	}
        gy_ado.light_info_ado.m_pRst = NULL;
	return TRUE;
}

数据库运行结果:串口接收到的数据存储到数据库中的Demo编写完成

运行上位机程序之初,将获取的灯的数据有序的添加到数据库中(实战)

思路:选择正确的串口之后,首先发送ping灯指令,上位机会收到灯返回的mac地址和虚拟地址信息,在接收到最后一个灯mac地址和虚拟地址之后的1秒钟之内没有获取到其他灯的mac地址和虚拟地址信息,则表示已经接收到mesh网内所有灯的mac地址和虚拟地址信息;接下来发送获取灯groupid的指令,同样,在收到最后一个灯groupid信息之后的1秒钟之内没有收到其他groupid的信息,则表示获取灯groupid信息已经完成(没有分配groupid的灯不会返回任何信息);最后获取所有灯的状态信息,发送相关指令之后,获取mesh网内的所有灯状态信息,在收到最后一个灯状态信息的1秒钟之内没有收到其他灯的状态信息,则视为所有灯状态信息接收完毕。

添加相关全局变量:uart_Timer_pThread、write_accessdata_state 和  write_accessdata_timer_count

#define TIMER_COUNT_MAX 2  //write_accessdata_timer_count最大取值
#define TIMER_NO_WORK 100  //write_accessdata_timer_count取值为TIMER_NO_WORK时,表示不计时

typedef struct {
	BYTE access_flag;//使得数据库表在不同线程中读取和存储可以有序进行,如果没有线程占用数据库则置0,如果线程被数据库占用,则置1
	BYTE data_update_flag;//当有新的数据被写入到数据库之后,将该标志位置1,否则置0
	CAdoLx light_info_ado;//数据库表描述符
        CWinThread * uart_Timer_pThread;//接收串口数据的线程
	/*选择正确的串口之后,首先发送ping灯指令,上位机会收到灯返回的mac地址和虚拟地址信息,在接收到最后一个灯mac地址和虚拟地址之后
	的1秒钟之内没有获取到其他灯的mac地址和虚拟地址信息,则表示已经接收到mesh网内所有灯的mac地址和虚拟地址信息;接下来发送获取灯
	groupid的指令,同样,在收到最后一个灯groupid信息之后的1秒钟之内没有收到其他groupid的信息,则表示获取灯groupid信息已经完成(没
	有分配groupid的灯不会返回任何信息);最后获取所有灯的状态信息,发送相关指令之后,获取mesh网内的所有灯状态信息,在收到最后一个灯
	状态信息的1秒钟之内没有收到其他灯的状态信息,则视为所有灯状态信息接收完毕。*/
	BYTE write_accessdata_state;//正在写什么数据到数据库中(write_idle、 write_ping、 write_groupid、 write_light_status)
	BYTE write_accessdata_timer_count;//写数据库计时(write_ping、 write_groupid、 write_light_status是否超时,如果超时,则表示已经全部写入完成)
}GY_ADO;
extern GY_ADO gy_ado;//GY_File.cpp文件中定义,这里属于外部声明

enum//write_accessdata_state的取值
{
	write_idle = 0,//没有向数据库中写任何数据
	write_ping,//正在向数据库中写ping回来的数据
	write_groupid,//正在向数据库中写灯的groupid
	write_light_status,//正在向数据库中写灯的状态值
};

选择串口时,屏蔽掉Demo中发送的相关指令

//注释掉gy_all_light_ping、gy_get_all_groupid、gy_get_all_status
void CdonglepcDlg::OnCbnSelchangeCombo1()//当组合框控件的选择发生变化时,触发此消息:打开对应串口,发送指令(77 01 01 13 66),接收版本号、MAC地址等数据
{
    ......
    BYTE gy_uart_tx[] = { 0x77, 0x01, 0x01, 0x66, 0x66 };
    DWORD dwBytesWrite = sizeof(gy_uart_tx);
    led_control(gy_uart_tx, dwBytesWrite);//通过串口发送灯控命令

    //gy_all_light_ping();//主动ping灯,获取灯的信息//77 01 01 1B 66

    //gy_get_all_groupid();//获取网络中所有灯虚拟地址对应的groupid//77 01 01 25 66

    //gy_get_all_status();//获取所有灯的状态信息//77 01 01 27 66
}

选择正确串口,并且获取到串口设备信息为:"网络状态:成功加入mesh网络"。开始ping灯并且计时

int cmd_operation(BYTE *str, int length)//将一个完整的数据命令(0xCC 0xCC结尾)解析并做相应的操作
{
    ......
    	case 3:
	    {
			gy_comx.dongle_state.Format(_T("网络状态:成功加入mesh网络"));
                        gy_all_light_ping();//主动ping灯,获取灯的信息//77 01 01 1B 66
			gy_ado.write_accessdata_state = write_ping;//正在写什么数据到数据库中
			//重新开始计时,如果1秒钟之内没有收到其他ping回来的数据,则表示所有数据接收完成
			gy_ado.write_accessdata_timer_count = 0;//写数据库计时(write_ping、 write_groupid、 write_light_status是否超时,如果超时,则表示已经全部写入完成)
			break;
		}
    ......
}

创建计时器线程


int create_timer_pThread(void)//创建线程,用于一秒钟执行一次相关函数,在ado_init函数中调用
{
	CdonglepcDlg *main_dlg = (CdonglepcDlg*)gy_comx.main_dlg;//用于全局变量控制某一对话框的控件,详情见博客收藏
	gy_ado.uart_Timer_pThread = new CWinThread();//创建线程
	gy_ado.uart_Timer_pThread->m_bAutoDelete = false;//设置是否自动删除为false
	gy_ado.uart_Timer_pThread = AfxBeginThread(timer_pThread_func, NULL);//启动线程
	if (gy_ado.uart_Timer_pThread == NULL)
	{
		main_dlg->MessageBox(_T("秒函数线程启动失败!"));
		exit(-1);
	}
	return 0;
}

在线程执行函数中添加函数 get_mesh_light_info_at_start,作用,将mesh网络中灯的相关信息完整有序的写入到数据库中

UINT timer_pThread_func(LPVOID pParam)//串口数据接收线程执行函数,在create_timer_pThread函数中调用
{
	while (1)
	{
		Sleep(1000);//每隔1秒钟执行一次下面的函数
                //函数下文详解
		get_mesh_light_info_at_start();//在选择正确串口之后,获取灯的mac地址和虚拟地址、groupid、灯状态,并且以此刷新或者存储到数据库中
	}
	return 0;
}
void get_mesh_light_info_at_start(void)//在选择正确串口之后,获取灯的mac地址和虚拟地址、groupid、灯状态,并且以此刷新或者存储到数据库中,在timer_pThread_func函数中调用
{
	if (gy_ado.write_accessdata_timer_count != TIMER_NO_WORK)//如果计时已经开始
	{
		gy_ado.write_accessdata_timer_count++;//写数据库计时(write_ping、 write_groupid、 write_light_status是否超时,如果超时,则表示已经全部写入完成)
		if (gy_ado.write_accessdata_timer_count >= TIMER_COUNT_MAX)//如果获取信息超时
		{
			if (gy_ado.write_accessdata_state == write_ping)
			{
				gy_get_all_groupid();//获取网络中所有灯虚拟地址对应的groupid//77 01 01 25 66
				gy_ado.write_accessdata_state = write_groupid;//正在写什么数据到数据库中(write_idle、 write_ping、 write_groupid、 write_light_status)
				gy_ado.write_accessdata_timer_count = 0;//重新计时
			}
			else if (gy_ado.write_accessdata_state == write_groupid)
			{
				gy_get_all_status();//获取所有灯的状态信息//77 01 01 27 66
				gy_ado.write_accessdata_state = write_light_status;//正在写什么数据到数据库中(write_idle、 write_ping、 write_groupid、 write_light_status)
				gy_ado.write_accessdata_timer_count = 0;//重新计时
			}
			else if (gy_ado.write_accessdata_state == write_light_status)
			{
				gy_ado.write_accessdata_state = write_idle;//正在写什么数据到数据库中(write_idle、 write_ping、 write_groupid、 write_light_status)
				gy_ado.write_accessdata_timer_count = TIMER_NO_WORK;//所有信息获取完毕,关闭计时
			}
		}
	}
}

程序运行结果

布局客户端——添加用户数据库表

 添加用户相关的数据库

在数据库表中添加数据库表

 运行程序,选择正确的串口之后,根据串口设备的meshID和数据库表模板新建数据库表以及向相关数据库表中添加数据

数据结构变量gy_ado中添加如下数据库描述符,用于链接上文中新增的数据库user_info.accdb

CAdoLx user_info_ado;//数据库表描述符

 在gy_ado_open函数中链接user_info.accdb数据库(原有代码上增加,不过多赘述)

BOOL gy_ado_open(void)//链接数据库
{
	if (!gy_ado.light_info_ado.Connect(CAdoLx::DBT_ACCESS, _T("./accessdatabase/light_info.accdb")))
	{
		gy_ado.light_info_ado.m_pConn = NULL;
		gy_ado.light_info_ado.m_pRst = NULL;
		AfxMessageBox(gy_ado.light_info_ado.GetLastError());
		return FALSE;
	}
	if (!gy_ado.user_info_ado.Connect(CAdoLx::DBT_ACCESS, _T("./accessdatabase/user_info.accdb")))
	{
		gy_ado.user_info_ado.m_pConn = NULL;
		gy_ado.user_info_ado.m_pRst = NULL;
		AfxMessageBox(gy_ado.user_info_ado.GetLastError());
		return FALSE;
	}
	return TRUE;
}

 在关闭数据库函数中做相应关闭处理(原有代码上添加)

BOOL gy_ado_close(void)//关闭数据库
{
	gy_ado.light_info_ado.m_pConn = NULL;
	gy_ado.light_info_ado.m_pRst = NULL;
	gy_ado.user_info_ado.m_pConn = NULL;
	gy_ado.user_info_ado.m_pRst = NULL;
	return TRUE;
}

选择正确的串口设备,获取串口设备的meshID网络之后,判断本地数据库是否存储有该mesh网络的数据,如果有,则不做任何处理,直接返回,如果没有,则向数据库表meshID_name中写入数据,并且新建有关数据库表,设置索引,向新建的数据库表中添加数据

int cmd_operation(BYTE *str, int length)//将一个完整的数据命令(0xCC 0xCC结尾)解析并做相应的操作
{
    ......
    case 'i'://mesh网络id和密钥
	{
		gy_comx.mesh_id.Format(_T("Mesh ID:%d %d%d%d%d"), *(DWORD*)&str[11 + 1], str[11 + 5], str[11 + 6], str[11 + 7], str[11 + 8]);
		main_dlg->m_mesh_id_str = gy_comx.mesh_id;
		main_dlg->GetDlgItem(IDC_STATIC3)->SetWindowText(gy_comx.mesh_id);//向静态文本框中添加文本内容

		//如果静态文本框中已经存在文本内容,我们使用上面的代码向静态文本框中再添加文本内容,则会产生文字重叠
		//如下是解决该问题的方法
		CRect rtlbl;
		main_dlg->GetDlgItem(IDC_STATIC3)->GetWindowRect(&rtlbl);
		main_dlg->ScreenToClient(&rtlbl); //转到客户端界面
		main_dlg->InvalidateRect(&rtlbl);//最后刷新对话框背景 

		BYTE i = 0;
		for (; i < 8; i++)
		{
			gy_ado.Dongle_Mesh[i] = str[11 + 1 + i];//记录dongle此时的meshID和密钥
		}
		//选择正确的串口设备之后,根据串口设备提供的meshID对相应的数据库初始化(该mesh网络在客户端本地数据库中是否已经存在,如果不存在,则添加相应的数据库表)
		//在cmd_operation函数中调用,下文中详解
		gy_meshID_access_start();

		break;
	}
    ......
}
//选择正确的串口设备之后,根据串口设备提供的meshID对相应的数据库初始化(该mesh网络在客户端本地数据库中是否已经存在,如果不存在,则添加相应的数据库表)
//在cmd_operation函数中调用
BOOL gy_meshID_access_start(void)
{
	CString str_data;
	str_data.Format(_T("INSERT INTO meshID_name VALUES('%d%d%d%d%d', '%d%d%d%d%d')"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7], *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);
	if (gy_ado.user_info_ado.ExecSQL(str_data) < 0)//将新的mesh网络存储到meshID_name数据库表中
	{
		gy_ado.user_info_ado.m_pRst = NULL;
		//AfxMessageBox(gy_ado.user_info_ado.GetLastError());
		return FALSE;
	}
	gy_ado.user_info_ado.m_pRst = NULL;

	str_data.Format(_T("SELECT * INTO %d%d%d%d%d FROM 10000000 WHERE 1>2"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);
	if (!gy_ado.light_info_ado.Select(str_data))//如果meshID和密钥在本地数据库中没有存储,则新建该mesh网络下灯信息的数据库表
	{
		gy_ado.light_info_ado.m_pRst = NULL;
		AfxMessageBox(gy_ado.light_info_ado.GetLastError());
		return FALSE;
	}
	gy_ado.light_info_ado.m_pRst = NULL;

	str_data.Format(_T("CREATE UNIQUE INDEX Mac_addrIndex ON %d%d%d%d%d(mac_addr)"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);
	if (gy_ado.light_info_ado.ExecSQL(str_data) < 0)//给新建的数据库表设置无重复索引
	{
		gy_ado.light_info_ado.m_pRst = NULL;
		//AfxMessageBox(gy_ado.light_info_ado.GetLastError());
		//return FALSE;
	}
	gy_ado.light_info_ado.m_pRst = NULL;

	str_data.Format(_T("SELECT * INTO %d%d%d%d%d_area FROM 10000000_area WHERE 1>2"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);
	if (!gy_ado.user_info_ado.Select(str_data))//新建该mesh网络下灯信息的数据库表成功之后,继续新建区域对应关系表
	{
		gy_ado.user_info_ado.m_pRst = NULL;
		AfxMessageBox(gy_ado.user_info_ado.GetLastError());
		return FALSE;
	}
	gy_ado.user_info_ado.m_pRst = NULL;

	str_data.Format(_T("CREATE UNIQUE INDEX AreaIndex ON %d%d%d%d%d_area(area)"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);
	if (gy_ado.user_info_ado.ExecSQL(str_data) < 0)//给新建的数据库表设置无重复索引
	{
		gy_ado.user_info_ado.m_pRst = NULL;
		//AfxMessageBox(gy_ado.user_info_ado.GetLastError());
		//return FALSE;
	}
	gy_ado.user_info_ado.m_pRst = NULL;

	str_data.Format(_T("CREATE UNIQUE INDEX area_nameIndex ON %d%d%d%d%d_area(area_name)"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);
	if (gy_ado.user_info_ado.ExecSQL(str_data) < 0)//给新建的数据库表设置无重复索引
	{
		gy_ado.user_info_ado.m_pRst = NULL;
		//AfxMessageBox(gy_ado.user_info_ado.GetLastError());
		//return FALSE;
	}
	gy_ado.user_info_ado.m_pRst = NULL;

	str_data.Format(_T("INSERT INTO %d%d%d%d%d_area VALUES(0,'未分区域')"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);

	if (gy_ado.user_info_ado.ExecSQL(str_data) < 0)//在新表中添加未分组的相关数据
	{
		gy_ado.user_info_ado.m_pRst = NULL;
		AfxMessageBox(_T("添加未分区域失败"));
		return FALSE;
	}
	gy_ado.user_info_ado.m_pRst = NULL;

	str_data.Format(_T("SELECT * INTO %d%d%d%d%d_area_group FROM 10000000_area_group WHERE 1>2"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);
	if (!gy_ado.user_info_ado.Select(str_data))//新建该mesh网络下灯信息的数据库表成功之后,继续新建组对应关系表
	{
		gy_ado.user_info_ado.m_pRst = NULL;
		AfxMessageBox(gy_ado.user_info_ado.GetLastError());
		return FALSE;
	}
	gy_ado.user_info_ado.m_pRst = NULL;

	str_data.Format(_T("CREATE UNIQUE INDEX groupidIndex ON %d%d%d%d%d_area_group(groupid)"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);
	if (gy_ado.user_info_ado.ExecSQL(str_data) < 0)//给新建的数据库表设置无重复索引
	{
		gy_ado.user_info_ado.m_pRst = NULL;
		//AfxMessageBox(gy_ado.user_info_ado.GetLastError());
		//return FALSE;
	}
	gy_ado.user_info_ado.m_pRst = NULL;

	str_data.Format(_T("CREATE UNIQUE INDEX groupid_nameIndex ON %d%d%d%d%d_area_group(groupid_name)"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);
	if (gy_ado.user_info_ado.ExecSQL(str_data) < 0)//给新建的数据库表设置无重复索引
	{
		gy_ado.user_info_ado.m_pRst = NULL;
		//AfxMessageBox(gy_ado.user_info_ado.GetLastError());
		//return FALSE;
	}
	gy_ado.user_info_ado.m_pRst = NULL;

	str_data.Format(_T("INSERT INTO %d%d%d%d%d_area_group VALUES(0,0,'未分组')"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);

	if (gy_ado.user_info_ado.ExecSQL(str_data) < 0)//在新表中添加未分组的相关数据
	{
		gy_ado.user_info_ado.m_pRst = NULL;
		AfxMessageBox(_T("区域中添加未分组失败"));
		return FALSE;
	}
	gy_ado.user_info_ado.m_pRst = NULL;

	return TRUE;
}

将灯返回的信息存储到数据库时,将数据库表10000000更改为Dongle现有的mesh网络,将数据放在他们应该去的地方

BOOL gy_write_all_light_mac_vir_addr(BYTE *hex)//将接收到的灯的MAC地址和虚拟地址存储到数据库表中
{
	//方法二:该方法比较简洁,作者打算使用这种方法存储数据到数据库
	CString ado_str;
	ado_str.Format(_T("INSERT INTO %d%d%d%d%d(mac_addr,vir_addr) VALUES('%02x:%02x:%02x:%02x:%02x:%02x','%02x:%02x:%02x:%02x')"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7],  hex[5], hex[4], hex[3], hex[2], hex[1], hex[0], hex[9], hex[8], hex[7], hex[6]);

	if (gy_ado.light_info_ado.ExecSQL(ado_str) < 0)
	{
		ado_str.Format(_T("UPDATE %d%d%d%d%d SET vir_addr = '%02x:%02x:%02x:%02x' WHERE mac_addr = '%02x:%02x:%02x:%02x:%02x:%02x'"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7], hex[9], hex[8], hex[7], hex[6], hex[5], hex[4], hex[3], hex[2], hex[1], hex[0]);
		if (gy_ado.light_info_ado.ExecSQL(ado_str) < 0)
		{
			gy_ado.light_info_ado.m_pRst = NULL;
			AfxMessageBox(_T("添加数据库时修改虚拟地址失败"));
			return FALSE;
		}
	}
	gy_ado.light_info_ado.m_pRst = NULL;
	return TRUE;
}

BOOL gy_write_all_light_groupid(BYTE *hex)//将接收到的灯的groupid存储到数据库表中,在gy_ado_write函数中调用
{
	//方法二:较为简便
	CString ado_str;
	ado_str.Format(_T("UPDATE %d%d%d%d%d SET groupid = %d WHERE vir_addr = '%02x:%02x:%02x:%02x'"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7],  hex[12], hex[3], hex[2], hex[1], hex[0]);
	if (gy_ado.light_info_ado.ExecSQL(ado_str) < 0)
	{
		gy_ado.light_info_ado.m_pRst = NULL;
		AfxMessageBox(_T("添加获取的groupid失败"));
		return FALSE;
	}
	gy_ado.light_info_ado.m_pRst = NULL;

	ado_str.Format(_T("INSERT INTO %d%d%d%d%d_area_group VALUES(0,%d,'组%d')"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7], hex[12], hex[12]);

	if (gy_ado.user_info_ado.ExecSQL(ado_str) < 0)
	{
		gy_ado.user_info_ado.m_pRst = NULL;
		//AfxMessageBox(_T("区域中添加组失败"));
		return FALSE;
	}
	gy_ado.user_info_ado.m_pRst = NULL;

	return TRUE;
}

BOOL gy_write_all_light_status(BYTE *hex)//将接收到的所有灯的状态信息存储到数据库表中,在gy_ado_write函数中调用(针对所有灯)
{
	//方法二:较为简洁
	CString ado_str;
	ado_str.Format(_T("UPDATE %d%d%d%d%d SET x_state = %d, y_state = %d WHERE vir_addr = '%02x:%02x:%02x:%02x'"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7], hex[5], hex[6], hex[3], hex[2], hex[1], hex[0]);
	if (gy_ado.light_info_ado.ExecSQL(ado_str) < 0)
	{
		gy_ado.light_info_ado.m_pRst = NULL;
		AfxMessageBox(_T("修改灯的状态值失败"));
		return FALSE;
	}
	gy_ado.light_info_ado.m_pRst = NULL;
	return TRUE;
}

 运行结果:

读取数据库中的数据,并将数据通过树形控件展示(Demo)

思路:添加一个树形控件,控件的根节点为mesh网络;下一层的子节点为区域,如果未划分区域,则子节点显示“为分区域”;再下一层子节点显示分组,如果未分组,则显示“未分组”;最后的叶子节点显示每个灯的mac地址。

在对话框中添加一个树形控件,并设置属性

树形控件添加变量

在OnInitDialog函数中给树形控件设置背景颜色

m_tree.SetBkColor(RGB(70, 190, 220));//设置tree的背景色*/ 

效果图(对话框背景图片有稍作修改)

根据实际测试,当同一个数据库中的数据库表互相嵌套调用时,因为使用同一个数据库描述符,导致数据库表调用混乱。解决办法:将不同类型的数据库表放置在不同的数据库中:

在结构体变量gy_ado中添加对应数据库描述符

CAdoLx light_info_ado;//数据库表描述符(之前存储灯信息时使用的数据库)
CAdoLx meshID_name_ado;//数据库表描述符(新添加数据库)
CAdoLx meshID_name_area_ado;//数据库表描述符(新添加数据库)
CAdoLx meshID_name_area_group_ado;//数据库表描述符(新添加数据库)
//CAdoLx user_info_ado;//数据库表描述符(之前添加的用户相关数据库,现已经插分成上面三个数据库)

之前适应到的数据库描述符user_info_ado也要做出相应的更改

//链接数据库,在程序初始化时执行该函数,使得程序运行的过程中与数据库一直保持连接,在ado_init函数中调用
BOOL gy_ado_open(void)//链接数据库
{
	if (!gy_ado.light_info_ado.Connect(CAdoLx::DBT_ACCESS, _T("./accessdatabase/light_info.accdb")))
	{
		gy_ado.light_info_ado.m_pConn = NULL;
		gy_ado.light_info_ado.m_pRst = NULL;
		AfxMessageBox(gy_ado.light_info_ado.GetLastError());
		return FALSE;
	}
	if (!gy_ado.meshID_name_ado.Connect(CAdoLx::DBT_ACCESS, _T("./accessdatabase/meshID_name.accdb")))
	{
		gy_ado.meshID_name_ado.m_pConn = NULL;
		gy_ado.meshID_name_ado.m_pRst = NULL;
		AfxMessageBox(gy_ado.meshID_name_ado.GetLastError());
		return FALSE;
	}
	if (!gy_ado.meshID_name_area_ado.Connect(CAdoLx::DBT_ACCESS, _T("./accessdatabase/meshID_name_area.accdb")))
	{
		gy_ado.meshID_name_area_ado.m_pConn = NULL;
		gy_ado.meshID_name_area_ado.m_pRst = NULL;
		AfxMessageBox(gy_ado.meshID_name_area_ado.GetLastError());
		return FALSE;
	}
	if (!gy_ado.meshID_name_area_group_ado.Connect(CAdoLx::DBT_ACCESS, _T("./accessdatabase/meshID_name_area_group.accdb")))
	{
		gy_ado.meshID_name_area_group_ado.m_pConn = NULL;
		gy_ado.meshID_name_area_group_ado.m_pRst = NULL;
		AfxMessageBox(gy_ado.meshID_name_area_group_ado.GetLastError());
		return FALSE;
	}
	return TRUE;
}
//断开数据库链接
BOOL gy_ado_close(void)//关闭数据库
{
	gy_ado.light_info_ado.m_pConn = NULL;
	gy_ado.light_info_ado.m_pRst = NULL;
	gy_ado.meshID_name_ado.m_pConn = NULL;
	gy_ado.meshID_name_ado.m_pRst = NULL;
	gy_ado.meshID_name_area_ado.m_pConn = NULL;
	gy_ado.meshID_name_area_ado.m_pRst = NULL;
	gy_ado.meshID_name_area_group_ado.m_pConn = NULL;
	gy_ado.meshID_name_area_group_ado.m_pRst = NULL;
	return TRUE;
}
//选择正确的串口设备之后,根据串口设备提供的meshID对相应的数据库初始化(该mesh网络在客户端本地数据库中是否已经存在,如果不存在,则添加相应的数据库表)
//在cmd_operation函数中调用(在之前的基础上修改数据库描述符)
BOOL gy_meshID_access_start(void)
{
	CString str_data;
	str_data.Format(_T("INSERT INTO meshID_name VALUES('%d%d%d%d%d', '%d%d%d%d%d')"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7], *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);
	if (gy_ado.meshID_name_ado.ExecSQL(str_data) < 0)//将新的mesh网络存储到meshID_name数据库表中
	{
		gy_ado.meshID_name_ado.m_pRst = NULL;
		//AfxMessageBox(gy_ado.user_info_ado.GetLastError());
		return FALSE;
	}
	gy_ado.meshID_name_ado.m_pRst = NULL;

	str_data.Format(_T("SELECT * INTO %d%d%d%d%d FROM 10000000 WHERE 1>2"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);
	if (!gy_ado.light_info_ado.Select(str_data))//如果meshID和密钥在本地数据库中没有存储,则新建该mesh网络下灯信息的数据库表
	{
		gy_ado.light_info_ado.m_pRst = NULL;
		AfxMessageBox(gy_ado.light_info_ado.GetLastError());
		return FALSE;
	}
	gy_ado.light_info_ado.m_pRst = NULL;

	str_data.Format(_T("CREATE UNIQUE INDEX Mac_addrIndex ON %d%d%d%d%d(mac_addr)"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);
	if (gy_ado.light_info_ado.ExecSQL(str_data) < 0)//给新建的数据库表设置无重复索引
	{
		gy_ado.light_info_ado.m_pRst = NULL;
		//AfxMessageBox(gy_ado.light_info_ado.GetLastError());
		//return FALSE;
	}
	gy_ado.light_info_ado.m_pRst = NULL;

	str_data.Format(_T("SELECT * INTO %d%d%d%d%d_area FROM 10000000_area WHERE 1>2"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);
	if (!gy_ado.meshID_name_area_ado.Select(str_data))//新建该mesh网络下灯信息的数据库表成功之后,继续新建区域对应关系表
	{
		gy_ado.meshID_name_area_ado.m_pRst = NULL;
		AfxMessageBox(gy_ado.meshID_name_area_ado.GetLastError());
		return FALSE;
	}
	gy_ado.meshID_name_area_ado.m_pRst = NULL;

	str_data.Format(_T("CREATE UNIQUE INDEX AreaIndex ON %d%d%d%d%d_area(area)"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);
	if (gy_ado.meshID_name_area_ado.ExecSQL(str_data) < 0)//给新建的数据库表设置无重复索引
	{
		gy_ado.meshID_name_area_ado.m_pRst = NULL;
		//AfxMessageBox(gy_ado.user_info_ado.GetLastError());
		//return FALSE;
	}
	gy_ado.meshID_name_area_ado.m_pRst = NULL;

	str_data.Format(_T("CREATE UNIQUE INDEX area_nameIndex ON %d%d%d%d%d_area(area_name)"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);
	if (gy_ado.meshID_name_area_ado.ExecSQL(str_data) < 0)//给新建的数据库表设置无重复索引
	{
		gy_ado.meshID_name_area_ado.m_pRst = NULL;
		//AfxMessageBox(gy_ado.user_info_ado.GetLastError());
		//return FALSE;
	}
	gy_ado.meshID_name_area_ado.m_pRst = NULL;

	str_data.Format(_T("INSERT INTO %d%d%d%d%d_area VALUES(0,'未分区域')"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);

	if (gy_ado.meshID_name_area_ado.ExecSQL(str_data) < 0)//在新表中添加未分组的相关数据
	{
		gy_ado.meshID_name_area_ado.m_pRst = NULL;
		AfxMessageBox(_T("添加未分区域失败"));
		return FALSE;
	}
	gy_ado.meshID_name_area_ado.m_pRst = NULL;

	str_data.Format(_T("SELECT * INTO %d%d%d%d%d_area_group FROM 10000000_area_group WHERE 1>2"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);
	if (!gy_ado.meshID_name_area_group_ado.Select(str_data))//新建该mesh网络下灯信息的数据库表成功之后,继续新建组对应关系表
	{
		gy_ado.meshID_name_area_group_ado.m_pRst = NULL;
		AfxMessageBox(gy_ado.meshID_name_area_group_ado.GetLastError());
		return FALSE;
	}
	gy_ado.meshID_name_area_group_ado.m_pRst = NULL;

	str_data.Format(_T("CREATE UNIQUE INDEX groupidIndex ON %d%d%d%d%d_area_group(groupid)"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);
	if (gy_ado.meshID_name_area_group_ado.ExecSQL(str_data) < 0)//给新建的数据库表设置无重复索引
	{
		gy_ado.meshID_name_area_group_ado.m_pRst = NULL;
		//AfxMessageBox(gy_ado.user_info_ado.GetLastError());
		//return FALSE;
	}
	gy_ado.meshID_name_area_group_ado.m_pRst = NULL;

	str_data.Format(_T("CREATE UNIQUE INDEX groupid_nameIndex ON %d%d%d%d%d_area_group(groupid_name)"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);
	if (gy_ado.meshID_name_area_group_ado.ExecSQL(str_data) < 0)//给新建的数据库表设置无重复索引
	{
		gy_ado.meshID_name_area_group_ado.m_pRst = NULL;
		//AfxMessageBox(gy_ado.user_info_ado.GetLastError());
		//return FALSE;
	}
	gy_ado.meshID_name_area_group_ado.m_pRst = NULL;

	str_data.Format(_T("INSERT INTO %d%d%d%d%d_area_group VALUES(0,0,'未分组')"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);

	if (gy_ado.meshID_name_area_group_ado.ExecSQL(str_data) < 0)//在新表中添加未分组的相关数据
	{
		gy_ado.meshID_name_area_group_ado.m_pRst = NULL;
		AfxMessageBox(_T("区域中添加未分组失败"));
		return FALSE;
	}
	gy_ado.meshID_name_area_group_ado.m_pRst = NULL;

	return TRUE;
}
//使用%d%d%d%d%d_area_group数据库表示,修改数据库描述符
BOOL gy_write_all_light_groupid(BYTE *hex)//将接收到的灯的groupid存储到数据库表中,在gy_ado_write函数中调用
{
	//方法二:较为简便
	CString ado_str;
	ado_str.Format(_T("UPDATE %d%d%d%d%d SET groupid = %d WHERE vir_addr = '%02x:%02x:%02x:%02x'"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7],  hex[12], hex[3], hex[2], hex[1], hex[0]);
	if (gy_ado.light_info_ado.ExecSQL(ado_str) < 0)
	{
		gy_ado.light_info_ado.m_pRst = NULL;
		AfxMessageBox(_T("添加获取的groupid失败"));
		return FALSE;
	}
	gy_ado.light_info_ado.m_pRst = NULL;

	ado_str.Format(_T("INSERT INTO %d%d%d%d%d_area_group VALUES(0,%d,'组%d')"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7], hex[12], hex[12]);

	if (gy_ado.meshID_name_area_group_ado.ExecSQL(ado_str) < 0)
	{
		gy_ado.meshID_name_area_group_ado.m_pRst = NULL;
		//AfxMessageBox(_T("区域中添加组失败"));
		return FALSE;
	}
	gy_ado.meshID_name_area_group_ado.m_pRst = NULL;

	return TRUE;
}

定义全局函数 gy_add_ado_to_tree:将数据库中的数据添加到树形控件中

BOOL gy_add_ado_to_tree(void)//从数据库中读取数据,并添加到树形控件
{
	CdonglepcDlg *main_dlg = (CdonglepcDlg*)gy_comx.main_dlg;//用于全局变量控制某一对话框的控件,详情见博客收藏
        main_dlg->m_tree.DeleteAllItems();    //删除树的所有节点
	CString ado_data;
	ado_data.Format(_T("SELECT * FROM meshID_name ORDER BY local_name ASC"));//local_name按照升序排列(meshID_name数据库表)
	if (!gy_ado.meshID_name_ado.Select(ado_data))
	{
		AfxMessageBox(gy_ado.meshID_name_ado.GetLastError());
		return FALSE;
	}
	while (!gy_ado.meshID_name_ado.IsEOF())//当没有到达查询数据的最末尾(这一层填充根节点,为meshID网络)***********************************1
	{
		gy_ado.meshID_name_ado.GetFieldByIndex(1, ado_data);//获取local_name列对应的值(字符串)
		HTREEITEM hRoot = main_dlg->m_tree.InsertItem(ado_data);

		ado_data.Format(_T("SELECT * FROM %d%d%d%d%d_area ORDER BY area_name ASC"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);//area_name按照升序排列(%d%d%d%d%d_area数据库表)
		if (!gy_ado.meshID_name_area_ado.Select(ado_data))
		{
			AfxMessageBox(gy_ado.meshID_name_area_ado.GetLastError());
			return FALSE;
		}
		while (!gy_ado.meshID_name_area_ado.IsEOF())//当没有到达查询数据的最末尾(填充第一层子节点,为区域)*************************************2
		{
			gy_ado.meshID_name_area_ado.GetFieldByIndex(1, ado_data);//获取area_name列对应的值(字符串)

			HTREEITEM node1 = main_dlg->m_tree.InsertItem(ado_data, hRoot);//在hRoot节点下添加子节点

			BYTE area_byte;
			gy_ado.meshID_name_area_ado.GetFieldByIndex(0, area_byte);//获取area列对应的值(字节)

			CString ado_data1;
			ado_data1.Format(_T("SELECT * FROM %d%d%d%d%d_area_group WHERE area=%d ORDER BY groupid_name ASC"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7], area_byte);//area_name按照升序排列(%d%d%d%d%d_area数据库表)
			if (!gy_ado.meshID_name_area_group_ado.Select(ado_data1))
			{
				AfxMessageBox(gy_ado.meshID_name_area_group_ado.GetLastError());
				return FALSE;
			}
			while (!gy_ado.meshID_name_area_group_ado.IsEOF())//当没有到达查询数据的最末尾(填充第二层子节点,为groupid)********************************3
			{
				gy_ado.meshID_name_area_group_ado.GetFieldByIndex(2, ado_data);//获取groupid_name列对应的值(字符串)
				HTREEITEM node2 = main_dlg->m_tree.InsertItem(ado_data, node1);//在node1节点下添加子节点

				BYTE groupid_byte;
				gy_ado.meshID_name_area_group_ado.GetFieldByIndex(1, groupid_byte);//获取area列对应的值(字节)

				CString ado_data1;
				ado_data1.Format(_T("SELECT * FROM %d%d%d%d%d WHERE groupid=%d ORDER BY mac_addr ASC"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7], groupid_byte);//area_name按照升序排列(%d%d%d%d%d_area数据库表)
				if (!gy_ado.light_info_ado.Select(ado_data1))
				{
					AfxMessageBox(gy_ado.light_info_ado.GetLastError());
					return FALSE;
				}
				while (!gy_ado.light_info_ado.IsEOF())//当没有到达查询数据的最末尾(最有一层叶子节点,为灯mac)********************************4
				{
					gy_ado.light_info_ado.GetFieldByIndex(0, ado_data);//获取mac_addr列对应的值(字符串)
					HTREEITEM node3 = main_dlg->m_tree.InsertItem(ado_data, node2);//在node2节点下添加子节点
					gy_ado.light_info_ado.MoveNext();//指向下一行
				}

				gy_ado.meshID_name_area_group_ado.MoveNext();//指向下一行
			}

			gy_ado.meshID_name_area_ado.MoveNext();//指向下一行
		}

		gy_ado.meshID_name_ado.MoveNext();//指向下一行
	}
	return TRUE;
}

从数据库中读取数据并添加到树形控件中:屏蔽掉函数cmd_operation中case 3位置主动获取灯信息的相关功能(使用数据库现有的数据),case 'i'位置添加函数gy_add_ado_to_tree,将数据库中的数据添加到树形控件中。

int cmd_operation(BYTE *str, int length)//将一个完整的数据命令(0xCC 0xCC结尾)解析并做相应的操作
{
    ......
    case 'i'://mesh网络id和密钥
    {
        ......
        gy_add_ado_to_tree();//从数据库中读取数据,并添加到树形控件(前文有详解)
        break;
    }
    ......    
    case 's'://获取dongle网络状态
    {
        ......
        case 3:
        {
            gy_comx.dongle_state.Format(_T("网络状态:成功加入mesh网络"));
            /*以下代码临时注释掉*/
            //gy_all_light_ping();//主动ping灯,获取灯的信息//77 01 01 1B 66
            //gy_ado.write_accessdata_state = write_ping;//正在写什么数据到数据库中
            ////重新开始计时,如果1秒钟之内没有收到其他ping回来的数据,则表示所有数据接收完成
            //gy_ado.write_accessdata_timer_count = 0;//写数据库计时(write_ping、 write_groupid、 write_light_status是否超时,如果超时,则表示已经全部写入完成)
            break;
        }
        ......
    }
    ......
}

运行程序,效果图:

选择树形控件的checkbox时,其他checkbox也要做出相应的逻辑响应

参考文章:https://blog.csdn.net/a_dev/article/details/84580517

树形控件添加响应函数:当用户已在树形控件内单击了鼠标左键,则相应该函数

响应函数具体定义:

//注意在该响应函数执行完之前,你所获取的树形控件复选框状态还是未执行该函数时的复选框状态
void CdonglepcDlg::OnNMClickTree1(NMHDR *pNMHDR, LRESULT *pResult)//当用户已在树形控件内单击了鼠标左键
{
	// TODO: 在此添加控件通知处理程序代码
	UINT uFlag;
	CPoint point;
	::GetCursorPos(&point);
	::ScreenToClient(m_tree.m_hWnd, &point);
	HTREEITEM   hItem = m_tree.HitTest(point, &uFlag);//HitTest函数能够得到与当前鼠标位置相关的项的句柄

	if (uFlag & TVHT_ONITEMSTATEICON)//若点击CHECKBOX则传递TVHT_ONITEMSTATEICON
	{
		m_tree.SelectItem(hItem);//将当前选中的父接点或子接点高亮
		BOOL ItemState = !m_tree.GetCheck(hItem);//设置树状结构的状态,当前状态取反则以
		SetCheckStatus(hItem, ItemState);//树形控件中勾选框 处理点击操作,先处理子级,再根据本级兄弟节点状态,决定是否处理父级。在函数OnNMClickTree1中调用(该函数下文详解)
	}

	*pResult = 0;
}
//树形控件中勾选框 处理点击操作,先处理子级,再根据本级兄弟节点状态,决定是否处理父级。在函数OnNMClickTree1中调用
void CdonglepcDlg::SetCheckStatus(HTREEITEM hTreeItem, BOOL bCheck)
{
    //下文详解
    SetChildCheckStatus(hTreeItem, bCheck);//树形控件中勾选框 处理子级的递归方法,在函数SetCheckStatus中调用
    //下文详解
    SetParentCheckStatus(hTreeItem, bCheck);//树形控件中勾选框 处理父级的递归方法,在函数SetCheckStatus中调用
}
void CdonglepcDlg::SetChildCheckStatus(HTREEITEM hTreeItem, BOOL bCheck)//树形控件中勾选框 处理子级的递归方法,在函数SetCheckStatus中调用
{
	if (hTreeItem == nullptr)
	{
		return;
	}
	//子节点方向
	HTREEITEM hLayer = m_tree.GetChildItem(hTreeItem);//获取当前接点的子接点第一个句柄
	while (nullptr != hLayer)
	{
		m_tree.SetCheck(hLayer, bCheck);//设置当前接点的状态
		SetChildCheckStatus(hLayer, bCheck);//嵌套子节点(递归)
		hLayer = m_tree.GetNextItem(hLayer, TVGN_NEXT);//获取子接点中的下一个接点的句柄
	}
}

void CdonglepcDlg::SetParentCheckStatus(HTREEITEM hTreeItem, BOOL bCheck)//树形控件中勾选框 处理父级的递归方法,在函数SetCheckStatus中调用
{
	if (hTreeItem == nullptr)
	{
		return;
	}

	HTREEITEM hParent = m_tree.GetParentItem(hTreeItem);//获取树形控件中某个指定节点的父节点。参数hItem同上。返回值是父节点的句柄。
	if (hParent == nullptr)
	{
		return;
	}

	BOOL hParent_check = 0;//记录父节点勾选框最终设置的状态

	if (bCheck == 0)//如果当前节点本身的状态为未勾选,则父节点状态一定设置为为勾选状态
	{
		hParent_check = bCheck;
		m_tree.SetCheck(hParent, hParent_check);//设置父节点的状态为未勾选
	}
	else
	{
		HTREEITEM hLayer = m_tree.GetChildItem(hParent);//获取当前节点的子节点第一个句柄
		while (nullptr != hLayer)
		{
			BOOL ItemState = m_tree.GetCheck(hLayer);//获取当前节点复选框状态

			if (ItemState == 0 && hLayer != hTreeItem)//如果当前节点复选框状态为未勾选,则父节点也设置为未勾选,并跳出循环,进入下一层嵌套
			{
				hParent_check = ItemState;
				m_tree.SetCheck(hParent, hParent_check);//设置父节点的状态为未勾选
				break;
			}
			hLayer = m_tree.GetNextItem(hLayer, TVGN_NEXT);//获取子接点中的下一个接点的句柄
		}

		if (hLayer == nullptr)
		{
			hParent_check = 1;
			m_tree.SetCheck(hParent, hParent_check);//设置父节点的状态为勾选状态
		}
	}

	SetParentCheckStatus(hParent, hParent_check);//嵌套(递归),设置再上一层的父节点
}

效果图

给数据库添加临界值,避免数据库使用混乱

在结构体变量gy_ado中添加数据库临界值

CRITICAL_SECTION light_info_cs;//对应数据库临界值
CRITICAL_SECTION meshID_name_cs;//对应数据库临界值
CRITICAL_SECTION meshID_name_area_cs;//对应数据库临界值
CRITICAL_SECTION meshID_name_area_group_cs;//对应数据库临界值

ado_init函数中初始化数据库临界值

void ado_init(void)//初始化数据库相关函数和变量,在初始化函数OnInitDialog中调用
{
    ......
	InitializeCriticalSection(&gy_ado.light_info_cs);//初始化数据库临界值
	InitializeCriticalSection(&gy_ado.meshID_name_cs);//初始化数据库临界值
	InitializeCriticalSection(&gy_ado.meshID_name_area_cs);//初始化数据库临界值
	InitializeCriticalSection(&gy_ado.meshID_name_area_group_cs);//初始化数据库临界值
    ......
}

gy_ado_close函数中释放临界值(注意临界值的释放:当数据库不被占用的时候,再去释放数据库临界值)

//注意临界值的释放:当数据库不被占用的时候,再去释放数据库临界值
BOOL gy_ado_close(void)//关闭数据库,在OnBnClickedButton2函数中调用(程序退出按钮被按下之后)
{
	gy_ado.light_info_ado.m_pConn = NULL;
	gy_ado.light_info_ado.m_pRst = NULL;
	gy_ado.meshID_name_ado.m_pConn = NULL;
	gy_ado.meshID_name_ado.m_pRst = NULL;
	gy_ado.meshID_name_area_ado.m_pConn = NULL;
	gy_ado.meshID_name_area_ado.m_pRst = NULL;
	gy_ado.meshID_name_area_group_ado.m_pConn = NULL;
	gy_ado.meshID_name_area_group_ado.m_pRst = NULL;

	//如果数据库正在被使用(如:正在被写入数据),则最好不要强行释放数据库临界值,可能会导致不必要的错误
	//所以当数据库解除占用之后,再释放数据库临界值
	EnterCriticalSection(&gy_ado.light_info_cs);//锁住数据库,暂时只能这里使用,直到数据库不再被占用(该位置用于检测)
	DeleteCriticalSection(&gy_ado.light_info_cs);//释放数据库临界值

	EnterCriticalSection(&gy_ado.meshID_name_cs);//锁住数据库,暂时只能这里使用,直到数据库不再被占用(该位置用于检测)
	DeleteCriticalSection(&gy_ado.meshID_name_cs);//释放数据库临界值

	EnterCriticalSection(&gy_ado.meshID_name_area_cs);//锁住数据库,暂时只能这里使用,直到数据库不再被占用(该位置用于检测)
	DeleteCriticalSection(&gy_ado.meshID_name_area_cs);//释放数据库临界值

	EnterCriticalSection(&gy_ado.meshID_name_area_group_cs);//锁住数据库,暂时只能这里使用,直到数据库不再被占用(该位置用于检测)
	DeleteCriticalSection(&gy_ado.meshID_name_area_group_cs);//释放数据库临界值

	return TRUE;
}

在使用到数据库的地方添加临界值功能,具体过程和代码略。注意不要漏写,也不要写错,不然程序运行会使数据库混乱。

存储灯信息的数据库表中,如果灯没有被分组,则设置灯的groupid为0,便于树形控件检索并显示灯信息

在将ping灯之后灯返回的数据添加到数据库中时,把灯的groupid初始化为0一并写入到数据库中,后面获取灯分组信息时再将数据库刷新即可

BOOL gy_write_all_light_mac_vir_addr(BYTE *hex)//将接收到的灯的MAC地址和虚拟地址存储到数据库表中
{
	//方法二:该方法比较简洁,作者打算使用这种方法存储数据到数据库
	CString ado_str;
        //注意多添加一个groupid值(值为0)
	ado_str.Format(_T("INSERT INTO %d%d%d%d%d(mac_addr,vir_addr,groupid) VALUES('%02x:%02x:%02x:%02x:%02x:%02x','%02x:%02x:%02x:%02x',0)"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7],  hex[5], hex[4], hex[3], hex[2], hex[1], hex[0], hex[9], hex[8], hex[7], hex[6]);

	EnterCriticalSection(&gy_ado.light_info_cs);//锁住数据库,暂时只能这里使用
	if (gy_ado.light_info_ado.ExecSQL(ado_str) < 0)
	{
		ado_str.Format(_T("UPDATE %d%d%d%d%d SET vir_addr = '%02x:%02x:%02x:%02x' WHERE mac_addr = '%02x:%02x:%02x:%02x:%02x:%02x'"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7], hex[9], hex[8], hex[7], hex[6], hex[5], hex[4], hex[3], hex[2], hex[1], hex[0]);
		if (gy_ado.light_info_ado.ExecSQL(ado_str) < 0)
		{
			gy_ado.light_info_ado.m_pRst = NULL;
			LeaveCriticalSection(&gy_ado.light_info_cs);//解锁数据库,其他线程或者函数可以使用
			AfxMessageBox(_T("添加数据库时修改虚拟地址失败"));
			return FALSE;
		}
	}
	gy_ado.light_info_ado.m_pRst = NULL;
	LeaveCriticalSection(&gy_ado.light_info_cs);//解锁数据库,其他线程或者函数可以使用
	return TRUE;
}

效果图:未分组的灯也能显示出来

串口设备被选中到数据库中信息添加到树形控件过程梳理(假设Dongle处在mesh网络里)

串口设备被选中,程序会发送获取串口设备信息的指令

//当组合框控件的选择发生变化时,触发此消息:打开对应串口,发送指令(77 01 01 13 66),接收版本号、MAC地址等数据
void CdonglepcDlg::OnCbnSelchangeCombo1()
{
    ......
    BYTE gy_uart_tx[] = { 0x77, 0x01, 0x01, 0x66, 0x66 };
    DWORD dwBytesWrite = sizeof(gy_uart_tx);
    led_control(gy_uart_tx, dwBytesWrite);//通过串口发送灯控命令
}

程序接收到设备返回的meshID之后,判断数据库中是否存在该meshID,如果不存在,则新建相关数据库表,并且添加相关信息

int cmd_operation(BYTE *str, int length)//将一个完整的数据命令(0xCC 0xCC结尾)解析并做相应的操作
{
    ......
    case 'i'://mesh网络id和密钥
    {
        ......
        //选择正确的串口设备之后,根据串口设备提供的meshID对相应的数据库初始化(该mesh网络在客户端本地数据库中是否已经存在,如果不存在,则添加相应的数据库表)
	//在cmd_operation函数中调用
	gy_meshID_access_start();
        break;
    }
    ......
}

程序接收到设备返回的成功加入mesh网络的信息之后,开始ping灯,获取灯的mac、虚拟地址、groupid等信息

int cmd_operation(BYTE *str, int length)//将一个完整的数据命令(0xCC 0xCC结尾)解析并做相应的操作
{
    ......
    case 's'://获取dongle网络状态
    {
        ......
        case 3:
        {
            gy_comx.dongle_state.Format(_T("网络状态:成功加入mesh网络"));
            gy_all_light_ping();//主动ping灯,获取灯的信息//77 01 01 1B 66
            gy_ado.write_accessdata_state = write_ping;//正在写什么数据到数据库中
            //重新开始计时,如果1秒钟之内没有收到其他ping回来的数据,则表示所有数据接收完成
            gy_ado.write_accessdata_timer_count = 0;//写数据库计时(write_ping、 write_groupid、 write_light_status是否超时,如果超时,则表示已经全部写入完成)
        }
        ......  
        break;
    }
    ......
}

在所有灯状态接收完毕之后,将设备信息添加到树形控件中

//在选择正确串口之后,获取灯的mac地址和虚拟地址、groupid、灯状态,并且以此刷新或者存储到数据库中,在timer_pThread_func函数中调用
void get_mesh_light_info_at_start(void)
{
    ......
	else if (gy_ado.write_accessdata_state == write_light_status)
	{
		gy_ado.write_accessdata_state = write_idle;//正在写什么数据到数据库中(write_idle、 write_ping、 write_groupid、 write_light_status)
		gy_ado.write_accessdata_timer_count = TIMER_NO_WORK;//所有信息获取完毕,关闭计时

		gy_add_ado_to_tree();//从数据库中读取数据,并添加到树形控件
	}
}

实现功能:点击树形控件复选框,复选框打钩,则发送开灯指令,复选框去掉钩则发送关灯指令

主对话框类中添加函数GetCurrentLayer,得到鼠标点击树形控件的第几层

BYTE CdonglepcDlg::GetCurrentLayer(HTREEITEM hTreeItem)//获取鼠标点击的当前位置为树形控件的第几层(根节点规定为第1层)
{
	BYTE Layer_Count = 1;//定义临时变量,记录鼠标点击的位置为树形控件的第几层
	if (hTreeItem == nullptr)//如果当前节点句柄为空,则表示没有选中任何节点,返回0
	{
		Layer_Count = 0;
		return Layer_Count;
	}
	HTREEITEM hParent = hTreeItem;//初始化父节点为当前节点
	while ((hParent = m_tree.GetParentItem(hParent)) != nullptr)//如果节点有父节点,则Layer_Count加1
	{
		Layer_Count++;
	}
	return Layer_Count;
}

主对话框类中添加函数,根据鼠标点击树形控件的第几层和复选框状态控制开关灯

void CdonglepcDlg::Control_Light_Open_Close(HTREEITEM hTreeItem, BYTE Layer_Count, BOOL ItemState)//根据鼠标点击树形控件的第几层和复选框状态控制开关灯
{
	CString tree_data = m_tree.GetItemText(hTreeItem);
	int n = tree_data.Find(_T(" ("));
	if (n != -1)//如果当前节点中存在灯在线和离线数量时,将tree_data的在线离线数量信息去除,保留该字符串与数据库中相一致的信息
	{
		//代码暂时保留,目前还没有在节点上添加在线和离线信息
	}

	UINT8 x, y;
	if (ItemState)
	{
		x = 0x37;
		y = 0x37;
	}
	else
	{
		x = 0x32;
		y = 0x32;
	}
	switch (Layer_Count)
	{
		case 1://如果点击的是第一层,全控(开关灯)
		{
			gy_ctl_all_cct(x, y);//对全部灯进行调节(亮暗冷暖、开灯关灯)77 01 03 15 x y 66(全关:32 32  全开:37 37)
			break;
		}
		case 2://如果点击的是第二层,区域控(开关灯)
		{
			BYTE area_byte = 0;//根据区域名获取对应的区域(BYTE area)
			CString ado_data;
			ado_data.Format(_T("SELECT * FROM %d%d%d%d%d_area WHERE area_name='"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);
			ado_data = ado_data + tree_data +_T('\'');
			EnterCriticalSection(&gy_ado.meshID_name_area_cs);//锁住数据库,暂时只能这里使用
			if (!gy_ado.meshID_name_area_ado.Select(ado_data))
			{
				gy_ado.meshID_name_area_ado.m_pRst = NULL;
				LeaveCriticalSection(&gy_ado.meshID_name_area_cs);//解锁数据库,其他线程或者函数可以使用
				AfxMessageBox(gy_ado.meshID_name_area_ado.GetLastError());
				return ;
			}
			if(!gy_ado.meshID_name_area_ado.IsEOF())
			{
				gy_ado.meshID_name_area_ado.GetFieldByIndex(0, area_byte);//获取area列对应的值(字节)
			}
			gy_ado.meshID_name_area_ado.m_pRst = NULL;
			LeaveCriticalSection(&gy_ado.meshID_name_area_cs);//解锁数据库,其他线程或者函数可以使用

			ado_data.Format(_T("SELECT * FROM %d%d%d%d%d_area_group WHERE area=%d ORDER BY groupid ASC"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7], area_byte);
			EnterCriticalSection(&gy_ado.meshID_name_area_group_cs);//锁住数据库,暂时只能这里使用
			if (!gy_ado.meshID_name_area_group_ado.Select(ado_data))
			{
				gy_ado.meshID_name_area_group_ado.m_pRst = NULL;
				LeaveCriticalSection(&gy_ado.meshID_name_area_group_cs);//解锁数据库,其他线程或者函数可以使用
				AfxMessageBox(gy_ado.meshID_name_area_group_ado.GetLastError());
				return ;
			}
			while (!gy_ado.meshID_name_area_group_ado.IsEOF())//当没有到达查询数据的最末尾
			{
				BYTE group_byte = 0;
				gy_ado.meshID_name_area_group_ado.GetFieldByIndex(1, group_byte);//获取area列对应的值(字节)
				if (group_byte > 0 && group_byte <= 255)
				{
					gy_ctl_group_cct(group_byte, x, y);//对单组灯进行调节(亮暗冷暖、开灯关灯)77 01 04 16 groupid x y 66
				}
				gy_ado.meshID_name_area_group_ado.MoveNext();//指向下一行
			}
			gy_ado.meshID_name_area_group_ado.m_pRst = NULL;
			LeaveCriticalSection(&gy_ado.meshID_name_area_group_cs);//解锁数据库,其他线程或者函数可以使用
			break;
		}
		case 3://如果点击的是第三层,组控(开关灯)
		{
			CString ado_data;
			ado_data.Format(_T("SELECT * FROM %d%d%d%d%d_area_group WHERE groupid_name='"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);
			ado_data = ado_data + tree_data + _T('\'');
			EnterCriticalSection(&gy_ado.meshID_name_area_group_cs);//锁住数据库,暂时只能这里使用
			if (!gy_ado.meshID_name_area_group_ado.Select(ado_data))
			{
				gy_ado.meshID_name_area_group_ado.m_pRst = NULL;
				LeaveCriticalSection(&gy_ado.meshID_name_area_group_cs);//解锁数据库,其他线程或者函数可以使用
				AfxMessageBox(gy_ado.meshID_name_area_group_ado.GetLastError());
				return;
			}
			if (!gy_ado.meshID_name_area_group_ado.IsEOF())//当没有到达查询数据的最末尾
			{
				BYTE group_byte = 0;
				gy_ado.meshID_name_area_group_ado.GetFieldByIndex(1, group_byte);//获取groupid列对应的值(字节)
				if (group_byte > 0 && group_byte <= 255)
				{
					gy_ctl_group_cct(group_byte, x, y);//对单组灯进行调节(亮暗冷暖、开灯关灯)77 01 04 16 groupid x y 66
				}
			}
			gy_ado.meshID_name_area_group_ado.m_pRst = NULL;
			LeaveCriticalSection(&gy_ado.meshID_name_area_group_cs);//解锁数据库,其他线程或者函数可以使用
			break;
		}
		case 4://如果点击的是第三层,单控(开关灯)
		{
			CString ado_data;
			ado_data.Format(_T("SELECT * FROM %d%d%d%d%d WHERE mac_addr='"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);
			ado_data = ado_data + tree_data + _T('\'');
                        EnterCriticalSection(&gy_ado.light_info_cs);//锁住数据库,暂时只能这里使用
			if (!gy_ado.light_info_ado.Select(ado_data))
			{
				gy_ado.light_info_ado.m_pRst = NULL;
				LeaveCriticalSection(&gy_ado.light_info_cs);//解锁数据库,其他线程或者函数可以使用
				AfxMessageBox(gy_ado.light_info_ado.GetLastError());
				return;
			}
			if (!gy_ado.light_info_ado.IsEOF())//当没有到达查询数据的最末尾
			{
				CString vir_addr;
				gy_ado.light_info_ado.GetFieldByIndex(1, vir_addr);//获取vir_addr列对应的值(字符串)    
                               //gy_str_2_to_hex函数下文详解://将两个以上字节的CString字符串前两个字节转化为16进制
				gy_ctl_single_cct(gy_str_2_to_hex(vir_addr.Right(2)), gy_str_2_to_hex(vir_addr.Mid(6,2)), gy_str_2_to_hex(vir_addr.Mid(3,2)), gy_str_2_to_hex(vir_addr.Left(2)), x, y);//对单个灯调节//77 01 07 1C vir_1 vir_2 vir_3 vir_4 x y 66
			}
			gy_ado.light_info_ado.m_pRst = NULL;
			LeaveCriticalSection(&gy_ado.light_info_cs);//解锁数据库,其他线程或者函数可以使用
			break;
		}
	}
	return;
}

上面代码提到的函数gy_str_2_to_hex具体定义

//将两个以上字节的CString字符串前两个字节转化为16进制
int gy_str_2_to_hex(CString str)
{
    ////将一个字节的CString字符串转化为16进制(后文详解)
	return gy_str_1_to_hex(str.Left(1))*16 + gy_str_1_to_hex(str.Mid(1,1));
}

上面代码提到的函数gy_str_1_to_hex具体定义

BYTE gy_str_1_to_hex(CString str)//将一个字节的CString字符串转化为16进制
{
	int num = -1;
	if (str == _T("0")|| str == _T("1") || str == _T("2") || str == _T("3") || str == _T("4") || str == _T("5") || str == _T("6") || str == _T("7") || str == _T("8") || str == _T("9"))
	{
		num = _ttoi(str);
	}
	else if (str == _T("a"))
	{
		num = 0x0A;
	}
	else if (str == _T("b"))
	{
		num = 0x0B;
	}
	else if (str == _T("c"))
	{
		num = 0x0C;
	}
	else if (str == _T("d"))
	{
		num = 0x0D;
	}
	else if (str == _T("e"))
	{
		num = 0x0E;
	}
	else if (str == _T("f"))
	{
		num = 0x0F;
	}
	return num;
}

在鼠标点击树形控件的复选框时,执行控灯指令

//具体添加1和2的位置
void CdonglepcDlg::OnNMClickTree1(NMHDR *pNMHDR, LRESULT *pResult)//当用户已在树形控件内单击了鼠标左键
{
	// TODO: 在此添加控件通知处理程序代码
	UINT uFlag;
	CPoint point;
	::GetCursorPos(&point);
	::ScreenToClient(m_tree.m_hWnd, &point);
	HTREEITEM   hItem = m_tree.HitTest(point, &uFlag);//HitTest函数能够得到与当前鼠标位置相关的项的句柄

	if (uFlag & TVHT_ONITEMSTATEICON)//若点击CHECKBOX则传递TVHT_ONITEMSTATEICON
	{
		m_tree.SelectItem(hItem);//将当前选中的父接点或子接点高亮
		BOOL ItemState = !m_tree.GetCheck(hItem);//设置树状结构的状态,当前状态取反则以
                //********************************************1*****************
		BYTE Layer_Count = GetCurrentLayer(hItem);//获取鼠标点击的当前位置为树形控件的第几层(根节点规定为第1层)
                //********************************************2*****************
		Control_Light_Open_Close(hItem, Layer_Count, ItemState);//根据鼠标点击树形控件的第几层和复选框状态控制开关灯

		SetCheckStatus(hItem, ItemState);//树形控件中勾选框 处理点击操作,先处理子级,再根据本级兄弟节点状态,决定是否处理父级。在函数OnNMClickTree1中调用
	}

	*pResult = 0;
}

执行程序,可控灯

在树形控件中添加灯状态图片

准备好灯在线和不在线的图片

将.ico图片添加到工程

主对话框类中添加公有成员变量,用于为树形控件节点添加图片

// 图像列表类对象  
CImageList m_imagelist;

在OnInitDialog函数中加载图标

//树形控件添加图标
HICON icon[3];
icon[0] = AfxGetApp()->LoadIcon(IDI_ICON9);
icon[1] = AfxGetApp()->LoadIcon(IDI_ICON4);
icon[2] = AfxGetApp()->LoadIcon(IDI_ICON6);
m_imagelist.Create(18, 18, ILC_COLOR32 | ILC_MASK, 3, 3); //16,16为图标分辩率,4,4为该list最多能容纳的图标数
for (int i = 0; i < 3; i++)

{

	m_imagelist.Add(icon[i]); //读入图标

}
m_tree.SetImageList(&m_imagelist, TVSIL_NORMAL);

相应的在头文件添加枚举变量,设置节点添加什么图标时使用

enum {
	node_image = 0,//非灯节点的图标
	offline_image,//灯节点不在线图标
	online_image,//灯节点在线图标
};

在gy_add_ado_to_tree函数中给最底层的叶子节点添加在线图标(其他节点不设置时,则默认使用第一个图标)

//1所在位置添加灯的在线图标(测试使用)
BOOL gy_add_ado_to_tree(void)//从数据库中读取数据,并添加到树形控件
{
    ......
	while (!gy_ado.light_info_ado.IsEOF())//当没有到达查询数据的最末尾(最有一层叶子节点,为灯mac)********************************4
	{
		gy_ado.light_info_ado.GetFieldByIndex(0, ado_data);//获取mac_addr列对应的值(字符串)
		HTREEITEM node3 = main_dlg->m_tree.InsertItem(ado_data, node2);//在node2节点下添加子节点
        //**************************1**********************
		main_dlg->m_tree.SetItemImage(node3, online_image, online_image);//最底层的叶子节点添加图标
		gy_ado.light_info_ado.MoveNext();//指向下一行
	}
    ......
}

效果图

灯在线和不在线使用不同的图标区分开来

在读取dongle的meshID和密钥时初始化灯信息数据库表中的online_flag,将其全部置1(默认不在线),等获取到ping灯的返回信息之后再将其置2(此时灯在线),如果有些灯没有返回相关的信息,则online_flag仍然为1,表示灯处于离线状态,最后根据online_flag的值来添加灯相应的图标

void gy_online_flag_init(void)//灯信息数据库表online_flag值初始化为1,在cmd_operation函数中调用
{
	CString ado_str;
	ado_str.Format(_T("UPDATE %d%d%d%d%d SET online_flag = 1"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);
	EnterCriticalSection(&gy_ado.light_info_cs);//锁住数据库,暂时只能这里使用
	if (gy_ado.light_info_ado.ExecSQL(ado_str) < 0)
	{
		gy_ado.light_info_ado.m_pRst = NULL;
		LeaveCriticalSection(&gy_ado.light_info_cs);//解锁数据库,其他线程或者函数可以使用
		AfxMessageBox(_T("添加获取的groupid失败"));
		return ;
	}
	gy_ado.light_info_ado.m_pRst = NULL;
	LeaveCriticalSection(&gy_ado.light_info_cs);//解锁数据库,其他线程或者函数可以使用
	return;
}
//1所在的位置添加初始化online_flag的值
int cmd_operation(BYTE *str, int length)//将一个完整的数据命令(0xCC 0xCC结尾)解析并做相应的操作
{
    ......
    case 'i'://mesh网络id和密钥
    {
        ......
        gy_online_flag_init();//灯信息数据库表online_flag值初始化为1*****************1
        ......
    }
    ......
}

在接收ping灯返回的信息添加到数据库时,将online_flag置为2

BOOL gy_write_all_light_mac_vir_addr(BYTE *hex)//将接收到的灯的MAC地址和虚拟地址存储到数据库表中
{
	//方法二:该方法比较简洁,作者打算使用这种方法存储数据到数据库
	CString ado_str;
	ado_str.Format(_T("INSERT INTO %d%d%d%d%d(mac_addr,vir_addr,groupid,online_flag) VALUES('%02x:%02x:%02x:%02x:%02x:%02x','%02x:%02x:%02x:%02x',0,2)"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7],  hex[5], hex[4], hex[3], hex[2], hex[1], hex[0], hex[9], hex[8], hex[7], hex[6]);

	EnterCriticalSection(&gy_ado.light_info_cs);//锁住数据库,暂时只能这里使用
	if (gy_ado.light_info_ado.ExecSQL(ado_str) < 0)
	{
		ado_str.Format(_T("UPDATE %d%d%d%d%d SET vir_addr = '%02x:%02x:%02x:%02x',online_flag=2 WHERE mac_addr = '%02x:%02x:%02x:%02x:%02x:%02x'"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7], hex[9], hex[8], hex[7], hex[6], hex[5], hex[4], hex[3], hex[2], hex[1], hex[0]);
		if (gy_ado.light_info_ado.ExecSQL(ado_str) < 0)
		{
			gy_ado.light_info_ado.m_pRst = NULL;
			LeaveCriticalSection(&gy_ado.light_info_cs);//解锁数据库,其他线程或者函数可以使用
			AfxMessageBox(_T("添加数据库时修改虚拟地址失败"));
			return FALSE;
		}
	}
	gy_ado.light_info_ado.m_pRst = NULL;
	LeaveCriticalSection(&gy_ado.light_info_cs);//解锁数据库,其他线程或者函数可以使用
	return TRUE;
}

修改函数gy_add_ado_to_tree:根据灯的状态添加对应图标,并且将在线的灯和不在线的灯分开(一个组里面,前面部分显示在线的灯,后面部分显示不在线的灯)

//1所在位置添加灯的在线图标(测试使用)
BOOL gy_add_ado_to_tree(void)//从数据库中读取数据,并添加到树形控件
{
    ......
    //online_flag按照降序排列
    ado_data1.Format(_T("SELECT * FROM %d%d%d%d%d WHERE groupid=%d ORDER BY online_flag DESC"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7], groupid_byte);(%d%d%d%d%d_area数据库表)
    ......
	while (!gy_ado.light_info_ado.IsEOF())//当没有到达查询数据的最末尾(最有一层叶子节点,为灯mac)********************************4
	{
		gy_ado.light_info_ado.GetFieldByIndex(0, ado_data);//获取mac_addr列对应的值(字符串)
		HTREEITEM node3 = main_dlg->m_tree.InsertItem(ado_data, node2);//在node2节点下添加子节点
        //**************************1**********************
		main_dlg->m_tree.SetItemImage(node3, online_image, online_image);//最底层的叶子节点添加图标
		gy_ado.light_info_ado.MoveNext();//指向下一行
	}
    ......
}

效果图

树形控件mesh网络节点、区域节点、组节点末尾处添加“在线灯数量/灯的总数量

 在gy_add_ado_to_tree函数中添加计数变量,用于记录网络节点、区域节点、组节点下灯数量的相关信息,并展开根节点和区域节点

//1-25所在的位置按照顺序添加,可按照该顺序阅读理解
BOOL gy_add_ado_to_tree(void)//从数据库中读取数据,并添加到树形控件
{
	int all_light_count = 0;//记录灯总数量**************************************1
	int all_light_online_count = 0;//记录在线灯的总数量***********************************2
	int area_light_count = 0;//记录某一区域内灯总数量********************************************3
	int area_light_online_count = 0;//记录某一区域内灯在线数量*******************************************4
	int group_light_count = 0;//记录某一组内灯总数量********************************************************5
	int group_light_online_count = 0;//记录某一组内灯在线总数量*************************************************6

	CdonglepcDlg *main_dlg = (CdonglepcDlg*)gy_comx.main_dlg;//用于全局变量控制某一对话框的控件,详情见博客收藏
	main_dlg->m_tree.DeleteAllItems();    //删除树的所有节点
	CString ado_data;
	ado_data.Format(_T("SELECT * FROM meshID_name  WHERE meshID = '%d%d%d%d%d' ORDER BY local_name ASC"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);//local_name按照升序排列(meshID_name数据库表)
	EnterCriticalSection(&gy_ado.meshID_name_cs);//锁住数据库,暂时只能这里使用
	if (!gy_ado.meshID_name_ado.Select(ado_data))
	{
		gy_ado.meshID_name_ado.m_pRst = NULL;
		LeaveCriticalSection(&gy_ado.meshID_name_cs);//解锁数据库,其他线程或者函数可以使用
		AfxMessageBox(gy_ado.meshID_name_ado.GetLastError());
		return FALSE;
	}
	while (!gy_ado.meshID_name_ado.IsEOF())//当没有到达查询数据的最末尾(这一层填充根节点,为meshID网络)
	{
		gy_ado.meshID_name_ado.GetFieldByIndex(1, ado_data);//获取local_name列对应的值(字符串)
		HTREEITEM hRoot = main_dlg->m_tree.InsertItem(ado_data);
		//main_dlg->m_tree.SetItemImage(hRoot, node_image, node_image);//根节点添加图标

		ado_data.Format(_T("SELECT * FROM %d%d%d%d%d_area ORDER BY area_name ASC"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);//area_name按照升序排列(%d%d%d%d%d_area数据库表)
		EnterCriticalSection(&gy_ado.meshID_name_area_cs);//锁住数据库,暂时只能这里使用
		if (!gy_ado.meshID_name_area_ado.Select(ado_data))
		{
			gy_ado.meshID_name_area_ado.m_pRst = NULL;
			LeaveCriticalSection(&gy_ado.meshID_name_area_cs);//解锁数据库,其他线程或者函数可以使用
			AfxMessageBox(gy_ado.meshID_name_area_ado.GetLastError());
			return FALSE;
		}
		while (!gy_ado.meshID_name_area_ado.IsEOF())//当没有到达查询数据的最末尾(填充第一层子节点,为区域)
		{
			gy_ado.meshID_name_area_ado.GetFieldByIndex(1, ado_data);//获取area_name列对应的值(字符串)

			HTREEITEM node1 = main_dlg->m_tree.InsertItem(ado_data, hRoot);//在hRoot节点下添加子节点

			BYTE area_byte;
			gy_ado.meshID_name_area_ado.GetFieldByIndex(0, area_byte);//获取area列对应的值(字节)

			CString ado_data1;
			ado_data1.Format(_T("SELECT * FROM %d%d%d%d%d_area_group WHERE area=%d ORDER BY groupid_name ASC"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7], area_byte);//area_name按照升序排列(%d%d%d%d%d_area数据库表)
			EnterCriticalSection(&gy_ado.meshID_name_area_group_cs);//锁住数据库,暂时只能这里使用
			if (!gy_ado.meshID_name_area_group_ado.Select(ado_data1))
			{
				gy_ado.meshID_name_area_group_ado.m_pRst = NULL;
				LeaveCriticalSection(&gy_ado.meshID_name_area_group_cs);//解锁数据库,其他线程或者函数可以使用
				AfxMessageBox(gy_ado.meshID_name_area_group_ado.GetLastError());
				return FALSE;
			}
			area_light_count = 0;//初始化area_light_count值为0**********************************16
			area_light_online_count = 0;//初始化area_light_online_count值为0**************************************17
			while (!gy_ado.meshID_name_area_group_ado.IsEOF())//当没有到达查询数据的最末尾(填充第二层子节点,为groupid)
			{
				gy_ado.meshID_name_area_group_ado.GetFieldByIndex(2, ado_data);//获取groupid_name列对应的值(字符串)
				HTREEITEM node2 = main_dlg->m_tree.InsertItem(ado_data, node1);//在node1节点下添加子节点

				BYTE groupid_byte;
				gy_ado.meshID_name_area_group_ado.GetFieldByIndex(1, groupid_byte);//获取area列对应的值(字节)

				
				CString ado_data1;
				ado_data1.Format(_T("SELECT * FROM %d%d%d%d%d WHERE groupid=%d ORDER BY online_flag DESC"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7], groupid_byte);//online_flag按照降序排列(%d%d%d%d%d_area数据库表)
				EnterCriticalSection(&gy_ado.light_info_cs);//锁住数据库,暂时只能这里使用
				if (!gy_ado.light_info_ado.Select(ado_data1))
				{
					gy_ado.light_info_ado.m_pRst = NULL;
					LeaveCriticalSection(&gy_ado.light_info_cs);//解锁数据库,其他线程或者函数可以使用
					AfxMessageBox(gy_ado.light_info_ado.GetLastError());
					return FALSE;
				}
				group_light_count = 0;//初始化group_light_count值为0**********************************7
				group_light_online_count = 0;//初始化group_light_online_count值为0**************************************8
				while (!gy_ado.light_info_ado.IsEOF())//当没有到达查询数据的最末尾(最有一层叶子节点,为灯mac)
				{
					gy_ado.light_info_ado.GetFieldByIndex(0, ado_data);//获取mac_addr列对应的值(字符串)
					HTREEITEM node3 = main_dlg->m_tree.InsertItem(ado_data, node2);//在node2节点下添加子节点
					BYTE online_flag;
					gy_ado.light_info_ado.GetFieldByIndex(5, online_flag);//获取online_flag列对应的值(字节:判断设备是否在线)
					main_dlg->m_tree.SetItemImage(node3, online_flag, online_flag);//最底层的叶子节点添加图标
					all_light_count++;//每添加一个灯,记录灯总数量的all_light_count加1********************************************9
					group_light_count++;//*****************************************************11
					area_light_count++;//******************************************************19
					if (online_flag == 2)
					{
						all_light_online_count++;//如果添加的灯在线,则all_light_online_count加1*******************************************10
						group_light_online_count++;//***************************************************12
						area_light_online_count++;//***************************************************18
					}
					gy_ado.light_info_ado.MoveNext();//指向下一行
				}
				
				ado_data = main_dlg->m_tree.GetItemText(node2);//****************************************13
				ado_data.Format(ado_data + _T(" (%d/%d)"), group_light_online_count, group_light_count);//****************14
				main_dlg->m_tree.SetItemText(node2, ado_data);//*******************************15

				gy_ado.light_info_ado.m_pRst = NULL;
				LeaveCriticalSection(&gy_ado.light_info_cs);//解锁数据库,其他线程或者函数可以使用
				gy_ado.meshID_name_area_group_ado.MoveNext();//指向下一行
			}

			ado_data = main_dlg->m_tree.GetItemText(node1);//****************************************20
			ado_data.Format(ado_data + _T(" (%d/%d)"), area_light_online_count, area_light_count);//****************21
			main_dlg->m_tree.SetItemText(node1, ado_data);//*******************************22

			gy_ado.meshID_name_area_group_ado.m_pRst = NULL;
			LeaveCriticalSection(&gy_ado.meshID_name_area_group_cs);//解锁数据库,其他线程或者函数可以使用

            main_dlg->m_tree.Expand(node1, TVE_EXPAND);//展开区域节点*************************27
			gy_ado.meshID_name_area_ado.MoveNext();//指向下一行
		}

		ado_data = main_dlg->m_tree.GetItemText(hRoot);//****************************************23
		ado_data.Format(ado_data + _T(" (%d/%d)"), all_light_online_count, all_light_count);//****************24
		main_dlg->m_tree.SetItemText(hRoot, ado_data);//*******************************25

		gy_ado.meshID_name_area_ado.m_pRst = NULL;
		LeaveCriticalSection(&gy_ado.meshID_name_area_cs);//解锁数据库,其他线程或者函数可以使用
        main_dlg->m_tree.Expand(hRoot, TVE_EXPAND);//展开根节点*************************26
		gy_ado.meshID_name_ado.MoveNext();//指向下一行
	}
	gy_ado.meshID_name_ado.m_pRst = NULL;
	LeaveCriticalSection(&gy_ado.meshID_name_cs);//解锁数据库,其他线程或者函数可以使用
	return TRUE;
}

在Control_Light_Open_Close函数中将tree_data的在线离线数量信息去除,保留该字符串与数据库中相一致的信息

void CdonglepcDlg::Control_Light_Open_Close(HTREEITEM hTreeItem, BYTE Layer_Count, BOOL ItemState)//根据鼠标点击树形控件的第几层和复选框状态控制开关灯
{
	CString tree_data = m_tree.GetItemText(hTreeItem);
	int n = tree_data.Find(_T(" ("));
	if (n != -1)//如果当前节点中存在灯在线和离线数量时,将tree_data的在线离线数量信息去除,保留该字符串与数据库中相一致的信息
	{
		tree_data = tree_data.Left(n);
	}
    ......
}

效果图

右键选中树形控件的某个节点,弹出菜单

添加菜单资源

设置菜单

树形控件添加鼠标右击响应函数

 树形控件单击鼠标右键的响应函数中,根据点击节点的位置不同来动态添加不同的菜单

首先定义一个共有成员变量,记录下鼠标右键点击的是哪一个节点,可供其他函数调用

HTREEITEM hSave_menu;//树形控件右键点击菜单时记录鼠标此时点击的具体节点
void CdonglepcDlg::OnNMRClickTree1(NMHDR *pNMHDR, LRESULT *pResult)//在属性空间中单击了鼠标右键,执行该函数
{
	// TODO: 在此添加控件通知处理程序代码

	UINT uFlag;
	CPoint point, point_menu;
	::GetCursorPos(&point);
	point_menu = point;
	::ScreenToClient(m_tree.m_hWnd, &point);
	HTREEITEM   hItem = m_tree.HitTest(point, &uFlag);//HitTest函数能够得到与当前鼠标位置相关的项的句柄

	if ((hItem != NULL) && (TVHT_ONITEM & uFlag))     //如果点击的位置是在节点位置上面
	{
                //hSave_menu为定义的共有成员变量
                hSave_menu = hItem;//记录下此时鼠标右键点击的是哪一个节点,可供其他函数调用
		CMenu menu[4];
		menu[0].LoadMenu(IDR_MENU1);
		menu[1].LoadMenu(IDR_MENU2);
		menu[2].LoadMenu(IDR_MENU3);
		menu[3].LoadMenu(IDR_MENU4);

		BYTE n = GetCurrentLayer(hItem);//获取鼠标点击的当前位置为树形控件的第几层(根节点规定为第1层)
		//根据不同类型的节点弹出菜单
		CMenu *psubmenu = NULL;
		switch (n)
		{
		case 1://如果点击的是mesh网络根节点
		{
			psubmenu = menu[0].GetSubMenu(0);
			break;
		}
		case 2://如果点击的是区域节点
		{
			psubmenu = menu[1].GetSubMenu(0);
			break;
		}
		case 3://如果点击的是组节点
		{
			psubmenu = menu[2].GetSubMenu(0);
			break;
		}
		case 4://如果点击的是叶子节点(灯)
		{
			psubmenu = menu[3].GetSubMenu(0);
			break;
		}
		default:
			break;
		}
		psubmenu->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point_menu.x, point_menu.y, this, NULL);//显示菜单
	}

	*pResult = 0;
}

效果图:

添加菜单响应函数(新建区域)

添加事件处理程序

函数中添加新建区域的功能代码

void CdonglepcDlg::OnMesh32790()//区域菜单 中新建区域响应函数
{
	// TODO: 在此添加命令处理程序代码
	add_new_area();//新建区域
}

//在对话框类中添加函数add_new_area,作为新建区域具体实现方法
BOOL CdonglepcDlg::add_new_area(void)//新建区域具体实现方法
{
	CString str_data;
	//按照area升序查询区域数据库表********************************1
	str_data.Format(_T("SELECT * FROM %d%d%d%d%d_area ORDER BY area ASC"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);//area_name按照升序排列(%d%d%d%d%d_area数据库表)
	EnterCriticalSection(&gy_ado.meshID_name_area_cs);//锁住数据库,暂时只能这里使用
	if (!gy_ado.meshID_name_area_ado.Select(str_data))
	{
		gy_ado.meshID_name_area_ado.m_pRst = NULL;
		LeaveCriticalSection(&gy_ado.meshID_name_area_cs);//解锁数据库,其他线程或者函数可以使用
		AfxMessageBox(gy_ado.meshID_name_area_ado.GetLastError());
		return FALSE;
	}

	//依次轮询数据库表中的行,并且查看相邻两个区域之间的area值是否有跳跃性变化,如果有则在这两个值中间取一个值作为area值来新建区域,如果没有
	//跳跃性变化,则在末尾新建区域******************************2
	BYTE add_area = 0, area_byte;
	while (!gy_ado.meshID_name_area_ado.IsEOF())//当没有到达查询数据的最末尾
	{
		gy_ado.meshID_name_area_ado.GetFieldByIndex(0, area_byte);//获取area列对应的值(字节)
		if (add_area != area_byte)
		{
			break;
		}
		add_area++;
		gy_ado.meshID_name_area_ado.MoveNext();//指向下一行
	}

	//根据add_area的值新建区域,主要add_area值不能超过255,否则提示区域已达上限****************3
	if (add_area > 255)
	{
		gy_ado.meshID_name_area_ado.m_pRst = NULL;
		LeaveCriticalSection(&gy_ado.meshID_name_area_cs);//解锁数据库,其他线程或者函数可以使用
		AfxMessageBox(_T("区域数量已达上限,新建区域失败"));
		return FALSE;
	}
	str_data.Format(_T("INSERT INTO %d%d%d%d%d_area VALUES(%d,'区域%d')"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7], add_area, add_area);
	//EnterCriticalSection(&gy_ado.meshID_name_area_cs);//锁住数据库,暂时只能这里使用
	if (gy_ado.meshID_name_area_ado.ExecSQL(str_data) < 0)//在新表中添加区域相关数据
	{
		gy_ado.meshID_name_area_ado.m_pRst = NULL;
		LeaveCriticalSection(&gy_ado.meshID_name_area_cs);//解锁数据库,其他线程或者函数可以使用
		AfxMessageBox(_T("新建区域失败"));
		return FALSE;
	}
	//在树形控件中显示出新建的区域**************************4
	str_data.Format(_T("区域%d (0/0)"), add_area);
	m_tree.InsertItem(str_data, hSave_menu);//根节点下面添加区域节点
	gy_ado.meshID_name_area_ado.m_pRst = NULL;
	LeaveCriticalSection(&gy_ado.meshID_name_area_cs);//解锁数据库,其他线程或者函数可以使用
	UpdateData(FALSE);//屏幕刷新显示

	return TRUE;
}

效果图

添加菜单响应函数(删除区域(区域中不能有灯、即无效区域,否则不可以删除))

添加删除区域的响应函数(过程同上)

添加具体实现方法

void CdonglepcDlg::On32780()//删除无效区域
{
	// TODO: 在此添加命令处理程序代码
	delete_useless_area();//删除无效区域的具体方法
}

BOOL CdonglepcDlg::delete_useless_area(void)//删除无效区域的具体方法
{
	CString str_data;
	str_data = m_tree.GetItemText(hSave_menu);
	int m = str_data.GetLength();//获取该节点的文本的总长度
	int n = str_data.Find(_T("/"));//获取“/”在文本中的位置
	if ((m - n - 2) > 1)//如果灯的总数量的值大于等于两位数,则表示该区域中还有灯信息数据,该区域不能被删除*********1
	{
		AfxMessageBox(_T("该区域不是无效区域,删除失败!!"));
		return FALSE;
	}
	else if ((m - n - 2) == 1)
	{
		if (str_data.Mid(n+1, 1) != _T('0'))//如果灯的总数量的值不等于0,则表示该区域中还有灯信息数据,该区域不能被删除**********2
		{
			AfxMessageBox(_T("该区域不是无效区域,删除失败!!"));
			return FALSE;
		}
		else//如果灯的总数量的值等于0,则表示该区域中没有灯信息数据,该区域可以被删除*************3
		{
			n = str_data.Find(_T(" ("));//获取“/”在文本中的位置
			str_data = str_data.Left(n);//获取该区域节点去掉灯数量的完整字符串
			//根据area_name的字符串数据查找对应的area数值,后面删除数据库中该区域的group以此为依据
			str_data.Format(_T("SELECT * FROM %d%d%d%d%d_area WHERE area_name = '") + str_data + _T("'"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);//area_name按照升序排列(%d%d%d%d%d_area数据库表)
			EnterCriticalSection(&gy_ado.meshID_name_area_cs);//锁住数据库,暂时只能这里使用
			if (!gy_ado.meshID_name_area_ado.Select(str_data))
			{
				gy_ado.meshID_name_area_ado.m_pRst = NULL;
				LeaveCriticalSection(&gy_ado.meshID_name_area_cs);//解锁数据库,其他线程或者函数可以使用
				AfxMessageBox(gy_ado.meshID_name_area_ado.GetLastError());
				return FALSE;
			}
			BYTE area_byte;
			if (!gy_ado.meshID_name_area_ado.IsEOF())//当没有到达查询数据的最末尾
			{
				BOOL flag = gy_ado.meshID_name_area_ado.GetFieldByIndex(0, area_byte);//获取area列对应的值(字节)
				if (!flag)
				{
					gy_ado.meshID_name_area_ado.m_pRst = NULL;
					LeaveCriticalSection(&gy_ado.meshID_name_area_cs);//解锁数据库,其他线程或者函数可以使用
					AfxMessageBox(_T("未检测到该区域,删除失败!!"));
					return FALSE;
				}
			}
			gy_ado.meshID_name_area_ado.m_pRst = NULL;
			LeaveCriticalSection(&gy_ado.meshID_name_area_cs);//解锁数据库,其他线程或者函数可以使用

			//删除area数值对应的group
			str_data.Format(_T("DELETE  FROM %d%d%d%d%d_area_group WHERE area = %d"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7], area_byte);
			EnterCriticalSection(&gy_ado.meshID_name_area_group_cs);//锁住数据库,暂时只能这里使用
			if (gy_ado.meshID_name_area_group_ado.ExecSQL(str_data) < 0)//删除dongle所在mesh网络下对应的分组表
			{
				gy_ado.meshID_name_area_group_ado.m_pRst = NULL;
				LeaveCriticalSection(&gy_ado.meshID_name_area_group_cs);//解锁数据库,其他线程或者函数可以使用
				AfxMessageBox(gy_ado.meshID_name_area_group_ado.GetLastError());
				//AfxMessageBox(_T("删除区域失败!!"));
				return FALSE;
			}
			gy_ado.meshID_name_area_group_ado.m_pRst = NULL;
			LeaveCriticalSection(&gy_ado.meshID_name_area_group_cs);//解锁数据库,其他线程或者函数可以使用

			//删除该区域
			str_data.Format(_T("DELETE  FROM %d%d%d%d%d_area WHERE area = %d"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7],area_byte);
			EnterCriticalSection(&gy_ado.meshID_name_area_cs);//锁住数据库,暂时只能这里使用
			if (gy_ado.meshID_name_area_ado.ExecSQL(str_data) < 0)//删除dongle所在mesh网络下对应的区域表
			{
				gy_ado.meshID_name_area_ado.m_pRst = NULL;
				LeaveCriticalSection(&gy_ado.meshID_name_area_cs);//解锁数据库,其他线程或者函数可以使用
				AfxMessageBox(gy_ado.meshID_name_area_ado.GetLastError());
				return FALSE;
			}
			gy_ado.meshID_name_area_ado.m_pRst = NULL;
			LeaveCriticalSection(&gy_ado.meshID_name_area_cs);//解锁数据库,其他线程或者函数可以使用
		}

		//删除对应区域节点,刷新显示
		m_tree.DeleteItem(hSave_menu);//删除该节点
		UpdateData(FALSE);//刷新显示
	}
	return TRUE;
}

效果图

添加菜单响应函数(添加组)

区域菜单增加“添加组”一栏

void CdonglepcDlg::OnMenu()//区域菜单中新建组
{
	// TODO: 在此添加命令处理程序代码
	add_new_area_group();//在指定区域中新建组的具体实现方法
}

BOOL CdonglepcDlg::add_new_area_group(void)//在指定区域中新建组的具体实现方法
{
	CString str_data;
	//按照groupid升序查询区域数据库表********************************1
	str_data.Format(_T("SELECT * FROM %d%d%d%d%d_area_group ORDER BY groupid ASC"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);//按照groupid升序排列(%d%d%d%d%d_area_group数据库表)
	EnterCriticalSection(&gy_ado.meshID_name_area_group_cs);//锁住数据库,暂时只能这里使用
	if (!gy_ado.meshID_name_area_group_ado.Select(str_data))
	{
		gy_ado.meshID_name_area_group_ado.m_pRst = NULL;
		LeaveCriticalSection(&gy_ado.meshID_name_area_group_cs);//解锁数据库,其他线程或者函数可以使用
		AfxMessageBox(gy_ado.meshID_name_area_group_ado.GetLastError());
		return FALSE;
	}

	//依次轮询数据库表中的行,并且查看相邻两个区域之间的groupid值是否有跳跃性变化,如果有则在这两个值中间取一个值作为groupid值来新建组,如果没有
	//跳跃性变化,则在末尾新建组******************************2
	BYTE add_area_group = 0, area_groupid_byte;
	while (!gy_ado.meshID_name_area_group_ado.IsEOF())//当没有到达查询数据的最末尾
	{
		gy_ado.meshID_name_area_group_ado.GetFieldByIndex(1, area_groupid_byte);//获取groupid列对应的值(字节)
		if (add_area_group != area_groupid_byte)
		{
			break;
		}
		add_area_group++;
		gy_ado.meshID_name_area_group_ado.MoveNext();//指向下一行
	}

	//判断add_area_group是否超过255,超过则提示分组已达上限****************3
	if (add_area_group > 255)
	{
		gy_ado.meshID_name_area_group_ado.m_pRst = NULL;
		LeaveCriticalSection(&gy_ado.meshID_name_area_group_cs);//解锁数据库,其他线程或者函数可以使用
		AfxMessageBox(_T("组数量已达上限,新建组域失败"));
		return FALSE;
	}
	gy_ado.meshID_name_area_group_ado.m_pRst = NULL;
	LeaveCriticalSection(&gy_ado.meshID_name_area_group_cs);//解锁数据库,其他线程或者函数可以使用

	str_data = m_tree.GetItemText(hSave_menu);
	int n = str_data.Find(_T(" ("));//获取“ (”在文本中的位置
	str_data = str_data.Left(n);//获取该区域节点去掉灯数量的完整字符串
	//根据area_name的字符串数据查找对应的area数值,后面添加的group以此为父节点
	str_data.Format(_T("SELECT * FROM %d%d%d%d%d_area WHERE area_name = '") + str_data + _T("'"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);//area_name按照升序排列(%d%d%d%d%d_area数据库表)
	EnterCriticalSection(&gy_ado.meshID_name_area_cs);//锁住数据库,暂时只能这里使用
	if (!gy_ado.meshID_name_area_ado.Select(str_data))
	{
		gy_ado.meshID_name_area_ado.m_pRst = NULL;
		LeaveCriticalSection(&gy_ado.meshID_name_area_cs);//解锁数据库,其他线程或者函数可以使用
		AfxMessageBox(gy_ado.meshID_name_area_ado.GetLastError());
		return FALSE;
	}
	BYTE area_byte;
	if (!gy_ado.meshID_name_area_ado.IsEOF())//当没有到达查询数据的最末尾
	{
		BOOL flag = gy_ado.meshID_name_area_ado.GetFieldByIndex(0, area_byte);//获取area列对应的值(字节)
		if (!flag)
		{
			gy_ado.meshID_name_area_ado.m_pRst = NULL;
			LeaveCriticalSection(&gy_ado.meshID_name_area_cs);//解锁数据库,其他线程或者函数可以使用
			AfxMessageBox(_T("未检测到该区域,新建组失败!!"));
			return FALSE;
		}
	}
	gy_ado.meshID_name_area_ado.m_pRst = NULL;
	LeaveCriticalSection(&gy_ado.meshID_name_area_cs);//解锁数据库,其他线程或者函数可以使用

	str_data.Format(_T("INSERT INTO %d%d%d%d%d_area_group VALUES(%d,%d,'组%d')"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7], area_byte, add_area_group, add_area_group);
	EnterCriticalSection(&gy_ado.meshID_name_area_group_cs);//锁住数据库,暂时只能这里使用
	if (gy_ado.meshID_name_area_group_ado.ExecSQL(str_data) < 0)//在新表中添加区域相关数据
	{
		gy_ado.meshID_name_area_group_ado.m_pRst = NULL;
		LeaveCriticalSection(&gy_ado.meshID_name_area_group_cs);//解锁数据库,其他线程或者函数可以使用
		AfxMessageBox(_T("新建组域失败"));
		return FALSE;
	}
	//在树形控件中显示出新建的区域**************************4
	str_data.Format(_T("组%d (0/0)"), add_area_group);
	m_tree.InsertItem(str_data, hSave_menu);//区域节点下面添加组节点
	m_tree.Expand(hSave_menu, TVE_EXPAND);//展开区域
	gy_ado.meshID_name_area_group_ado.m_pRst = NULL;
	LeaveCriticalSection(&gy_ado.meshID_name_area_group_cs);//解锁数据库,其他线程或者函数可以使用
	UpdateData(FALSE);//屏幕刷新显示
	return TRUE;
}

添加菜单响应函数(删除无效组)

void CdonglepcDlg::On32784()//组菜单中点击“删除组”,可删除该组(该组必须为无效组)
{
	// TODO: 在此添加命令处理程序代码
	delete_useless_area_group();//删除某个区域中无效组的具体实现方法
}

BOOL CdonglepcDlg::delete_useless_area_group(void)//删除某个区域中无效组的具体实现方法
{
	CString str_data;
	str_data = m_tree.GetItemText(hSave_menu);
	int m = str_data.GetLength();//获取该节点的文本的总长度
	int n = str_data.Find(_T("/"));//获取“/”在文本中的位置
	if ((m - n - 2) > 1)//如果灯的总数量的值大于等于两位数,则表示该组中还有灯信息数据,该组不能被删除*********1
	{
		AfxMessageBox(_T("指定删除的组不是无效组,删除失败!!"));
		return FALSE;
	}
	else if ((m - n - 2) == 1)
	{
		if (str_data.Mid(n + 1, 1) != _T('0'))//如果灯的总数量的值不等于0,则表示该组中还有灯信息数据,该组不能被删除**********2
		{
			AfxMessageBox(_T("指定删除的组不是无效组,删除失败!!"));
			return FALSE;
		}
		else//如果灯的总数量的值等于0,则表示该组中没有灯信息数据,该组可以被删除*************3
		{
			n = str_data.Find(_T(" ("));//获取“/”在文本中的位置
			str_data = str_data.Left(n);//获取该组节点去掉灯数量的完整字符串

			//删除该组
			str_data.Format(_T("DELETE  FROM %d%d%d%d%d_area_group WHERE groupid_name = '")+ str_data+_T("'"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);
			EnterCriticalSection(&gy_ado.meshID_name_area_group_cs);//锁住数据库,暂时只能这里使用
			if (gy_ado.meshID_name_area_group_ado.ExecSQL(str_data) < 0)//删除dongle所在mesh网络下对应的区域表
			{
				gy_ado.meshID_name_area_group_ado.m_pRst = NULL;
				LeaveCriticalSection(&gy_ado.meshID_name_area_group_cs);//解锁数据库,其他线程或者函数可以使用
				AfxMessageBox(gy_ado.meshID_name_area_group_ado.GetLastError());
				return FALSE;
			}
			gy_ado.meshID_name_area_group_ado.m_pRst = NULL;
			LeaveCriticalSection(&gy_ado.meshID_name_area_group_cs);//解锁数据库,其他线程或者函数可以使用
		}

		//删除对应区域节点,刷新显示
		m_tree.DeleteItem(hSave_menu);//删除该节点
		UpdateData(FALSE);//刷新显示
	}
	return TRUE;
}

在用户界面线程添加一个对话框,用于显示程序相关进度

参考博客:http://www.chenkexiong.com/mfc-dialog-progress-bar-use-multi-thread.html

新建对话框,设置对话框的Style为Popup,Border 为None,去掉Title Bar属性,打开 ClassWizard为此对话框建立一个新类CSplashDlg, 基类为CDialog. 

给对话框添加类,取名CSplashDlg, 基类为CDialog

UI线程是由一个动态可创建的类来控制,该类是从CWinThread派生的,非常类似从CWinApp派生的一个应用程序类.打开ClassWizard建立一个由CWinThread派生的类—-CSplashThread

在SplashThread.h 中加入 #include "CSplashDlg.h",并添加一个protected型指针变量

CSplashDlg* m_pSplashDlg; //声明一个对话框指针

在UI线程的InitInstance()函数中调用刚才创建的对话框并显示

BOOL CSplashThread::InitInstance()
{
	// TODO:    在此执行任意逐线程初始化
	::AttachThreadInput(m_nThreadID, AfxGetApp()->m_nThreadID, TRUE);
	//:通常系统内的每个线程都有自己的输入队列。本函数允许线程和进程共享输入队列。连接了线程后,输入焦点、窗口激活、鼠标捕获、键盘状态以及输入队列状态都会进入共享状态 . (这个函数可以不用)
	m_pSplashDlg = new CSplashDlg;
	m_pSplashDlg->Create(IDD_DIALOG1);//创建对话框
	m_pSplashDlg->EnableWindow(FALSE);//禁用控件
	m_pSplashDlg->ShowWindow(SW_SHOW);//显示对话框
	return TRUE;
}

为CSplashThread类添加一个函数HideSplash(), 用来隐藏启动画面(即关闭对话框)

void CSplashThread::HideSplash()
{
	m_pSplashDlg->SendMessage(WM_CLOSE);
}

在ExitInstance()中释放资源

int CSplashThread::ExitInstance()//释放资源
{
	m_pSplashDlg->DestroyWindow();
	delete m_pSplashDlg;
	return CWinThread::ExitInstance();
}

在主对话框类头文件dongle_pcDlg.h中包含用户界面线程头文件

#include "CSplashThread.h"

并添加两个变量

public://设为pulic类型,是为了在其他类中能够访问
	CSplashThread* pSplashThread;
	CSplashDlg* m_pSplashDlg;

在主对话框OnInitDialog()中启动UI线程:

	pSplashThread = (CSplashThread*)AfxBeginThread(
		RUNTIME_CLASS(CSplashThread),
		THREAD_PRIORITY_NORMAL,
		0, CREATE_SUSPENDED);
	ASSERT(pSplashThread->IsKindOf(RUNTIME_CLASS(CSplashThread)));
	pSplashThread->ResumeThread();
	Sleep(1);

在CSplashDlg对话框中添加函数OnInitDialog

在OnInitDialog函数中添加任务栏隐藏功能的代码

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

	// TODO:  在此添加额外的初始化
	ModifyStyleEx(WS_EX_APPWINDOW, WS_EX_TOOLWINDOW, 1);//任务栏隐藏

	return TRUE;  // return TRUE unless you set the focus to a control
				  // 异常: OCX 属性页应返回 FALSE
}

导入bmp图片,作为CSplashDlg对话框的背景图片

因为该对话框类继承CDialog类,没有类似SetBackgroundImage加载背景图片的函数直接调用,所以可以按照以下方式加载图片

在对话框的.h文件中添加函数声明

afx_msg BOOL OnEraseBkgnd (CDC* pDC);

在对话框的.cpp文件中添加其消息映射宏

BEGIN_MESSAGE_MAP(CSplashDlg, CDialog)
	ON_WM_ERASEBKGND()//消息映射宏
END_MESSAGE_MAP()

在对话框的.cpp文件中实现该函数功能

BOOL CSplashDlg::OnEraseBkgnd(CDC* pDC) //为对话框添加背景图片
{
	CDialog::OnEraseBkgnd(pDC);
	CBitmap m_bitmap;
	m_bitmap.LoadBitmap(IDB_BITMAP8);

	if (!m_bitmap.m_hObject)
		return true;

	CRect rect;
	GetClientRect(&rect);
	CDC dc;
	dc.CreateCompatibleDC(pDC);
	CBitmap* pOldBitmap = dc.SelectObject(&m_bitmap);
	int bmw, bmh;
	BITMAP bmap;
	m_bitmap.GetBitmap(&bmap);
	bmw = bmap.bmWidth;
	bmh = bmap.bmHeight;
	int xo = 0, yo = 0;

	/*函数从源矩形中复制一个位图到目标矩形,必要时按目前目标设备设置的模式进行图像的拉伸或压缩。*/
	pDC->StretchBlt(xo, yo, rect.Width(), rect.Height(), &dc, 0, 0, bmw, bmh, SRCCOPY);

	dc.SelectObject(pOldBitmap);

	return true;
}

效果图

选择正确串口,程序发送ping灯指令,获取灯信息等待的时间较长,此时处于数据加载时间,应该提示用户正在加载相关数据

更改用户界面线程的InitInstance函数,将创建的状态显示对话框最初设置为不可见

BOOL CSplashThread::InitInstance()
{
	// TODO:    在此执行任意逐线程初始化
	::AttachThreadInput(m_nThreadID, AfxGetApp()->m_nThreadID, TRUE);
	//:通常系统内的每个线程都有自己的输入队列。本函数允许线程和进程共享输入队列。连接了线程后,输入焦点、窗口激活、鼠标捕获、键盘状态以及输入队列状态都会进入共享状态 . (这个函数可以不用)
	m_pSplashDlg = new CSplashDlg;
	m_pSplashDlg->Create(IDD_DIALOG1);//创建对话框
	m_pSplashDlg->EnableWindow(FALSE);//禁用控件
	//m_pSplashDlg->ShowWindow(SW_SHOW);//显示对话框
	m_pSplashDlg->ShowWindow(SW_HIDE);//隐藏对话框*****************************1
	return TRUE;
}

GY_File.cpp函数中添加显示状态对话框和隐藏状态对话框的全局函数

void gy_show_status_dlg(void)//显示状态对话框
{
	CSplashDlg *status_dlg = (CSplashDlg*)gy_current_status.status_dlg;//用于全局变量控制某一对话框的控件,详情见博客收藏
	status_dlg->ShowWindow(SW_SHOW);//隐藏对话框
	return;
}

void gy_show_hide_dlg(void)//隐藏状态对话框
{
	CSplashDlg *status_dlg = (CSplashDlg*)gy_current_status.status_dlg;//用于全局变量控制某一对话框的控件,详情见博客收藏
	status_dlg->ShowWindow(SW_HIDE);//隐藏对话框
	return;
}

在cmd_operation函数中:如果获得的串口状态信息是已经成功入网,则显示状态对话框

int cmd_operation(BYTE *str, int length)//将一个完整的数据命令(0xCC 0xCC结尾)解析并做相应的操作
{
    ......
        case 3:
            {
                 gy_comx.dongle_state.Format(_T("网络状态:成功加入mesh网络"));   				
                 gy_all_light_ping();//主动ping灯,获取灯的信息//77 01 01 1B 66				
                 gy_ado.write_accessdata_state = write_ping;//正在写什么数据到数据库中					
                 gy_show_status_dlg();//显示状态对话框*******************1   					
                 //重新开始计时,如果1秒钟之内没有收到其他ping回来的数据,则表示所有数据接收完成					
                 gy_ado.write_accessdata_timer_count = 0;//写数据库计时(write_ping、 write_groupid、 write_light_status是否超时,如果超时,则表示已经全部写入完成)					
                 break;        					
            }				
    ......
}

当所有数据接收完毕,再隐藏对话框

//添加1所在的位置,接收完所有数据之后隐藏状态对话框
void get_mesh_light_info_at_start(void)//在选择正确串口之后,获取灯的mac地址和虚拟地址、groupid、灯状态,并且以此刷新或者存储到数据库中,在timer_pThread_func函数中调用
{
	if (gy_ado.write_accessdata_timer_count != TIMER_NO_WORK)//如果计时已经开始
	{
		gy_ado.write_accessdata_timer_count++;//写数据库计时(write_ping、 write_groupid、 write_light_status是否超时,如果超时,则表示已经全部写入完成)
		if (gy_ado.write_accessdata_timer_count >= TIMER_COUNT_MAX)//如果获取信息超时
		{
			if (gy_ado.write_accessdata_state == write_ping)
			{
				gy_get_all_groupid();//获取网络中所有灯虚拟地址对应的groupid//77 01 01 25 66
				gy_ado.write_accessdata_state = write_groupid;//正在写什么数据到数据库中(write_idle、 write_ping、 write_groupid、 write_light_status)
				gy_ado.write_accessdata_timer_count = 0;//重新计时
			}
			else if (gy_ado.write_accessdata_state == write_groupid)
			{
				gy_get_all_status();//获取所有灯的状态信息//77 01 01 27 66
				gy_ado.write_accessdata_state = write_light_status;//正在写什么数据到数据库中(write_idle、 write_ping、 write_groupid、 write_light_status)
				gy_ado.write_accessdata_timer_count = 0;//重新计时
			}
			else if (gy_ado.write_accessdata_state == write_light_status)
			{
				gy_ado.write_accessdata_state = write_idle;//正在写什么数据到数据库中(write_idle、 write_ping、 write_groupid、 write_light_status)
				gy_ado.write_accessdata_timer_count = TIMER_NO_WORK;//所有信息获取完毕,关闭计时

				gy_add_ado_to_tree();//从数据库中读取数据,并添加到树形控件

				gy_show_hide_dlg();//隐藏状态对话框*********************1
			}
		}
	}
}

显示对话框显示的时候,要求主对话框变灰,并且不可以被操作

复制主对话框,作为主对话框的背景框,删掉该对话框中所有控件

给该对话框添加一个类

在背景对话框编辑OnInitDialog()函数,添加以下代码,作用:使背景对话框变成半透明,并且隐藏任务栏

BOOL MainDlgBackground::OnInitDialog()
{
	CDialogEx::OnInitDialog();

	// TODO:  在此添加额外的初始化
	ModifyStyleEx(WS_EX_APPWINDOW, WS_EX_TOOLWINDOW, 1);//任务栏隐藏

	//设置半透明对话框
	SetWindowLong(this->GetSafeHwnd(), GWL_EXSTYLE,
		GetWindowLong(this->GetSafeHwnd(), GWL_EXSTYLE) ^ 0x80000);
	HINSTANCE hInst = LoadLibrary(_T("User32.DLL"));  //加载库文件
	if (hInst)
	{
		typedef BOOL(WINAPI *MYFUNC)(HWND, COLORREF, BYTE, DWORD);
		MYFUNC func = NULL;	//函数指针
		//取得SetLayeredWindowAttributes函数指针 
		func = (MYFUNC)GetProcAddress(hInst, "SetLayeredWindowAttributes");
		//使用SetLayeredWindowAttributes函数设定透明度
		if (func)func(this->GetSafeHwnd(), RGB(0, 0, 0), 200, 0x2);
		FreeLibrary(hInst);
	}

	return TRUE;  // return TRUE unless you set the focus to a control
				  // 异常: OCX 属性页应返回 FALSE
}

在用户界面线程的头文件CSplashThread.h中添加头文件

#include "MainDlgBackground.h"

还要添加保护成员变量

MainDlgBackground* m_pBackgroundDlg;//声明一个对话框指针

在InitInstance函数中创建对话框(注意创建的先后顺序

BOOL CSplashThread::InitInstance()
{
	// TODO:    在此执行任意逐线程初始化
	::AttachThreadInput(m_nThreadID, AfxGetApp()->m_nThreadID, TRUE);
	//:通常系统内的每个线程都有自己的输入队列。本函数允许线程和进程共享输入队列。连接了线程后,输入焦点、窗口激活、鼠标捕获、键盘状态以及输入队列状态都会进入共享状态 . (这个函数可以不用)
	
	//*********************************新建背景对话框********************************************//
	m_pBackgroundDlg = new MainDlgBackground;
	m_pBackgroundDlg->Create(IDD_DONGLE_PC_DIALOG_TM);//创建对话框
	m_pBackgroundDlg->EnableWindow(FALSE);//禁用控件
	//m_pSplashDlg->ShowWindow(SW_SHOW);//显示对话框
	m_pBackgroundDlg->ShowWindow(SW_HIDE);//隐藏对话框*****************************1
	
	/*********************************新建状态对话框*******************************************/
	m_pSplashDlg = new CSplashDlg;
	m_pSplashDlg->Create(IDD_DIALOG1);//创建对话框
	m_pSplashDlg->EnableWindow(FALSE);//禁用控件
	//m_pSplashDlg->ShowWindow(SW_SHOW);//显示对话框
	m_pSplashDlg->ShowWindow(SW_HIDE);//隐藏对话框
	gy_current_status.status_dlg = m_pSplashDlg;//可加可不加,在状态对话框中已经添加过

	return TRUE;
}

添加全局指针变量,指向背景对话框("GY_File.h"文件中)

typedef struct {
	BYTE gy_current_status;//当前软件的运行状态,单独开辟一个线程,在该线程中使用该变量,初始化为gy_thread_idle
	CWinThread * gy_current_status_pThread;//接收串口数据的线程
	void * status_dlg;//状态对话框的指针,方便其他位置调用对话框(状态对话框)
	void * background_dlg;//状态对话框的指针,方便其他位置调用对话框(背景对话框)*********************1
}GY_CURRENT_STATUS;
extern GY_CURRENT_STATUS gy_current_status;//结构体变量在GY_File.cpp文件中定义,这里是外部声明

在背景对话框的cpp文件中添加头文件

#include "GY_File.h"

在BOOL MainDlgBackground::OnInitDialog()函数中添加赋值程序,使得新添加的变量指向背景对话框

gy_current_status.background_dlg = this;//指向状态对话框的指针

修改显示状态对话框和隐藏状态对话框函数,显示状态对话框时,也显示背景对话框,隐藏状态对话框也同时隐藏背景对话框

void gy_show_status_dlg(void)//显示状态对话框
{
	CSplashDlg *status_dlg = (CSplashDlg*)gy_current_status.status_dlg;//用于全局变量控制某一对话框的控件,详情见博客收藏
	status_dlg->ShowWindow(SW_SHOW);//显示对话框
	/**************************显示背景对话框*******************************/
	status_dlg = (CSplashDlg*)gy_current_status.background_dlg;//用于全局变量控制某一对话框的控件,详情见博客收藏
	status_dlg->ShowWindow(SW_SHOW);//显示对话框
	return;
}

void gy_show_hide_dlg(void)//隐藏状态对话框
{
	CSplashDlg *status_dlg = (CSplashDlg*)gy_current_status.status_dlg;//用于全局变量控制某一对话框的控件,详情见博客收藏
	status_dlg->ShowWindow(SW_HIDE);//隐藏对话框
	/**************************隐藏背景对话框*******************************/
	status_dlg = (CSplashDlg*)gy_current_status.background_dlg;//用于全局变量控制某一对话框的控件,详情见博客收藏
	status_dlg->ShowWindow(SW_HIDE);//隐藏对话框
	return;
}

效果图

此处有一个BUG,状态对话框和背景对话框不随主对话框的移动而移动,如下图所示:

解决方法:修改void gy_show_status_dlg(void),显示状态对话框和背景对话框时根据主对话框的位置来显示(注意显示的先后顺序)

void gy_show_status_dlg(void)//显示状态对话框
{
	CdonglepcDlg *main_dlg = (CdonglepcDlg*)gy_comx.main_dlg;//用于全局变量控制某一对话框的控件,详情见博客收藏
	CRect rcDlgs;
	main_dlg->GetClientRect(rcDlgs);  //得到对主话框相对于本身的位置及大小
	main_dlg->ClientToScreen(rcDlgs); //得到对主话框相对于屏幕的位置及大小

	/**************************显示背景对话框*******************************/
	CSplashDlg * status_dlg = (CSplashDlg*)gy_current_status.background_dlg;//用于全局变量控制某一对话框的控件,详情见博客收藏
	status_dlg->SetWindowPos(NULL, rcDlgs.left, rcDlgs.top, 0, 0, SWP_NOSIZE);//状态对话框随主对话框的移动而移动
	status_dlg->ShowWindow(SW_SHOW);//显示对话框

	status_dlg = (CSplashDlg*)gy_current_status.status_dlg;//用于全局变量控制某一对话框的控件,详情见博客收藏
	status_dlg->SetWindowPos(NULL, rcDlgs.left+400, rcDlgs.top+200,0,0, SWP_NOSIZE);//背景对话框随主对话框的移动而移动
	status_dlg->ShowWindow(SW_SHOW);//显示对话框
	return;
}

在背景对话框中动态显示正在加载数据的图标

准备相关图片(背景透明),添加到位图资源

添加的8张位图ID如下所示(连续的整数)

#define IDB_LOADIMAGE1                  169
#define IDB_LOADIMAGE2                  170
#define IDB_LOADIMAGE3                  171
#define IDB_LOADIMAGE4                  172
#define IDB_LOADIMAGE5                  173
#define IDB_LOADIMAGE6                  174
#define IDB_LOADIMAGE7                  175
#define IDB_LOADIMAGE8                  176

背景对话框类中添加公有成员变量m_BmpID,记录需要刷新的位图ID

public:
	int m_BmpID;//记录需要刷新的位图ID

在BOOL MainDlgBackground::OnInitDialog()函数中初始化m_BmpID

m_BmpID = IDB_LOADIMAGE1;//初始化为第一个位图的ID

添加picture control控件,并且ID重命名(如果ID不重命名,控件就不能添加变量),然后给控件添加变量

在背景对话框中添加时钟函数

 

  在时钟函数void MainDlgBackground::OnTimer(UINT_PTR nIDEvent)中添加如下代码来实现位图切换

设置位图背景透明参考:https://blog.csdn.net/xiashengfu/article/details/8678125

//#define IDB_LOADIMAGE1                  169
//#define IDB_LOADIMAGE2                  170
//#define IDB_LOADIMAGE3                  171
//#define IDB_LOADIMAGE4                  172
//#define IDB_LOADIMAGE5                  173
//#define IDB_LOADIMAGE6                  174
//#define IDB_LOADIMAGE7                  175
//#define IDB_LOADIMAGE8                  176
void MainDlgBackground::OnTimer(UINT_PTR nIDEvent)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值
	if (nIDEvent == 2)
	{
		m_BmpID++;
		if (m_BmpID > IDB_LOADIMAGE8)
		{
			m_BmpID = IDB_LOADIMAGE1;
		}

		/*OnPaint();*/
		ShowPicture(m_BmpID);//显示新的位图图片,自定义添加的公有成员函数,详情见下方代码

	}
	CDialogEx::OnTimer(nIDEvent);
}

void MainDlgBackground::ShowPicture(UINT pictureResource)//显示新的位图图片,自定义添加的公有成员函数
{
	CBitmap bitmap;//创建CBitmap对象用于存放我们需要加载的图片
	HBITMAP hbmp;//用于记录图片加载后的句柄
	bitmap.LoadBitmap(pictureResource);//加载图片资源
	hbmp = (HBITMAP)bitmap.GetSafeHandle();//获取图片句柄
	this->mPictureViewer.SetBitmap(hbmp);//为空间设置图片

	 //为了让图片自动缩放以适应空间的尺寸 需要获取图片尺寸信息
	BITMAP bmpInfo;//存储图片信息用于获取图片的宽度和高度
	bitmap.GetBitmap(&bmpInfo);
	int bmpWith = bmpInfo.bmWidth;//图片宽度
	int bmpHeight = bmpInfo.bmHeight;//图片高度


	CRect rect;//记录Picture Control控件的尺寸
	this->mPictureViewer.GetClientRect(&rect);
	int nx = rect.left + (rect.Width() - 150) / 2;//计算图片插入位置x
	int ny = rect.top + (rect.Height() - 150) / 2;//计算图片插入位置y

	CDC *pDC = this->mPictureViewer.GetDC();//获取DC
	pDC->SetStretchBltMode(COLORONCOLOR);//设置图片模式

	CDC dcMemory;
	dcMemory.CreateCompatibleDC(pDC);
	CBitmap *pOldBitmap = dcMemory.SelectObject(&bitmap);

	//pDC->StretchBlt(0, 0, rect.Width(), rect.Height(), &dcMemory, 0, 0, 150, bmpHeight, SRCCOPY);
	TransparentBlt(pDC->m_hDC, 0, 0, rect.Width(), rect.Height(),
		pDC->m_hDC, 0, 0, 150, 150, RGB(236, 233, 216));//这个位置设置透明背景
	ReleaseDC(pDC);//释放DC 注意获取后必须释放

}

在显示状态对话框函数gy_show_status_dlg中添加启用时钟的代码,屏蔽掉状态对话框的相关代码

	/**************************显示背景对话框*******************************/
	MainDlgBackground * background_dlg = (MainDlgBackground*)gy_current_status.background_dlg;//用于全局变量控制某一对话框的控件,详情见博客收藏
	background_dlg->SetTimer(2, 300, NULL);//设置时钟,时间设置为2,每个300毫秒执行一次时钟函数OnTimer**************1
	background_dlg->SetWindowPos(NULL, rcDlgs.left, rcDlgs.top, 0, 0, SWP_NOSIZE);//状态对话框随主对话框的移动而移动
	background_dlg->ShowWindow(SW_SHOW);//显示对话框

在隐藏状态对话框函数gy_show_hide_dlg中添加释放时钟的代码,屏蔽掉状态对话框的相关代码

	/**************************隐藏背景对话框*******************************/
	MainDlgBackground * background_dlg = (MainDlgBackground*)gy_current_status.background_dlg;//用于全局变量控制某一对话框的控件,详情见博客收藏
	background_dlg->ShowWindow(SW_HIDE);//隐藏对话框
	background_dlg->KillTimer(2);//释放时钟

效果图

在背景对话框中添加动态显示文字

背景对话框添加静态文本控件

设置属性。注意:ID必须重命名,否则无法添加控件变量

在显示状态对话框函数gy_show_status_dlg中添代码,让文本框显示正在重新获取MAC地址和虚拟地址

/**************************显示背景对话框*******************************/
MainDlgBackground * background_dlg = (MainDlgBackground*)gy_current_status.background_dlg;//用于全局变量控制某一对话框的控件,详情见博客收藏
background_dlg->SetTimer(2, 300, NULL);//设置时钟,时间设置为2,每个300毫秒执行一次时钟函数OnTimer
background_dlg->GetDlgItem(IDC_STATIC2)->SetWindowText(_T("正在重新获取灯的MAC地址和虚拟地址..."));//向静态文本框中添加文本内容************1
background_dlg->SetWindowPos(NULL, rcDlgs.left, rcDlgs.top, 0, 0, SWP_NOSIZE);//状态对话框随主对话框的移动而移动
background_dlg->ShowWindow(SW_SHOW);//显示对话框

在get_mesh_light_info_at_start函数中根据dongle此时获取灯的信息状态而不断刷新静态文本

//添加1、2、3、4所在的位置,其他位置不变
void get_mesh_light_info_at_start(void)//在选择正确串口之后,获取灯的mac地址和虚拟地址、groupid、灯状态,并且以此刷新或者存储到数据库中,在timer_pThread_func函数中调用
{
	if (gy_ado.write_accessdata_timer_count != TIMER_NO_WORK)//如果计时已经开始
	{
		gy_ado.write_accessdata_timer_count++;//写数据库计时(write_ping、 write_groupid、 write_light_status是否超时,如果超时,则表示已经全部写入完成)
		if (gy_ado.write_accessdata_timer_count >= TIMER_COUNT_MAX)//如果获取信息超时
		{
			if (gy_ado.write_accessdata_state == write_ping)
			{
				gy_get_all_groupid();//获取网络中所有灯虚拟地址对应的groupid//77 01 01 25 66
				gy_ado.write_accessdata_state = write_groupid;//正在写什么数据到数据库中(write_idle、 write_ping、 write_groupid、 write_light_status)
				gy_ado.write_accessdata_timer_count = 0;//重新计时
				MainDlgBackground * background_dlg = (MainDlgBackground*)gy_current_status.background_dlg;//用于全局变量控制某一对话框的控件,详情见博客收藏
				background_dlg->GetDlgItem(IDC_STATIC2)->SetWindowText(_T("正在重新获取灯的组(groupid)信息..."));//向静态文本框中添加文本内容**********1
			}
			else if (gy_ado.write_accessdata_state == write_groupid)
			{
				gy_get_all_status();//获取所有灯的状态信息//77 01 01 27 66
				gy_ado.write_accessdata_state = write_light_status;//正在写什么数据到数据库中(write_idle、 write_ping、 write_groupid、 write_light_status)
				gy_ado.write_accessdata_timer_count = 0;//重新计时
				MainDlgBackground * background_dlg = (MainDlgBackground*)gy_current_status.background_dlg;//用于全局变量控制某一对话框的控件,详情见博客收藏
				background_dlg->GetDlgItem(IDC_STATIC2)->SetWindowText(_T("正在重新获取灯的状态(x、y)信息..."));//向静态文本框中添加文本内容*********2
			}
			else if (gy_ado.write_accessdata_state == write_light_status)
			{
				gy_ado.write_accessdata_state = write_idle;//正在写什么数据到数据库中(write_idle、 write_ping、 write_groupid、 write_light_status)
				gy_ado.write_accessdata_timer_count = TIMER_NO_WORK;//所有信息获取完毕,关闭计时
				MainDlgBackground * background_dlg = (MainDlgBackground*)gy_current_status.background_dlg;//用于全局变量控制某一对话框的控件,详情见博客收藏
				background_dlg->GetDlgItem(IDC_STATIC2)->SetWindowText(_T("全部信息获取完成..."));//向静态文本框中添加文本内容**********3

				gy_add_ado_to_tree();//从数据库中读取数据,并添加到树形控件
				Sleep(1000);//********4
				gy_show_hide_dlg();//隐藏状态对话框*********************1
			}
		}
	}
}

效果图

将某个灯移动到另外一个分组中(以下步骤可实现相关功能,但是用户体验不行,笔者决定放弃该方法,采用另外一种方法)

思路:根据灯的mac地址,查询数据库表中的标志位online_flag,如果标志位显示灯不在线,则弹出移动失败对话框;如果灯在线,则发送灯分组指令,将灯移动到指定组,1秒钟之后获取主动获取灯分组信息,1秒钟之内如果收到灯返回的分组信息,并且与我们设定的组信息一致,则提示移动分组成功,否则显示移动分组失败。

主对话框类中添加公有成员函数和变量

BOOL move_a_light_to_another_group(HTREEITEM hSave_menu, BYTE groupid, BYTE flag);//移动指定灯到另外一个区域的组(单灯操作)
BYTE move_group_flag;//将灯移动到其他组的标志位,初始化为0,单灯移动时置位为1,批量操作时置位为2
BYTE gy_vir_addr[4];//临时记录移动分组时单灯的虚拟地址,初始化为0
BYTE gy_groupid;//记录当前灯需要分到哪一个组
HTREEITEM gy_tree_node;//记录将要移动分组的灯在树形控件中的位置
BOOL CdonglepcDlg::move_a_light_to_another_group(HTREEITEM hSave_menu,BYTE groupid,BYTE flag)//移动指定灯到另外一个区域的组(单灯操作)
{
	CString str_data,gy_mac_str;
	HTREEITEM tree_menu = hSave_menu;
	gy_mac_str = m_tree.GetItemText(tree_menu);
	str_data.Format(_T("SELECT * FROM %d%d%d%d%d WHERE mac_addr='")+ gy_mac_str + _T("'"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);
	EnterCriticalSection(&gy_ado.light_info_cs);//锁住数据库,暂时只能这里使用
	if (!gy_ado.light_info_ado.Select(str_data))
	{
		gy_ado.light_info_ado.m_pRst = NULL;
		LeaveCriticalSection(&gy_ado.light_info_cs);//解锁数据库,其他线程或者函数可以使用
		AfxMessageBox(gy_ado.light_info_ado.GetLastError());
		return FALSE;
	}
	if (!gy_ado.light_info_ado.IsEOF())//当没有到达查询数据的最末尾
	{
		BYTE onlie_flag;
		gy_ado.light_info_ado.GetFieldByIndex(5, onlie_flag);//获取onlie_flag列对应的值(字节,判断灯是否在线)
		if (onlie_flag != 2)//如果灯不在线
		{
			gy_ado.light_info_ado.m_pRst = NULL;
			LeaveCriticalSection(&gy_ado.light_info_cs);//解锁数据库,其他线程或者函数可以使用
			gy_mac_str.Format(gy_mac_str+_T("不在线,移动分组失败!!!"));
			AfxMessageBox(gy_mac_str);
			return FALSE;
		}
		CString vir_addr;
		gy_ado.light_info_ado.GetFieldByIndex(1, vir_addr);//获取vir_addr列对应的值(字符串)
		gy_vir_addr[0] = gy_str_2_to_hex(vir_addr.Right(2));//临时存储需要修改的灯的虚拟地址
		gy_vir_addr[1] = gy_str_2_to_hex(vir_addr.Mid(6, 2));//*********
		gy_vir_addr[2] = gy_str_2_to_hex(vir_addr.Mid(3, 2));//*********
		gy_vir_addr[3] = gy_str_2_to_hex(vir_addr.Left(2));//***********
		gy_add_single_group(gy_vir_addr[0], gy_vir_addr[1], gy_vir_addr[2], gy_vir_addr[3], groupid);//将一个灯加入分组//77 01 06 1D vir_1 vir_2 vir_3 vir_4 groupid 66
		move_group_flag = flag;//将灯移动到其他组的标志位,初始化为0,单灯移动时置位为1,批量操作时置位为2,这里由菜单执行程序传入的参数赋值
	}
	gy_ado.light_info_ado.m_pRst = NULL;
	LeaveCriticalSection(&gy_ado.light_info_cs);//解锁数据库,其他线程或者函数可以使用
	return TRUE;
}

 新建移动灯到其他组的对话框,并添加类,给下拉列表控件添加变量

点击灯菜单中“移动到其他组”时,创建该对话框

void CdonglepcDlg::On32787()
{
	// TODO: 在此添加命令处理程序代码
	Min_1_Dlg dl;
	INT_PTR nResponse1 = dl.DoModal();
}

新建的对话框初始化中将区域对应的下拉列表添加该网络中现有的所有区域

BOOL Min_1_Dlg::OnInitDialog()
{
	CDialogEx::OnInitDialog();

	// TODO:  在此添加额外的初始化
    m_comb_area.ResetContent();//清空组合框的所有数据
	CString ado_data;
	ado_data.Format(_T("SELECT * FROM %d%d%d%d%d_area ORDER BY area_name ASC"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);//area_name按照升序排列(%d%d%d%d%d_area数据库表)
	EnterCriticalSection(&gy_ado.meshID_name_area_cs);//锁住数据库,暂时只能这里使用
	if (!gy_ado.meshID_name_area_ado.Select(ado_data))
	{
		gy_ado.meshID_name_area_ado.m_pRst = NULL;
		LeaveCriticalSection(&gy_ado.meshID_name_area_cs);//解锁数据库,其他线程或者函数可以使用
		AfxMessageBox(gy_ado.meshID_name_area_ado.GetLastError());
		return FALSE;
	}
	while (!gy_ado.meshID_name_area_ado.IsEOF())//当没有到达查询数据的最末尾(填充第一层子节点,为区域)
	{
		gy_ado.meshID_name_area_ado.GetFieldByIndex(1, ado_data);//获取area_name列对应的值(字符串)
		m_comb_area.AddString(ado_data);
		gy_ado.meshID_name_area_ado.MoveNext();//指向下一行
	}
	gy_ado.meshID_name_area_ado.m_pRst = NULL;
	LeaveCriticalSection(&gy_ado.meshID_name_area_cs);//解锁数据库,其他线程或者函数可以使用
	return TRUE;  // return TRUE unless you set the focus to a control
				  // 异常: OCX 属性页应返回 FALSE
}

选中区域下拉列表中的某一项时,对应区域里的组也同时添加到组下拉列表控件中

void Min_1_Dlg::OnCbnSelchangeCombo1()//当选中的内容已经更改
{
	// TODO: 在此添加控件通知处理程序代码
	UpdateData(TRUE);//将控件中输入的值更新到变量中
    m_comb_group.ResetContent();//清空组合框的所有数据
	/***************************************获取area_name对应的area值(BYTE)************************************/
	CString ado_data;
	m_comb_area.GetWindowText(ado_data);
	ado_data.Format(_T("SELECT * FROM %d%d%d%d%d_area WHERE area_name = '") + ado_data +_T("'"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);//area_name按照升序排列(%d%d%d%d%d_area数据库表)
	EnterCriticalSection(&gy_ado.meshID_name_area_cs);//锁住数据库,暂时只能这里使用
	if (!gy_ado.meshID_name_area_ado.Select(ado_data))
	{
		gy_ado.meshID_name_area_ado.m_pRst = NULL;
		LeaveCriticalSection(&gy_ado.meshID_name_area_cs);//解锁数据库,其他线程或者函数可以使用
		AfxMessageBox(gy_ado.meshID_name_area_ado.GetLastError());
		return ;
	}
	BYTE area_byte;
	if(!gy_ado.meshID_name_area_ado.IsEOF())//当没有到达查询数据的最末尾(填充第一层子节点,为区域)
	{
		gy_ado.meshID_name_area_ado.GetFieldByIndex(0, area_byte);//获取area列对应的值(BYTE)
	}
	else
	{
		return;
	}
	gy_ado.meshID_name_area_ado.m_pRst = NULL;
	LeaveCriticalSection(&gy_ado.meshID_name_area_cs);//解锁数据库,其他线程或者函数可以使用

	ado_data.Format(_T("SELECT * FROM %d%d%d%d%d_area_group WHERE area=%d ORDER BY groupid_name ASC"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7], area_byte);//area_name按照升序排列(%d%d%d%d%d_area数据库表)
	EnterCriticalSection(&gy_ado.meshID_name_area_group_cs);//锁住数据库,暂时只能这里使用
	if (!gy_ado.meshID_name_area_group_ado.Select(ado_data))
	{
		gy_ado.meshID_name_area_group_ado.m_pRst = NULL;
		LeaveCriticalSection(&gy_ado.meshID_name_area_group_cs);//解锁数据库,其他线程或者函数可以使用
		AfxMessageBox(gy_ado.meshID_name_area_group_ado.GetLastError());
		return ;
	}
	while (!gy_ado.meshID_name_area_group_ado.IsEOF())//当没有到达查询数据的最末尾(填充第二层子节点,为groupid)
	{
		gy_ado.meshID_name_area_group_ado.GetFieldByIndex(2, ado_data);//获取groupid_name列对应的值(字符串)
		m_comb_group.AddString(ado_data);
		gy_ado.meshID_name_area_group_ado.MoveNext();//指向下一行
	}
	gy_ado.meshID_name_area_group_ado.m_pRst = NULL;
	LeaveCriticalSection(&gy_ado.meshID_name_area_group_cs);//解锁数据库,其他线程或者函数可以使用
}

选择组下拉列表中,则记录下该组名对应的组ID值

void Min_1_Dlg::OnCbnSelchangeCombo2()
{
	// TODO: 在此添加控件通知处理程序代码
	CdonglepcDlg *main_dlg = (CdonglepcDlg*)gy_comx.main_dlg;//用于全局变量控制某一对话框的控件,详情见博客收藏
	UpdateData(TRUE);//将控件中输入的值更新到变量中
	CString ado_data;
	m_comb_group.GetWindowText(ado_data);
	ado_data.Format(_T("SELECT * FROM %d%d%d%d%d_area_group WHERE groupid_name='")+ ado_data+_T("'"), *(DWORD*)&gy_ado.Dongle_Mesh[0], gy_ado.Dongle_Mesh[4], gy_ado.Dongle_Mesh[5], gy_ado.Dongle_Mesh[6], gy_ado.Dongle_Mesh[7]);
	if (!gy_ado.meshID_name_area_group_ado.Select(ado_data))
	{
		gy_ado.meshID_name_area_group_ado.m_pRst = NULL;
		LeaveCriticalSection(&gy_ado.meshID_name_area_group_cs);//解锁数据库,其他线程或者函数可以使用
		AfxMessageBox(gy_ado.meshID_name_area_group_ado.GetLastError());
		return;
	}
	if(!gy_ado.meshID_name_area_group_ado.IsEOF())//当没有到达查询数据的最末尾(填充第二层子节点,为groupid)
	{

		gy_ado.meshID_name_area_group_ado.GetFieldByIndex(1, main_dlg->gy_groupid);//获取groupid列对应的值(BYTE) 
	}
	gy_ado.meshID_name_area_group_ado.m_pRst = NULL;
	LeaveCriticalSection(&gy_ado.meshID_name_area_group_cs);//解锁数据库,其他线程或者函数可以使用
}

点击确认按钮,发送移动分组指令

void Min_1_Dlg::OnBnClickedOk()
{
	// TODO: 在此添加控件通知处理程序代码
	/*CDialogEx::OnOK();*/
	CdonglepcDlg *main_dlg = (CdonglepcDlg*)gy_comx.main_dlg;//用于全局变量控制某一对话框的控件,详情见博客收藏
	main_dlg->move_a_light_to_another_group(main_dlg->gy_tree_node, main_dlg->gy_groupid, main_dlg->move_group_flag);
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章