概述

  • 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>实现的功能是创建一个窗口,并在该窗口中响应键盘及鼠标消息,程序实现步骤:

  1. WinMain函数定义
  2. 创建窗口
  3. 消息循环
  4. 编写窗口过程函数

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程序都是通过对话框与用户进行交互。

 
控件设计
 
控件实现

模态对话框

  1. 创建对话框
    资源视图>Dialog>右键>插入Dialog>重命名为 IDD_EXEC
  2. 为对话框添加类
    IDD_EXEC>右击>添加类>类命名为CDlgExec 头文件命名为DlgExec
  3. 为按钮添加事件处理程序
// TODO: 在此添加控件通知处理程序代码
void CMfcDlg::OnBnClickedLogin()
{
    //弹出模态对话框,具有堵塞功能。
    CDlgExec dlg;
    dlg.DoModal();
}

非模态对话框

  1. 创建对话框
    资源视图>Dialog>右键>插入Dialog>重命名为 IDD_Show
  2. 为对话框添加类
    IDD_EXEC>右击>添加类>类命名为CDlgShow 头文件命名为DlgShow
  3. 主对话框头文件添加私有属性
$ vim MfcDlg.h
#include "DlgShow.h"
// 类中添加私有属性
private:
    CDlgShow dlg;
  1. 主对话框消息处理程序初始化中添加创建非模态框对话框
$ vim MfcDlg.cpp
BOOL CMfcDlg::OnInitDialog()
{
    //创建非模态对话框
    dlg.Create(IDD_SHOW);
}
  1. 事件处理程序中添加显示
// 控件通知处理程序代码
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
 
列表控件 CListCtrl
/*列表控件*/
//设置属性 整行选中 网格显示
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.hTabSheet.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();

}

void CTabSheet::OnLButtonDown(UINT nFlags, CPoint point)
{
    CTabCtrl::OnLButtonDown(nFlags, point);

    if (m_nCurrentPage != GetCurFocus())
    {
        m_pPages[m_nCurrentPage]->ShowWindow(SW_HIDE);
        m_nCurrentPage = GetCurFocus();
        m_pPages[m_nCurrentPage]->ShowWindow(SW_SHOW);
        //      m_pPages[m_nCurrentPage]->SetFocus();  
    }
}

int CTabSheet::SetCurSel(int nItem)
{
    if (nItem < 0 || nItem >= m_nNumOfPages)
        return -1;

    int ret = m_nCurrentPage;

    if (m_nCurrentPage != nItem)
    {
        m_pPages[m_nCurrentPage]->ShowWindow(SW_HIDE);
        m_nCurrentPage = nItem;
        m_pPages[m_nCurrentPage]->ShowWindow(SW_SHOW);
        //      m_pPages[m_nCurrentPage]->SetFocus();  
        CTabCtrl::SetCurSel(nItem);
    }

    return ret;
}

int CTabSheet::GetCurSel()
{
    return CTabCtrl::GetCurSel();
}

//释放page资源
void CTabSheet::Free()
{

    CTabCtrl::DeleteAllItems();
    for (int i = 0; i < m_nNumOfPages; i++)
    {
        m_pPages[i]->DestroyWindow();
        m_IDD[i] = NULL;
        m_Title[i] = NULL;

    }
    m_nCurrentPage = 0;
    m_nNumOfPages = 0;
    //CTabCtrl::DestroyWindow();  //注意不要将this指针释放了
}
  • 拖入控件TabControl
  • 添加变量,设置类型为TabSheet
  • 创建标签页,设置属性bordernone,设置stylechild
  • 标签页添加类
  • 主窗口添加标签
/*标签页*/
tab.AddPage(TEXT("系统设置"), &tabcfg, IDD_TAB_CFG);
tab.AddPage(TEXT("系统管理"), &tabmgr, IDD_TAB_MGR);
//显示
tab.Show();