概述
- MFC微軟基礎類庫的作用在Windows平台做GUI開發使用
- MFC框架設計思想
Windows消息機制
- SDK 軟件開發工具包(Software Development Kit)
SDK是軟件開發人員為特定軟件包、框架、硬件平台、操作系統等建立引用軟件的開發工具的集合。
- API 應用程序編程接口(Application Programming Interface)
WindowsAPI函數是通過C實現的,主要在windows.h
頭文件中進行了聲明。
- 窗口和句柄
窗口是Windows應用程序中非常重要的元素,一個Windows應用程序至少要有一個窗口,稱為主窗口。
窗口時屏幕上的一塊矩形區域,是Windows應用程序與用戶進行交互的接口,利用窗口可接受用戶輸入及顯示輸出。

窗口可分為客戶區和非客戶區,客戶區市窗口的一部分。
窗口可有一個父窗口,有父窗口的窗口稱為子窗口。
Windows應用程序中,窗口時通過窗口句柄(HWND)來標識,對窗口的操作時首先要得到窗口句柄。
句柄(HANDLE)是Windows程序的一個重要概念,在Windows程序中,有各種資源如窗口、圖標、光標、畫刷等。系統在創建這些資源時會為其分配內存,並返回標識資源的標識號即句柄。
- 消息和消息隊列
Windows程序設計是一種完全不同於傳統DOS的程序設計方法,是一種事件驅動方式的程序設計模式,主要是基於消息的。
每個Windows應用程序開始執行后,系統都會為該程序創建一個消息隊列,這個消息隊列用來存放該程序創建的窗口的消息。
例如,當用戶在窗口中畫圖時,按下鼠標左鍵,此時操作系統會感知此事件,於是將事件包裝成一個消息,投遞到應用程序的消息隊列中,等待應用程序的處理。然后,應用程序通過一個消息循環不斷地從消息隊列中取出消息,並進行響應。在這個處理過程中,操作系統會給應用程序發送消息,實際是操作系統調用程序中一個專門負責處理消息的函數,這個函數成為窗口過程。

- WinMain函數
當Windows操作系統啟動一個程序時,它調用的就是該程序的WinMain()
函數,實際上是由插入到可執行文件中的啟動代碼調用的。
WinMain()
是Windows程序的入口函數,與DOS程序的入口點函數main()
的作用是相同的,但WinMain()
函數結束或返回時,Windows程序結束。
Windows編程模型
完整的Win32程序#include<windows.h>
實現的功能是創建一個窗口,並在該窗口中響應鍵盤及鼠標消息,程序實現步驟:
- WinMain函數定義
- 創建窗口
- 消息循環
- 編寫窗口過程函數
VS2017配置
包含目錄的路徑,這里以SDk版本10.0.15063.0為例,添加路徑如下:
C:\Program Files (x86)\Windows Kits\10\Include\10.0.15063.0\shared C:\Program Files (x86)\Windows Kits\10\Include\10.0.15063.0\ucrt C:\Program Files (x86)\Windows Kits\10\Include\10.0.15063.0\um C:\Program Files (x86)\Windows Kits\10\Include\10.0.15063.0\winrt 庫目錄的路徑,添加路徑如下: C:\Program Files (x86)\Windows Kits\10\Lib\10.0.15063.0\ucrt\x86 C:\Program Files (x86)\Windows Kits\10\Lib\10.0.15063.0\um\x86
windows.c
$ vim windows.c //底層實現窗口的頭文件 #include <windows.h> /** * 程序入口函數 * WINAPI 宏 代表 __stdcall,表示參數傳遞順序,從右到左依次入棧,在函數返回前清空棧。 * HINSTACE H表示句柄 * HINSTACE hInstance 應用程序實例句柄 * HINSTACE hPrevInstance 上一個應用程序句柄,在win32環境下參數一般為NULL,不起作用。 * LPSTR lpCmdLine 代表 char * argv[] * int nShowCmd 顯示命令,即最大化、最小化、正常 */ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { //1.設計窗口 //2.注冊窗口 //3.創建窗口 //4.顯示更新 //5.通過循環獲取消息 //6.處理消息即窗口過程 //設計窗口 WNDCLASS wc;//Windows類 wc.cbClsExtra = 0;//類的額外內存 wc.cbWndExtra = 0;//窗口額外內存 wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);//設置背景 wc.hCursor = LoadCursor(NULL, IDC_HAND);//設置光標,參數1為NULL為系統提供的光標 wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);//設置圖標 wc.hInstance = hInstance;//應用程序實例句柄,傳入實例句柄 wc.lpfnWndProc = WindowProc;//窗口過程,即回調函數 wc.lpszClassName = TEXT("WIN");//窗口類名 wc.lpszMenuName = NULL;//窗口菜單名稱 wc.style = 0;//顯示風格 0默認風格 //注冊窗口 RegisterClass(&wc); //創建窗口 // lpClassName 窗口類名 // lpWindowName 窗口標題名稱 // dwStyle 窗口風格 混合風格 // x X坐標 默認值 CW_USERDEFAULT // y Y坐標 默認值 CW_USERDEFAULT // nWidth // nHeight 默認值 CW_USERDEFAULT // hWndParent 父窗口,頂層方式彈出為NULL // hMenu 窗口菜單 // hInstance 實例句柄 // lpParam 附加值 HWND hwnd = CreateWindow( wc.lpszClassName, TEXT("WINDOWS"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, CW_USEDEFAULT );//todo “CreateWindowExW”: 指針與實參 12 不匹配 //顯示更新 ShowWindow( hwnd, SW_SHOWNORMAL//展示方式 普通方式 ); UpdateWindow(hwnd); //通過循環獲取消息 /* 每個消息都是一個結構體 HWND hwnd; 主窗口句柄 UINT message; 消息名稱 WPARAM wParam; 附件消息 通常為鍵盤消息 LPARAM lParam; 附加消息 通常為鼠標左右鍵消息 DWORD time; 消息產生時間 POINT pt; 附加消息 鼠標坐標點消息 */ MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { /* 捕獲消息 GetMessage() _Out_ LPMSG lpMsg;//消息地hi _In_opt_ HWND hWnd;//捕獲窗口 NULL為捕獲所有窗口 _In_ UNIT wMsgFilterMin;//最小的過濾消息 0表示捕獲所有消息 _In_ UINT wMsgFilterMax;//最大的過濾消息 0表示捕獲所有消息 */ //if (GetMessage(&msg, NULL, 0, 0) == FALSE) { // break;//若關閉窗口則退出死循環 //} // 翻譯消息 TranslateMessage(&msg);//例如針對鍵盤組合快捷鍵需翻譯 // 分發消息 DispatchMessage(&msg); } return 0; }
窗口處理過程
/** * 處理窗口過程 * HWND hWnd 消息所屬的窗口句柄 * UINT uMsg 具體的消息名稱 * WPARAM wParam 鍵盤附加消息 * LPARAM lParam 鼠標附加消息 */ LRESULT CALLBACK WindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { //根據不同消息做不同的處理 switch (uMsg) { //關閉窗口 發送另一個消息WM_DESTROY case WM_CLOSE: DestroyWindow(hWnd); //所有xxxWindow為結尾的方法都不會進入消息隊列而會直接執行 break; //關閉窗口退出進程 case WM_DESTROY: PostQuitMessage(0); break; //鼠標左鍵按下 case WM_LBUTTONDOWN: { int xPos = LOWORD(lParam); int yPos = HIWORD(lParam); char buf[1024]; wsprintf(buf, TEXT("X = %d Y = %d"), xPos, yPos);//todo 從“char [1024]”到“LPCWSTR”的類型不兼容 MessageBox(hWnd, buf, TEXT("TITLE"), MB_OK); break; } //鍵盤按下 case WM_KEYDOWN: MessageBox(hWnd, TEXT("KEYDOWN"), TEXT("TITLE"), MB_OK); break; //繪圖 case WM_PRINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, &ps); TextOut(hdc, 100, 100, TEXT("PRINT"), strlen("PRINT")); EndPaint(hWnd, &ps); break; } } //返回值使用默認處理方式 return DefWindowProc(hWnd, uMsg, wParam, lParam); }
MFC
MFC(Microsoft Foundation Classes, 微軟基礎類庫)是微軟提供的類庫(class libraries),以C++類的形式封裝的WindowsAPI,包含一個應用程序框架,以減少應用程序開發人員的工作量。其中類包含大量Windows句柄封裝類和Windows內建控件和組件的封裝類。
MFC把Windows SDK API函數包裝成幾百個類,MFC給Windows操作系統提供面向對象的接口,支持可重用性、自包含性以及其他OPP原則。MFC通過編寫類來封裝窗口、對話框等其他對象,引入關鍵的虛函數(覆蓋虛函數可改變派生類的功能)來完成,MFC設計者使類庫帶來的總開銷降到了最低。
MFC快速入門
- MFC源文件后綴為
.cpp
,因為MFC是C++編寫的。 - 編寫MFC程序需包含
#include<afxwin.h>
頭文件
$ vim MFCApp.h
// MFCApp.h: PROJECT_NAME 應用程序的主頭文件
#pragma once
#ifndef __AFXWIN_H__
#error "在包含此文件之前包含“stdafx.h”以生成 PCH 文件"
#endif
// 主符號
#include "resource.h"
// CMFCAppApp:
// 有關此類的實現,請參閱 MFCApp.cpp
//應用程序類CWinApp派生類(子類)
class CMFCAppApp : public CWinApp
{
public:
CMFCAppApp();
// 重寫
public:
//基類的虛函數,派生類只是重寫,MFC程序的入口地址。
virtual BOOL InitInstance();
// 實現
//聲明消息映射,必須在類聲明中。
DECLARE_MESSAGE_MAP()
};
extern CMFCAppApp theApp;
MFC消息映射
消息映射是將消息和成員函數相互關聯的表,例如框架窗口接收一個鼠標左擊消息,MFC將搜索該窗口的消息映射,如果存在一個處理WM_LBUTTONDOWN
消息的處理程序,就調用OnLButtonDown
。
//聲明消息映射,必須在類聲明中。
DECLARE_MESSAGE_MAP()
// 定義消息宏,必須在類實現中。 BEGIN_MESSAGE_MAP(CMFCAppApp, CWinApp) ON_COMMAND(ID_HELP, &CWinApp::OnHelp) END_MESSAGE_MAP()
Windows 字符集
- 多字節中1個字符對應1個字節
- 中文 1個字符對應多個字節
- 寬字節 Unicode
- 多字節轉為寬字節
L"test"
TEXT()
具有自適應編碼轉碼TCHER()
具有自適應編碼轉碼

統計字符串長度
//統計多字節長度 int num = 0; char * p = "test"; num = strlen(p); //統計寬字節字符串 wchar_t *p = L"test"; int num = wcslen(p);
char *
與CString
之間的轉換
// char *轉CString char * p = "test"; CString str = CString(p); //CString轉char * CString str = CString("test"); CStringA tmp; tmp = str; char * p = tmp.GetBuffer();
- MFC中后綴為
Ex
的函數都是擴展函數 - MFC中以
Afx
為前綴的都是全局函數
MFC基於對話框編程
對話框是一種特殊類型的窗口,大多數Windows程序都是通過對話框與用戶進行交互。


模態對話框
- 創建對話框
資源視圖>Dialog>右鍵>插入Dialog>重命名為 IDD_EXEC - 為對話框添加類
IDD_EXEC>右擊>添加類>類命名為CDlgExec 頭文件命名為DlgExec - 為按鈕添加事件處理程序
// TODO: 在此添加控件通知處理程序代碼 void CMfcDlg::OnBnClickedLogin() { //彈出模態對話框,具有堵塞功能。 CDlgExec dlg; dlg.DoModal(); }
非模態對話框
- 創建對話框
資源視圖>Dialog>右鍵>插入Dialog>重命名為 IDD_Show - 為對話框添加類
IDD_EXEC>右擊>添加類>類命名為CDlgShow 頭文件命名為DlgShow - 主對話框頭文件添加私有屬性
$ vim MfcDlg.h #include "DlgShow.h" // 類中添加私有屬性 private: CDlgShow dlg;
- 主對話框消息處理程序初始化中添加創建非模態框對話框
$ vim MfcDlg.cpp BOOL CMfcDlg::OnInitDialog() { //創建非模態對話框 dlg.Create(IDD_SHOW); }
- 事件處理程序中添加顯示
// 控件通知處理程序代碼 void CMfcDlg::OnBnClickedLogin() { //彈出非模態對話框 //CDlgShow dlg; //dlg.Create(IDD_SHOW);//創建 dlg.ShowWindow(SW_SHOWNORMAL);//顯示 }
靜態文本 CStaticText
- 添加變量 以
STATIC
為結尾的ID是不可以添加變量的需修改ID - 設置內容
SetWindowTextW()
- 獲取內容
GetWindowTextW()
void CMfcDlg::OnBnClickedSet() { text.SetWindowTextW(TEXT("Account")); btnSet.SetWindowTextW(TEXT("禁用")); btnSet.EnableWindow(FALSE);//禁用按鈕 } void CMfcDlg::OnBnClickedGet() { CString str; text.GetWindowTextW(str); MessageBox(str); }
靜態圖片BMP
- 添加變量
- 初始化顯示圖片
//顯示圖片 img.ModifyStyle(0xf, SS_BITMAP | SS_CENTERIMAGE);//設置風格為16進制位圖並居中顯示 //獲取圖片路徑獲取bitmap句柄 #define HBMP(filepath, width, height)(HBITMAP)LoadImage(AfxGetInstanceHandle(), filepath, IMAGE_BITMAP, width, height, LR_LOADFROMFILE|LR_CREATEDIBSECTION) //寬高按空間大小設置 CRect rect; img.GetWindowRect(rect); //設置bitmap img.SetBitmap(HBMP(TEXT("./Image/plane.bmp"), rect.Width(), rect.Height()));
編輯框 EditControl
- 屬性:
mutiline
多行、want return
換行 - 獲取設置值:
GetWindowTextW()
和SetWindowText()W
- 單行點擊回車會退出,重寫
OnOK()
注釋其中代碼。對話框 - 退出當前對話框
CDialog::OnOk()
和CDialog::OnCancel()
// 源編輯框默認文本 editor_src.SetWindowTextW(TEXT("please input something")); //點擊復制按鈕 void CMfcDlg::OnBnClickedBtnCopy() { CString str; editor_src.GetWindowTextW(str); editor_dst.SetWindowTextW(str); }
退出的方式
void CMfcDlg::OnBnClickedExit() { //退出當前對話框 CDialog::OnOK();//點擊確定退出對話框 //CDialog::OnCancel();//點擊取消推出對話框 //exit(0);//退出所有對話框 }
添加變量時添加value
,本身關聯的變量就是值。
UpdateData(TRUE);//將控件中的內容同步到變量中
void CMfcDlg::OnBnClickedBtnSetval() { editval = TEXT("setval"); UpdateData(FALSE);//將變量中的值同步到控件中 } void CMfcDlg::OnBnClickedBtnGetval() { UpdateData(TRUE);//將控件中的內容同步到變量中 MessageBox(editval); }
下拉框CComBox
data
屬性中添加數據,逗號分隔。sort
屬性排序。type
屬性為DropList
則不可編輯- 添加元素
AddString()
- 刪除元素
DeleteString()
- 插入元素
InsertString()
- 設置默認
SetCurSel()
- 獲取當前索引
GetCurSel()
- 根據索引獲取元素
GetLBText(int Index, CString str)
- 控件事件
OnCbnSelChangeXXX()
//下拉框添加元素 role.AddString(TEXT("管理員")); role.AddString(TEXT("渠道商")); role.AddString(TEXT("代理商")); role.AddString(TEXT("員工")); role.AddString(TEXT("玩家")); //設置默認選項 role.SetCurSel(0); //插入選項 role.InsertString(4, TEXT("會員")); //刪除選項 role.DeleteString(3); //獲取索引所對應的值 CString str; role.GetLBText(1, str); //MessageBox(str);
void CMfcDlg::OnCbnSelchangeComboRole() { //獲取當前索引 int index = role.GetCurSel(); //獲取值 CString str; role.GetLBText(index, str); MessageBox(str); }
列表控件 CListCtrl
- 控件屬性
view
設置為報表模式Report
- 添加表頭
InsertColumn()
- 添加正文 從0開始
InsertItem(0,content)
- 添加正文其他列表
SetItemText(row, col, content)
- 設置風格 整行選中
LVS_EX_FULLROWSELECT
網格顯示LVS_EX_GRIDLINES

/*列表控件*/ //設置屬性 整行選中 網格顯示 list.SetExtendedStyle(list.GetExtendedStyle()|LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES); //設置表頭 CString strArr[] = {TEXT("賬號"), TEXT("昵稱"), TEXT("郵箱")}; for (int i = 0; i < 3; i++) { list.InsertColumn(i, strArr[i], LVCFMT_LEFT, 100);//參數 索引 內容 對齊 列寬 } list.InsertItem(0, TEXT("a12l31c09e")); list.SetItemText(0, 1, TEXT("Alice")); list.SetItemText(0, 2, TEXT("alice520@gmail.com")); //循環添加 int j; for (int i = 0; i < 10; i++) { j = 0; CString str; str.Format(TEXT("alice%d"), i); list.InsertItem(i, str); list.SetItemText(i, ++j, str); list.SetItemText(i, ++j, str); }
樹形控件Tree Control
- 拖拽控件,命名ID為
IDC_TREE
- 設置私有
private
變量CTreeCtrl tree;
- 設置屬性:
Has Buttons
設置按鈕、Has Lines
設置線、Lines At Root
設置根節點線 - 准備圖標:資源視圖>Icon>右鍵>添加資源>Icon>導入,圖標采用ico文件72像素。
- 初始化中設置圖標
CMfcDlg::OnInitDialog()
//設置圖標 //CImageList imglist;//圖片集合,必須保存.h作為成員屬性。 imglist.Create(30, 30, ILC_COLOR32, 6, 6); //添加圖標 HICON icons[6]; icons[0] = AfxGetApp()->LoadIconW(IDI_ICON1); icons[1] = AfxGetApp()->LoadIconW(IDI_ICON2); icons[2] = AfxGetApp()->LoadIconW(IDI_ICON3); icons[3] = AfxGetApp()->LoadIconW(IDI_ICON4); icons[4] = AfxGetApp()->LoadIconW(IDI_ICON5); icons[5] = AfxGetApp()->LoadIconW(IDI_ICON6); for (int i = 0; i < 6; i++) { imglist.Add(icons[i]); } //設置圖標 tree.SetImageList(&imglist, TVSIL_NORMAL); //設置根節點 HTREEITEM root = tree.InsertItem(TEXT("ROOT"), 0, 1, NULL); //設置父節點 HTREEITEM parent = tree.InsertItem(TEXT("PARENT"), 2, 3, root); //設置子節點 HTREEITEM child1 = tree.InsertItem(TEXT("CHILD1"), 4, 5, parent); HTREEITEM child2 = tree.InsertItem(TEXT("CHILD2"), 4, 5, parent); //設置默認選項 tree.SelectItem(child1);
- 設置事件
OnTvnSelchanged
//樹項目切換 void CMfcDlg::OnTvnSelchangedTree(NMHDR *pNMHDR, LRESULT *pResult) { LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR); *pResult = 0; //獲取當前選中項 HTREEITEM item = tree.GetSelectedItem(); CString text = tree.GetItemText(item); MessageBox(text); }

標簽頁Tab Control

- 引用
TabSheet.h
和TabSheet.cpp
並添加到項目中
TabSheet.h
#if !defined(AFX_TABSHEET_H__42EE262D_D15F_46D5_8F26_28FD049E99F4__INCLUDED_)
#define AFX_TABSHEET_H__42EE262D_D15F_46D5_8F26_28FD049E99F4__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
// TabSheet.h : header file
//
/////////////////////////////////////////////////////////////////////////////
// CTabSheet window
#define MAXPAGE 16
class CTabSheet : public CTabCtrl
{
// Construction
public:
CTabSheet();
// Attributes
public:
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CTabSheet)
//}}AFX_VIRTUAL
// Implementation
public:
int GetCurSel();
int SetCurSel(int nItem);
void Show();
void Free();
void SetRect();
BOOL AddPage(LPCTSTR title, CDialog *pDialog, UINT ID);
virtual ~CTabSheet();
// Generated message map functions
protected:
LPCTSTR m_Title[MAXPAGE];
UINT m_IDD[MAXPAGE];
CDialog* m_pPages[MAXPAGE];
int m_nNumOfPages;
int m_nCurrentPage;
//{{AFX_MSG(CTabSheet)
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
/////////////////////////////////////////////////////////////////////////////
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_TABSHEET_H__42EE262D_D15F_46D5_8F26_28FD049E99F4__INCLUDED_)
TabSheet.cpp
// TabSheet.cpp : implementation file // #include "stdafx.h" #include "TabSheet.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // CTabSheet CTabSheet::CTabSheet() { m_nNumOfPages = 0; m_nCurrentPage = 0; } CTabSheet::~CTabSheet() { } BEGIN_MESSAGE_MAP(CTabSheet, CTabCtrl) //{{AFX_MSG_MAP(CTabSheet) ON_WM_LBUTTONDOWN() //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CTabSheet message handlers BOOL CTabSheet::AddPage(LPCTSTR title, CDialog *pDialog, UINT ID) { if (MAXPAGE == m_nNumOfPages) return FALSE; m_nNumOfPages++; m_pPages[m_nNumOfPages - 1] = pDialog; m_IDD[m_nNumOfPages - 1] = ID; m_Title[m_nNumOfPages - 1] = title; return TRUE; } void CTabSheet::SetRect() { CRect tabRect, itemRect; int nX, nY, nXc, nYc; GetClientRect(&tabRect); GetItemRect(0, &itemRect); nX = itemRect.left; nY = itemRect.bottom + 1; nXc = tabRect.right - itemRect.left - 2; nYc = tabRect.bottom - nY - 2; m_pPages[0]->SetWindowPos(&wndTop, nX, nY, nXc, nYc, SWP_SHOWWINDOW); for (int nCount = 1; nCount < m_nNumOfPages; nCount++) m_pPages[nCount]->SetWindowPos(&wndTop, nX, nY, nXc, nYc, SWP_HIDEWINDOW); } void CTabSheet::Show() { for (int i = 0; i < m_nNumOfPages; i++) { m_pPages[i]->Create(m_IDD[i], this); InsertItem(i, m_Title[i]); } m_pPages[0]->ShowWindow(SW_SHOW); for (int i = 1; i < m_nNumOfPages; i++) m_pPages[i]->ShowWindow(SW_HIDE); SetRect(); }