概述
- 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(); }