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);
}

 

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