該博客涉及的完整工程託管在https://github.com/Wsine/UpperMonitor,以爲好請給個Star (/▽\=)mysql
理論支持所有Win32運行環境git
在消息響應函數OnInitDialog()中完成整個框架的內容設置,包括插入Tab選項卡標籤,關聯對話框,調整大小和設置默認選項卡github
BOOL CUpperMonitorDlg::OnInitDialog() { CDialogEx::OnInitDialog(); // TODO: 在此添加額外的初始化代碼 // 1. 插入Tab選項卡標籤 TCITEM tcItemDebugger; tcItemDebugger.mask = TCIF_TEXT; tcItemDebugger.pszText = _T("調試助手"); m_MainMenu.InsertItem(0, &tcItemDebugger); TCITEM tcItemAppdev; tcItemAppdev.mask = TCIF_TEXT; tcItemAppdev.pszText = _T("應用開發"); m_MainMenu.InsertItem(1, &tcItemAppdev); // 2. 關聯對話框,將TAB控件設爲選項卡對應對話框的父窗口 m_MenuDebugger.Create(IDD_DEBUGGER, GetDlgItem(IDC_TAB)); m_MenuAppdev.Create(IDD_APPDEV, GetDlgItem(IDC_TAB)); // 3. 獲取TAB控件客戶區大小,用於調整選項卡對話框在父窗口中的位置 CRect rect; m_MainMenu.GetClientRect(&rect); rect.top += 22; rect.right -= 3; rect.bottom -= 2; rect.left += 1; // 4. 設置子對話框尺寸並移動到指定位置 m_MenuDebugger.MoveWindow(&rect); m_MenuAppdev.MoveWindow(&rect); // 5. 設置默認選項卡,對選項卡對話框進行隱藏和顯示 m_MenuDebugger.ShowWindow(SW_SHOWNORMAL); m_MenuAppdev.ShowWindow(SW_HIDE); m_MainMenu.SetCurSel(0); return TRUE; // 除非將焦點設置到控件,不然返回 TRUE }
在消息響應函數OnSelchangeTab()中完成選項卡切換,其實內容都一直存在,只是把非該選項卡的內容隱藏了,把該選項卡的內容顯示出來,僅此而已web
void CUpperMonitorDlg::OnSelchangeTab(NMHDR *pNMHDR, LRESULT *pResult) { *pResult = 0; // 獲取當前點擊選項卡標籤下標 int cursel = m_MainMenu.GetCurSel(); // 根據下標將相應的對話框顯示,其他隱藏 switch(cursel) { case 0: m_MenuDebugger.ShowWindow(SW_SHOWNORMAL); m_MenuAppdev.ShowWindow(SW_HIDE); break; case 1: m_MenuDebugger.ShowWindow(SW_HIDE); m_MenuAppdev.ShowWindow(SW_SHOWNORMAL); break; default: break; } }
首先須要在工程中include相應的文件,就是已經封裝好的ZM124U.lib和ZM124U.hsql
#pragma comment(lib, "./libs/ZM124U.lib") #include "./libs/ZM124U.h"
而後就能夠看成已經實現的函數同樣,直接調用便可。庫函數的傳入參數和返回值所有均可以在上面的參考文件中找到,返回值通常使用IFD_OK足夠了。下面以打開設備爲例,展現一下如何使用數據庫
void CDebugger::OnBnClickedBtnopendevice() { if(IDD_PowerOn() == IFD_OK) { // 更新狀態欄,成功 isDeviceOpen = true; ((CEdit*)GetDlgItem(IDC_EDITSTATUS))->SetWindowTextW(_T("開啓設備成功")); } else { // 更新狀態欄,失敗 isDeviceOpen = false; ((CEdit*)GetDlgItem(IDC_EDITSTATUS))->SetWindowTextW(_T("開啓設備失敗")); } }
unsigned char轉CString編程
這部分和printf函數類似,就是使用CString.Format()函數把它轉換爲相應的內容,而後拼接到最終的CString對象中。大多數類型轉CString均可以使用這種方法,說明CString封裝得好。CString.Format()函數的第一個參數的寫法相似於printf的輸出時的寫法。詳看MSDN上面的說明。api
CString uid, temp; unsigned char buff[1024]; uid.Empty(); for(int i = 0; i < buff_len; i++) { // 將得到的UID數據(1 byte)轉爲16進制 temp.Format(_T("%02x"), buff[i]); uid += temp; }
CString轉int / long併發
前提CString的內容是數字。使用函數_ttoi(CString) | _ttol(CString)
便可,如需轉unsigned char
類型,再使用強制轉換類型便可框架
CString mecNum; int mecNumInt = _ttoi(mecNum); long mecNumLong = _ttol(mecLong); unsigned char point = (unsigned char)_ttoi(mecNum) + 1;
CString轉char*
說實話沒有找到好的方法,可是隻要char*
空間足夠,可使用=
直接賦值,內置轉換,我以爲必定有這個API的,可是我沒找到而已
void CUtils::CString2CharStar(const CString& s, char* ch, int len) { int i; for (i = 0; i < len; i++) { ch[i] = s[i]; } ch[i] = '\0'; return; }
十六進制CString轉UnsignedChar*
這裏要求傳入參數必須是偶數個字符,同時操做兩個字符的轉換方法
void CUtils::HexCString2UnsignedCharStar(const CString& hexStr, unsigned char* asc, int* asc_len) { *asc_len = 0; int len = hexStr.GetLength(); char temp[200]; char tmp[3] = { 0 }; char* Hex; unsigned char* p; CString2CharStar(hexStr, temp, len); Hex = temp; p = asc; while (*Hex != '\0') { tmp[0] = *Hex; Hex++; tmp[1] = *Hex; Hex++; tmp[2] = '\0'; *p = (unsigned char)strtol(tmp, NULL, 16); p++; (*asc_len)++; } *p = '\0'; return; }
設置控件顏色
在每一個控件開始繪製內容在窗口的時候,都會向父窗口發送WM_CTLCOLOR消息
,所以咱們只須要重載相應的父窗口消息響應函數OnCtlColor()便可,程序會運行完響應函數的代碼再開始繪製內容。所以在響應函數中判斷並改變刷子顏色便可。
#define RED RGB(255, 0, 0) #define BLUE RGB(0, 0, 255) #define BLACK RGB(0, 0, 0) HBRUSH CDebugger::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) { HBRUSH hbr = CDialogEx::OnCtlColor(pDC, pWnd, nCtlColor); // TODO: 在此更改 DC 的任何特性 switch(pWnd->GetDlgCtrlID()) { case IDC_EDITSTATUS: if(this->isDeviceOpen) pDC->SetTextColor(BLUE); else pDC->SetTextColor(RED); break; case IDC_EDITIOSTATUS: if(this->canIO) pDC->SetTextColor(BLUE); else pDC->SetTextColor(RED); break; case IDC_EDITVERSIONINFO: case IDC_EDITCARDUID: pDC->SelectObject(&m_font); break; default: pDC->SetTextColor(BLACK); break; } return hbr; }
設置控件字體
在窗口的類中聲明CFont
類型的字體,在窗口的構造函數中爲字體其賦值,在數據交換的消息響應函數中把字體綁定到響應的控件中去。注意,Create出來的GDI對象都是須要手動刪除的,不然會形成GDI泄漏。【據說把Windows系統的2000多個GDI泄漏完了,系統就繪製不了任何東西了,看來就像電腦卡了哈哈o(^▽^)o不知道新版VS有沒有修復,我沒有試過,有人試了記得告訴我...
/* Debugger.h */ class CDebugger : CDialogEx { private: CFont m_font; }; /* Debugger.cpp */ CDebugger::CDebugger(CWnd* pParent) : CDialogEx(CDebugger::IDD, pParent) { // 建立字體 m_font.CreatePointFont(93, _T("楷體")); } CDebugger::~CDebugger() { DeleteObject(m_font); } void CDebugger::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); GetDlgItem(IDC_OpenDevice)->SetFont(&m_font); GetDlgItem(IDC_GETCARDINFO)->SetFont(&m_font); }
這裏使用的是ODBC(Open Database Connection)技術和OLE DB(對象連接與嵌入數據庫)技術。ODBC API能夠與任何具備ODBC驅動程序的關係數據庫進行通訊;OLE DB擴展了ODBC,提供數據庫編程的COM接口,提供可用於關係型和非關係型數據源的接口[也就是能夠操做電子表格、文本文件等]。
這部分的理論知識建議參考上面說起的參考內容
安裝過程省略,首先須要建表,雙擊ZD124UE_DEMO.sql
便可新建一個數據庫及其相應的表格。而後配置數據源,參照下圖,點擊Test查看連通狀況
注意:這裏Open函數的字符串須要和上面配置的數據源一致
BOOL CAdoMySQLHelper::MySQL_Connect(){ // 初始化OLE/COM庫環境 CoInitialize(NULL); try{ // 經過名字建立Connection對象 HRESULT hr = this->m_pConnection.CreateInstance("ADODB.Connection"); if (FAILED(hr)){ AfxMessageBox(_T("建立_ConnectionPtr智能指針失敗")); return false; } // 設置鏈接超時時間 this->m_pConnection->ConnectionTimeout = 600; // 設置執行命令超時時間 this->m_pConnection->CommandTimeout = 120; // 鏈接數據庫 this->m_pConnection->Open("DSN=MySQL5.5;Server=localhost;Database=ZD124UE_DEMO", "root", "root", adModeUnknown); } catch (_com_error &e){ // 若鏈接打開,須要在異常處理中關閉和釋放鏈接 if ((NULL != this->m_pConnection) && this->m_pConnection->State){ this->m_pConnection->Close(); this->m_pConnection.Release(); this->m_pConnection = NULL; } // 非CView和CDialog須要使用全局函數AfxMessageBox AfxMessageBox(e.Description()); } return true; }
void CAdoMySQLHelper::MySQL_Close(){ if ((NULL != this->m_pConnection) && (this->m_pConnection->State)){ this->m_pConnection->Close(); // 關閉鏈接 this->m_pConnection.Release();// 釋放鏈接 this->m_pConnection = NULL; } // 訪問完COM庫後,卸載COM庫 CoUninitialize(); }
這部分能夠完成數據庫四大操做中的增、刪、改三大操做,也就是用一行SQL語句完成
BOOL CAdoMySQLHelper::MySQL_ExecuteSQL(CString sql){ _CommandPtr m_pCommand; try{ m_pCommand.CreateInstance("ADODB.Command"); _variant_t vNULL; vNULL.vt = VT_ERROR; // 定義爲無參數 vNULL.scode = DISP_E_PARAMNOTFOUND; // 將創建的鏈接賦值給它 m_pCommand->ActiveConnection = this->m_pConnection; // SQL語句 m_pCommand->CommandText = (_bstr_t)sql; // 執行SQL語句 m_pCommand->Execute(&vNULL, &vNULL, adCmdText); } catch (_com_error &e){ // 須要在異常處理中釋放命令對象 if ((NULL != m_pCommand) && (m_pCommand->State)){ m_pCommand.Release(); m_pCommand = NULL; } // 非CView和CDialog須要使用全局函數AfxMessageBox AfxMessageBox(e.Description()); return false; } return true; }
查詢內容的方式實現以下面的代碼實現。同時爲了能夠查詢不一樣的表格,使用了void*
做爲返回值,具體使用方法看下面
void* CAdoMySQLHelper::MySQL_Query(CString cond, CString table){ // 打開數據集SQL語句 _variant_t sql = "SELECT * FROM " + (_bstr_t)table + " WHERE " + (_bstr_t)cond; OnRecord* pOnRecord = NULL; RemainTime* pRemainTime = NULL; try{ // 定義_RecordsetPtr智能指針 _RecordsetPtr m_pRecordset; HRESULT hr = m_pRecordset.CreateInstance(__uuidof(Recordset)); if (FAILED(hr)){ AfxMessageBox(_T("建立_RecordsetPtr智能指針失敗")); return (void*)false; } // 打開鏈接,獲取數據集 m_pRecordset->Open(sql, _variant_t((IDispatch*)(this->m_pConnection), true), adOpenForwardOnly, adLockReadOnly, adCmdText); // 肯定表不爲空 if (!m_pRecordset->ADOEOF){ // 移動遊標到最前 m_pRecordset->MoveFirst(); // 循環遍歷數據集 while (!m_pRecordset->ADOEOF){ if (table == ONTABLE) { /********* Get UID ********/ _variant_t varUID = m_pRecordset->Fields->GetItem(_T("UID"))->GetValue(); varUID.ChangeType(VT_BSTR); CString strUID = varUID.bstrVal; /********* Get RemainSeconds ********/ _variant_t varRemainTime = m_pRecordset->Fields->GetItem(_T("RemainTime"))->GetValue(); varRemainTime.ChangeType(VT_INT); int intRemainTime = varRemainTime.intVal; /********** Get StartTime ******************/ _variant_t varStartTime = m_pRecordset->GetCollect(_T("StartTime")); COleDateTime varDateTime = (COleDateTime)varStartTime; CString strStartTime = varDateTime.Format(TIMEFORMAT); /*********** Get isOverTime ***********/ _variant_t varIsOverTime = m_pRecordset->Fields->GetItem(_T("isOverTime"))->GetValue(); varIsOverTime.ChangeType(VT_BOOL); bool boolIsOverTime = varIsOverTime.boolVal; /************ Generate OnRecord ****************/ pOnRecord = new OnRecord(strUID, intRemainTime, strStartTime, boolIsOverTime); } else if(table == REMAINTIMETABLE) { /********* Get UID ********/ _variant_t varUID = m_pRecordset->Fields->GetItem(_T("UID"))->GetValue(); varUID.ChangeType(VT_BSTR); CString strUID = varUID.bstrVal; /********* Get RemainSeconds ********/ _variant_t varRemainTime = m_pRecordset->Fields->GetItem(_T("RemainTime"))->GetValue(); varRemainTime.ChangeType(VT_INT); int intRemainTime = varRemainTime.intVal; /************ Generate RemainTime ****************/ pRemainTime = new RemainTime(strUID, intRemainTime); } break; // Only Return one struct m_pRecordset->MoveNext(); } } } catch (_com_error &e){ AfxMessageBox(e.Description()); } if (table == ONTABLE) return (void*)pOnRecord; else return (void*)pRemainTime; }
調用方法以下:
void CAppdev::OnBnClickedBtnstartweb() { OnRecord* pRecord = (OnRecord*)adoMySQLHelper.MySQL_Query(cond, ONTABLE); // do something you want delete(pRecord); // Important! } void CAppdev::OnBnClickedBtnexitweb() { RemainTime* pRemainTime = (RemainTime*)adoMySQLHelper.MySQL_Query(cond, REMAINTIMETABLE); // do something you want delete(pRemainTime); // important! }
首先須要建立一個定時器,調用SetTimer()
函數設置一個定時器並用一個UINT_PTR
記錄下該定時器,這裏設置的定時器計時單位是毫秒;而後在消息響應函數OnTimer()
中對其進行相關的操做;切記定時器也是須要銷燬的,但須要在消息響應函數DestroyWindow()
中完成,不能在析構函數中完成
/* Appdev.h */ class CAppdev : public CDialogEx { private: UINT_PTR m_ActiveTimer; }; /* Appdev.cpp */ void CAppdev::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); // 啓動定時器 m_ActiveTimer = SetTimer(SCANTIMER_ID, SCANTIMER * 1000, NULL); } void CAppdev::OnTimer(UINT_PTR nIDEvent) { // 在此添加消息處理程序代碼和/或調用默認值 switch (nIDEvent){ case SCANTIMER_ID: // do something here break; default: break; } CDialogEx::OnTimer(nIDEvent); } BOOL CAppdev::DestroyWindow() { // 銷燬定時器 KillTimer(m_ActiveTimer); return CDialogEx::DestroyWindow(); }
軟件是一個讀寫器的上位機軟件,那麼系統會定時更新數據庫即用戶的餘時,並將已經超時的用戶移除和標記。若此時也有用戶訪問數據庫,因爲ADO DB使用的是讀寫模式打開的,所以多併發訪問會出問題。所以,這個問題就變成了一個經典的臨界區問題,因此能夠用經典的臨界區解法=。=在定時器操做數據庫的時候得到鎖,定時器結束釋放鎖,用戶只有當定時器沒得到鎖的時候才能訪問數據庫,其他狀況阻塞
void CAppdev::OnBnClickedBtnretimedefinit() { while (this->isWritingRemainTimeTable) { Sleep(100); } // 休眠0.1s等待定時器操做完成 adoMySQLHelper.MySQL_UpdateRemainTime(uid, DEFAULTREMAINTIME, REMAINTIMETABLE); } void CAppdev::OnTimer(UINT_PTR nIDEvent){ // 在此添加消息處理程序代碼和/或調用默認值 switch (nIDEvent){ case SCANTIMER_ID: this->isWritingRemainTimeTable = true; adoMySQLHelper.MySQL_ScanOnTable(SCANTIMER); this->isWritingRemainTimeTable = false; break; default: break; } CDialogEx::OnTimer(nIDEvent); }
在讀寫文件的時候,打開文件時添加參數CFile::modeNoTruncate
,該參數會打開指定路徑的文件,若沒有該文件,則新建文件後打開,所以不用考慮文件是否存在的狀況
本來的文件只能支持英文,爲了能支持中文甚至所有語言,選擇使用Unicode編碼格式,自定義文件的時候往文件頭添加Unicode編碼頭0xFEFF
便可讓程序知道該文件的編碼方式
BOOL FileUnicodeEncode(CFile &mFile) { WORD unicode = 0xFEFF; mFile.SeekToBegin(); mFile.Write(&unicode, 2); // Unicode return true; }
默認寫到文件末尾,不用煩惱各類插入問題,畢竟不是鏈表,插入比較麻煩
void CRecordHelper::SaveRecharges(CString uid, CString accounts, long remainings, CString result){ // 打開文件 CFile mFile(this->mSaveFile, CFile::modeCreate | CFile::modeNoTruncate | CFile::modeReadWrite); // 獲取當前時間 CTime curTime = CTime::GetCurrentTime(); // 格式化輸出 CString contents; contents.Format(_T("卡號:%s\r\n時間:%s\r\n結果:%s\r\n內容:用戶充值\r\n金額:%s\r\n餘額:%d\r\n\r\n"), uid, curTime.Format(TIMEFORMAT), result, accounts, remainings); // 指向文件末尾並寫入 mFile.SeekToEnd(); mFile.Write(contents, wcslen(contents)*sizeof(wchar_t)); // 關閉文件 mFile.Close(); }
讀取文件因爲要求用戶最新的內容輸出在最開頭,所以選擇讀取的時候倒序分段讀取,拼接字符串便可。寫入文件時使用CFile
類便可,可是要實現按行讀取使用CStdioFile
類比較方便,該類如其名,CStdioFIle::ReadString()
操做能夠讀取一行,剩下的就是判斷一段便可
CString CRecordHelper::LoadRecords(){ // 打開文件 CStdioFile mFile(this->mSaveFile, CFile::modeCreate | CFile::modeNoTruncate | CFile::modeRead | CFile::typeUnicode); // 指向開頭並循環讀入 mFile.SeekToBegin(); CString contents, line, multiLine; contents.Empty(); multiLine.Empty(); // 倒序分段讀取 while (mFile.ReadString(line)) { line.Trim(); if (line.IsEmpty()) { contents = multiLine + _T("\r\n") + contents; multiLine.Empty(); } else { multiLine += (line + _T("\r\n")); } } contents = multiLine + _T("\r\n") + contents; // 關閉文件並返回結果 mFile.Close(); return contents; }
這裏調用構造CFile對象的時候去掉CFile::modeNoTruncate
參數,就默認新建一個文件了
BOOL CRecordHelper::EmptyRecords(){ // 清空文件 CFile mFile(this->mSaveFile, CFile::modeCreate | CFile::modeReadWrite); FileUnicodeEncode(mFile); mFile.Close(); return true; }
這裏貼兩張運行截圖,運行結果都是正確的,只是展現一下界面和字體及顏色