第一章 Windows程序内部运行机制 1.1 API和SDK
API:Windows操作系统提供给应用程序编程的接口。
SDK(软件开发包):用于开发的所有资源的集合。
1.2 窗口和句柄
窗口
句柄:系统在创建资源时会为他们分配内存,并返回这些资源的标识号,即句柄。(类似于指针) 窗口句柄(HWND)、图标(HICON)、光标(HCURSOR)、画刷(HBRUSH)
1.3 消息和队列 消息(先进先出)
1 2 3 4 5 6 7 8 typedef struct tagMSG { HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; } MSG, *PMSG;
1.4 WinMain
Win32实现的步骤
定义WinMain函数;
创建窗口;
消息循环;
窗口过程函数。
1.4.1 WinMain函数的定义 winmain函数是程序入口函数,由系统调用。
1 2 3 4 5 6 int WINAPI WinMain ( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) ;
1.4.2 窗口的创建
步骤
设计窗口类;
注册窗口;
创建窗口;
显示及更新窗口;
1.4.2.1 设计窗口类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 typedef struct _WNDCLASS //窗口类{ UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HINSTANCE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCTSTR lpszMenuName; LPCTSTR lpszClassName; } WNDCLASS, *PWNDCLASS;
1.4.2.2 注册窗口 1 ATOM RegisterClass ( CONST WNDCLASS *lpWndClass ) ;
1.4.2.3 创建窗口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 HWND CreateWindow ( LPCTSTR lpClassName, LPCTSTR lpWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam ) ;
注意:创建窗口成功,函数返回系统为该窗口分配的句柄,否则返回NULL。创建窗口之前应先定义一个窗口句柄变量来接收创建窗口之后返回的句柄值。
1.4.2.4 显示及更新窗口 显示窗口
1 2 3 4 BOOL ShowWindow ( HWND hWnd, int nCmdShow ) ;
更新窗口
1 2 3 BOOL UpdateWindow ( HWND hWnd ) ;
1.4.3 消息循环 1 2 3 4 5 6 BOOL GetMessage ( LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax ) ;
注意:GetMessage函数除了接收到WM_QUIT(退出消息)外都返回非零值,出错返回-1。
通常编写的消息循环代码如下:
1 2 3 4 5 6 MSG msg; while (GetMessage(&msg,NULL ,0 ,0 )){ TranslateMessage(&msg); DispatchMessage(&msg); }
1.4.2窗口创建和1.4.3消息循环都包含在WinMain函数体内
1.4.4 窗口过程函数 即回调函数
1 2 3 4 5 6 LRESULT CALLBACK WindowProc ( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) ;
过程函数名WindowProc可以换,但要和声明保持一致。
使用switch/case来对不同消息作出不同反应。
如下方例子中的过程函数所示。
课后程序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 #define _CRT_SECURE_NO_DEPRECATE #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <windows.h> #include <stdexcept> LRESULT CALLBACK WinSunProc ( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) ;int WINAPI WinMain ( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { WNDCLASS wndcls; wndcls.cbClsExtra = 0 ; wndcls.cbWndExtra = 0 ; wndcls.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); wndcls.hCursor = LoadCursor(NULL , IDC_CROSS); wndcls.hIcon = LoadIcon(NULL , IDI_ERROR); wndcls.hInstance = hInstance; wndcls.lpfnWndProc = WinSunProc; wndcls.lpszClassName="hezexian2021" ; wndcls.lpszMenuName = NULL ; wndcls.style = CS_HREDRAW | CS_VREDRAW; RegisterClass(&wndcls); HWND hwnd; hwnd = CreateWindow("hezexian2021" , "He Zexian's home." , WS_OVERLAPPEDWINDOW, 0 , 0 , 600 , 400 , NULL , NULL , hInstance, NULL ); ShowWindow(hwnd, SW_SHOWNORMAL); UpdateWindow(hwnd); MSG msg; while (GetMessage(&msg, NULL , 0 , 0 )) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } LRESULT CALLBACK WinSunProc ( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { switch (uMsg) { case WM_CHAR: char szChar[20 ]; sprintf (szChar, "char code is %d" , wParam); MessageBox(hwnd, szChar, "char" , 0 ); break ; case WM_LBUTTONDOWN: MessageBox(hwnd, "mouse clicked" , "message" , 0 ); HDC hdc; hdc = GetDC(hwnd); TextOut(hdc, 0 , 50 , "何泽贤之家" ,strlen ("何泽贤之家" )); ReleaseDC(hwnd, hdc); break ; case WM_PAINT: HDC hDC; PAINTSTRUCT ps; hDC = BeginPaint(hwnd, &ps); TextOut(hDC, 0 , 0 , "http://www.hezexian.org" , strlen ("http://www.hezexian.org" )); EndPaint(hwnd, &ps); break ; case WM_CLOSE: if (IDYES == MessageBox(hwnd, "是否真的结束了?" , "message" , MB_YESNO)) { DestroyWindow(hwnd); } break ; case WM_DESTROY: PostQuitMessage(0 ); break ; default : return DefWindowProc(hwnd, uMsg, wParam, lParam); } return 0 ; }
补充: 1、如遇到cannot convert from ‘const char [7]’ to ‘LPCWSTR’的错误,请对工程进行字符设置菜单栏-<项目-<xxx 属性-<配置属性-<高级-<字符集,改成“not set”或改成“Use Multi-Byte Character Set”。 2、vs2019—Windows桌面应用程序
参考文献 [1] VC深入详解 孙鑫 第一章 Windows程序内部运行机制 - focusahaha - 博客园 (cnblogs.com).https://www.cnblogs.com/focusahaha/p/12601081.html [2]孙鑫.VC深度详解修订版[M]. 北京:电子工业出版社, 2012. 1-26.
第二章 掌握C++ 2.1 从结构到类 2.1.1 结构体的定义
C++相比于C的特性:封装性、继承性、多态性;
对象具有状态和行为,状态保存在成员变量中,行为通过函数实现;
标准输入输出流对象:cin(>>)默认键盘 和 cout(<<)、cerr(<<)默认显示器;自动根据数据类型调整输入输出格式;
结构体中的函数称为成员函数。
程序2.1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <iostream> using namespace std ; struct point { int x, y; void output () { cout << "x=" << x << endl << "y=" << y << endl ; } }pt; int main () { cout << "请输入x和y的值:" << endl ; cin >> pt.x; cin >> pt.y; pt.output(); return 0 ; }
2.1.2 结构体与类 在C++中,结构体(struct)和类(class)可以通用。 区别在于访问控制的差异:
struct默认访问控制标识符public;public:可以在类的外部进行访问
class默认访问控制标识符private;private:只能在类的内部进行访问
protective。
程序2.2:将程序2.1中的struct point修改为class point
1 2 3 4 5 6 7 8 9 class point //类:抽象出一些事物共有的属性{ public: int x, y; void output () { cout << "x=" << x << endl << "y=" << y << endl ; } }pt;
2.2 C++的特性 2.2.1 类与对象 类:抽象出一些事物共有的属性; 对象:有具体的属性值。
2.2.2 构造函数 作用:在定义对象的同时,对成员变量进行初始化; 创建对象本身(分配内存) 规定:构造函数的名字和类名相同(唯一性); 没用返回值; 可以有参数。 注意:
如果一个类没有定义任何构造函数,那么C会提供一个默认的不带参构造函数。而只要类中定义了一个构造函数,C便不再提供任何其他构造函数;
每个类必须有一个构造函数,没有构造函数不能创建任何对象。构造函数如代码2.3所示。
2.2.3 析构函数 ~类名(); //对象生命周期结束,释放其占用资源;是一个对象最后调用的成员函数
注意:析构函数不允许有返回值,更不允许有参数,并且一个类中只有一个构造函数。代码2.3:构造函数和析构函数
1 2 3 4 5 6 7 8 9 10 11 12 13 class point //类名:point { public: int x , y; point() { x = 0 ; y = 0 ; } ~point() { } };
2.2.4 函数的重载 条件:函数参数类型、个数不同才能构成重载。 注意:
只有函数的返回值类型不同,不能重载;
重载时,注意函数带有默认参数的情况。(P38)
对比覆盖:重载是发生在同一个类当中;覆盖是发生在父类和子类之间
代码2.4 函数的重载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class point { public: int x , y; point() { x = 0 ; y = 0 ; } point(int a, int b) { x = a; y = b; } }; void main () { point pt (5 ,5 ) ; }
2.2.5 this指针
this指针是一个隐含的指针,指向对象本身,代表对象的地址。
用法:当形参的变量名和成员变量的变量名冲突时,可用this->来区别对哪一个变量进行赋值。(P40)
this->x是对成员变量进行赋值; x是对形参赋值。
2.2.6 类的继承 2.2.6.1 继承 例如:class fish : public animal{};
animal是父类,fish是子类。子类fish以public(公有)的方式继承父类animal。
子类除了自己的成员变量和成员方法外,还可以继承父类的成员变量和成员方法。
构造函数和析构函数的调用次序:
2.2.6.2 在子类中调用父类带参数的构造函数 例如:父类构造函数: animal(int h , int w){}
则在构造子类时,应该显式地去调用父类的带参数构造函数: fish():animal(400,300){}
2.2.6.3 类的继承及类中成员的访问特性 3种访问权限修饰符: public:定义的成员可以在任何地方被访问; protected:定义的成员只能在该类及其子类中访问; private:定义的成员只能在该类自身中访问。==>不能被子类继承
3种继承方式:
2.2.6.4 多重继承 定义形式:class B: public C , public D
了解父类表顺序对调用构造函数和析构函数的影响。对于上面的例子,先构造C再构造D,先析构D再析构C。
2.2.7 虚函数与多态性、纯虚函数 2.2.7.1虚函数与多态性 程序 2.5
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 (P49)... class animal { public: ... void breathe () {cout <<"animal breathe" <<endl ;} }; class fish : public animal{ public: ... void breathe () {cout <<"fish bubble" <<endl ;} } void breathetest (animal* pan) { pan->breathe(); } void main () { animal* pan; fish fi; pan = &fi; breathetest(pan); }
1、fish对象也是一个animal类,C++自动进行类型转换;反之,不能把animal对象看成fish对象。
2、当我们将fish转化为animal时,该对象就会被认为是原对象内存模型的上半部分。
3、将父类函数修改为虚函数,输出结果为fish bubble。
1 2 3 4 (P49)... virtual void breathe () ...
虚函数:用virtual关键字申明的函数—>多态性; 迟邦定:编译时并不确定具体调用的函数,而是在运行时依据对象的类型来确认调用的是哪一个函数。
概括(用法):在父类的函数前加上virtual关键字,在子类重写该函数,运行时将会根据对象的实际类型来调用相应的函数。
2.2.7.2 纯虚函数
纯虚函数不被具体实现,是抽象类,不能实例化对象。
纯虚函数可以让类先具有一个操作名称,而没有操作内容,在子类继承时再去具体定义。
写法:
1 virtual void breathe () = 0 ;
注意:子类如果有对父类虚函数的覆盖定义,无论该覆盖定义是否有virtual,都是虚函数。
2.2.8函数的覆盖和隐藏 2.2.8.1 覆盖 1、覆盖(P52):发生在父类和子类之间 的 函数完全一样(函数名、参数列表),编译器根据实际类型确定要调用的函数。 2、对比重载:重载是发生在同一个类当中;覆盖是发生在父类和子类之间
程序2.6 函数的覆盖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class animal { public: virtual void breathe () { cout <<"animal breathe" <<endl ; } }; class fish : public animal{ public: void breathe () { cout <<"fish bubble" <<endl ; } }; void main () { fish fi; fi.breathe(); }
2.2.8.2 隐藏 两种父类函数隐藏的情况:
子类和父类函数完全相同(函数名、参数列表),但父类函数没有virtual,则父类函数被隐藏。
子类和父类函数同名,但参数列表不同,则父类函数被隐藏。
区别隐藏与覆盖:覆盖是发生在父类与子类之间,两个函数必须完全相同且都是虚函数。否则就是隐藏。
2.2.9 引用 定义形式:
注意:
引用只是一个别名,不占用内存空间;此处要和指针区分开来。
引用多用于函数的形参定义。如程序2.4所示。
程序2.7 引用示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream> using namespace std ; void change (int * a,int * b) ; int main () { int x = 5 , y = 3 ; cout << "x=" << x << endl ; cout << "y=" << y << endl ; change(&x,&y); cout << "x=" << x << endl ; cout << "y=" << y << endl ; return 0 ; } void change (int * a, int * b) { int c; c = *a; *a = *b; *b = c; }
2.2.10 C++类设计习惯和头文件重复包含问题的解决 1、头文件存放类的定义及类成员函数的声明; 源文件存放类中成员函数的实现。
2、注意文件包含;#include "animal.h" //"",从当前目录开始搜索,然后是系统目录和PATH环境变量所列出的目录
#include <iostream> //<>,从系统目录开始搜索,然后是PATH环境变量所列出的目录。``不搜索当前目录
注意子类的头文件中也要包含父类的头文件,如fish.h中的#include “animal.h”。
3、::是作用域表示符,表明函数或数据成员属于哪个类; 前面如果不跟类名,表示全局函数(即非成员函数)或全局数据。
4、在头文件中声明函数时用了virtual,在源文件中就不用写virtual。
5、要解决类重复定义的问题,要使用条件预处理指令(如课后程序animal.h和fish.h所示)
程序2.8 条件预处理指令解决类重复定义的问题
1 2 3 4 5 6 7 8 9 #ifndef ANIMAL_H_H #define ANIMAL_H_H class animal { }; #endif
2.2.11 VC++程序编译连接的原理与过程 1、编译:头文件不参与编译 源文件单独编译 XXX.obj目标文件 2、链接
编译链接过程如下。
课后程序 animal.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #ifndef ANIMAL_H_H #define ANIMAL_H_H class animal { public: animal(); ~animal(); void eat () ; void sleep () ; virtual void breathe () ; }; #endif
animal.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <iostream> #include "animal.h" using namespace std ; animal::animal() { } animal::~animal() { } void animal::eat () { } void animal::sleep () { } void animal::breathe () { cout << "animal breathe" << endl ; }
fish.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include "animal.h" #ifndef FISH_H_H #define FISH_H_H class fish : public animal { public: void breathe () ; }; #endif
fish.cpp
1 2 3 4 5 6 7 8 9 10 11 #include <iostream> #include "fish.h" using namespace std ; void fish::breathe () { cout << "fish bubble" << endl ; }
main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include "animal.h" #include "fish.h" void breathetest (animal* pan) { pan->breathe(); } int main () { animal* pan; fish fi; animal an; pan = &fi; breathetest(pan); pan = &an; breathetest(pan); return 0 ; }
运行结果
问题及反思 vs2019编程出现的问题: 1、建立C++空项目后,要在右侧工程的源文件的文件夹下创建XXX.cpp文件;
否则编译时无法启动程序XXX.exe,系统找不到指定文件。
2、头文件#include 后添加using namespace std; 否则报错:error C1083: 无法打开包括文件:“iostream.h”: No such file or directory 3、定义结构体或类时,别忘记最后的分号;结构体名或类名后面没有小括号; 4、引用成员函数别忘记括号:pt.output(); 5、.sln为项目文件
参考文献 [1]<https://blog.csdn.net/weixin_43751983/article/details/91147918 >this指针 [2]孙鑫.VC++深度详解修订版[M]. 北京:电子工业出版社, 2012. 27-62.
第三章 MFC框架程序剖析 3.1创建MFC AppWizard 如何利用vs2019创建MFC应用见参考文献[1] 需要注意的地方有 [1]创建MFC单文档应用程序
[2]开启类视图窗口
3.2基于MFC的程序框架剖析 在MFC中,类的命名都以C开头;对于单文档应用程序,如图3.2所示,都有:
CAboutDlg帮助类,同于说明这个工程的开发信息;
CMainFrame主框架类;
C工程名App应用程序入口;
C工程名Doc文档类,用来管理、存放数据;
C工程名View用来将文档中的数据可视化。
CMainFrame类和CTestView类都有一个共同的基类:Cwnd类,其封装了与窗口相关的操作。
3.2.1 MFC中的WinMain函数 文件路径(在安装路径下直接搜索MFC,找到mfc):D:\Program Files (x86)\visualstudio\VC\Tools\MSVC\14.29.30037\atlmfc\src,打开appmodul.cpp 查找WinMain
1 2 3 4 5 6 7 8 extern "C" int WINAPI_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, _In_ LPTSTR lpCmdLine, int nCmdShow) #pragma warning (suppress: 4985) { return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow); }
3.2.1.1 theApp全局对象 以简单的C++源程序为例,在入口函数main()加载之前,就已经为全局变量(对象)分配了内存空间,并为其赋了初值。对于一个全局对象来说,此时就会调用该对象的构造函数构造该对象并进行初始化操作,然后才是进入main()函数。(P72例3-4已标出先后顺序)
对于MFC来说,通过产生一个应用程序类的对象来唯一标识应用程序的实例。每个MFC程序有且仅有一个从应用程序类(CWinApp)派生的类;每个MFC程序实例有且仅有一个该派生类的实例化对象,即theApp全局对象,theApp表示了该应用程序本身。theApp的定义如程序3.1所示,在test.cpp中查看。
程序3.1 theApp全局对象
1 2 3 4 5 6 7 8 9 CtestApp theApp; class CtestApp : public CWinApp { ...... }
文件路径(在安装路径下直接搜索MFC,找到mfc):D:\Program Files (x86)\visualstudio\VC\Tools\MSVC\14.29.30037\atlmfc\src,打开appcore.cpp 查找CWinApp(184行)其中,程序3.2
1 2 3 4 5 CWinApp::CWinApp(LPCTSTR lpszAppName) { ... pModuleState->m_pCurrentWinApp = this; ... }
程序3.3
1 2 3 4 5 class CWinApp : public CWinThread{... explicit CWinApp (LPCTSTR lpszAppName = NULL ) ; ... };
补充:如果某个函数的参数有默认值,那么在调用该函数时可以传参,也可以不传参直接使用默认值。
由程序3.3可见,来CWinApp类的定义时,CWinApp的构造函数的形参有默认值NULL。因此,在调用CWinApp类的构造函数时,不用显式地传参。
3.2.1.2 AfxWinMain函数 WinMain函数实际上是通过调用AfxWinMain函数来完成它的功能的。 其中,Afx前缀的函数代表应用程序框架函数,辅助我们生成应用程序框架的应用模型。在MFC中,Afx为前缀的函数都是全局函数,可以在程序的任何位置调用。
在AfxWinMain函数的定义中,有程序3.4
1 2 3 4 5 6 7 8 9 10 11 12 13 CWinThread* pThread = AfxGetThread(); CWinApp* pApp = AfxGetApp(); if (pApp != NULL && !pApp->InitApplication()) goto InitFailure; if (!pThread->InitInstance()){} nReturnCode = pThread->Run();
3.2.1.3 InitInstance函数 见程序3.4的第九行。
3.2.2 MFC框架窗口 3.2.2.1 设计和注册窗口 窗口类的注册是由位于wincore.cpp的AfxEndDeferRegisterClass函数完成的。AfxEndDeferRegisterClass函数预定义了几种缺省的窗口类,首先判断窗口类的类型,再赋予其相应的类名。部分代码如书本p79所示。
接着调用AfxRegisterClass函数注册从窗口类,该函数首先获得窗口类信息,窗口已经注册,返回真。否则注册该窗口类。
注意:AfxRegisterClass实际上就是AfxEndDeferRegisterClass(宏定义);
3.2.2.2 创建窗口 窗口的创建是由CWnd类中的CreateEx函数完成的。定义:afxwin.h,实现:wincore.cpp。(以Ex结尾的函数表示扩展函数。)
CreateEx函数不是虚函数,CFrameWnd类的Create函数内调用的实际上就是CWnd类的CreatEx函数。CreateEx函数内部调用的PreCreateWindow函数是一个虚函数,在产生窗口之前有机会修改窗口外观。
3.2.2.3 显示和更新窗口 CTestApp中名为m_pMainWnd的成员变量保存了应用程序框架窗口(CMainFrame)对象的指针,在InitInstance函数(初始化工作:注册、显示、更新)内部:程序3.5
1 2 3 m_pMainWnd->ShowWindow(SW_SHOW); m_pMainWnd->UpdateWindow();
3.2.3 消息循环 见程序3.4的第12行。在thrdcore.cpp中/*消息循环*/ nReturnCode = pThread->Run();
书本p85例3-16,该函数主要结构是一个for循环,在收到WM_QUIT时退出。在循环中的PumpMessage()与第一章的SDK编程的消息处理代码一致。
3.2.4 MFC运行过程梳理
3.3 窗口类、窗口对象与窗口 3.3.1 三者之间的关系 :: 前面没有东西,表示所使用的函数是一个全局函数。如果当前定义的成员函数与内部调用的API函数重名,后者必须加 :: ,否则报错。
C窗口类对象与窗口并不是一回事,他们之间唯一的关系是C窗口类对象内部定义了一个窗口句柄变量,保存了与这个C窗口类对象相关的那个窗口的句柄。窗口销毁时,与之对应的C窗口类对象销毁与否要看其生命周期是否结束。但C++窗口类对象销毁时,与之相关的窗口也将销毁。
上段话用自己的话说:在窗口销毁后,CWnd的成员变量m_hWnd设为NULL,并没有被销毁(也有可能被销毁:对象生命周期结束(函数运行到右大括号“}”));而在C++窗口类对象析构时,窗口被销毁。
在系统文件afxwin.h中,CWnd已有一个用于保存句柄的成员变量m_hWnd,ShowWindow()和UpdateWindow()不需要再传递这个句柄,因为它已经是成员变量。程序3.6
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 class CWnd : public CCmdTarget{ DECLARE_DYNCREATE(CWnd) protected: static const MSG* PASCAL GetCurrentMessage () ; public: HWND m_hWnd; ...... }; int WINAPI WinMain ( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { ... ... CWnd wnd; wnd.CreateEx(...); wnd.ShowWindow(SW_SHOWNORMAL); wnd.UpdateWindow(); ... return 0 ; }
3.3.2 在窗口中显示按钮 CButton的Create函数声明:BOOL Create( LPCTSTR lpszCaption, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );
lpszCaption:按钮文本;
dwStyle:按钮风格+窗口风格;
rect:定义一个矩形区域;
pParentWnd:指定父窗口。MFC不再通过窗口句柄,而是通过一个与窗口相关的C++窗口类对象指针来传递窗口对象。
nID:按钮控件标识。可取整数随机值。在框架窗口产生之后,再创建该标识,否则没地方放置。
窗口创建时都会产生WM_Create消息,OnCreate函数用于相应这条窗口创建消息。
如果将按钮创建在CMainFrame::OnCreate()函数内,按钮的父窗口是主框架窗口,此时按钮遮挡住了保存等按钮。
改为在CTestView.cpp中创建button,首先在testView.cpp中创建OnCreate函数,步骤如问题及反思[3]所示。运行结果如下。
而将m_btn.Create()中的this改为GetParent(),运行结果变为
可见,按钮的位置与其父窗口有关,与创建它的代码所在的类无关。
将按钮窗口销毁,m_btn并没有销毁,因为m_btn是CTestView类的一个成员变量,其生命周期与CTestView对象一致。
课后程序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class CtestView : public CView{ ... private: CButton m_btn; }; int CtestView::OnCreate (LPCREATESTRUCT lpCreateStruct) { if (CView::OnCreate(lpCreateStruct) == -1 ) return -1 ; m_btn.Create(_T("button" ), WS_CHILD | BS_DEFPUSHBUTTON |WS_VISIBLE , CRect(0 , 0 , 100 , 100 ), this, 123 ); return 0 ; }
运行结果:
问题及反思 [1]如何利用vs2019创建MFC应用见参考文献[1]
[2]C2664 “BOOL CButton::Create(LPCTSTR,DWORD,const RECT &,CWnd *,UINT)”: 无法将参数 1 从“const char [7]”转换为“LPCTSTR” 解决方法: 方法1、”button”改为_T(“button”) 方法2、调试>>XXX调试属性>>配置属性>>高级>>高级属性>>字符集,改为:使用多字节字符集
[3]vs2019为一个类添加某函数的方法如下所示。第四步单击最右侧向下小三角,选择Add OnCreate。
参考文献 [1] <https://blog.csdn.net/m0_37062716/article/details/113827243?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control >.安装MFC,创建MFC工程文件
[2][https://blog.csdn.net/huijie4728/article/details/50487315](https://blog.csdn.net/huijie4728/article/details/50487315) . 问题及反思[2]
[3][https://blog.csdn.net/feilong911hao/article/details/39231533?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control](https://blog.csdn.net/feilong911hao/article/details/39231533?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control) . 问题及反思[2]
[4]孙鑫.VC++深度详解修订版[M]. 北京:电子工业出版社, 2012. 63-99.
第四章 4.1 MFC消息映射机制 4.1.1 classwizard 所有操作,包括鼠标点击鼠标移动等操作都只能由视类窗口捕获。因为视类窗口在框架窗口之上,如果在框架窗口操作。就会被视类窗口覆盖而看不见了。
删除通过向导添加的消息响应函数时,应在向导中删除。
4.1.2 消息映射机制 一个MFC消息响应函数在程序中有3处相关信息:
1、消息响应函数原型
1 2 3 4 5 6 protected: DECLARE_MESSAGE_MAP() public: afx_msg void OnLButtonDown (UINT nFlags, CPoint point) ;
2、消息映射宏
消息映射宏ON_WM_LBUTTONDOWN()的作用:把消息(WM_LBUTTONDOWN)与消息响应函数(OnLButtonDown)关联起来。
1 2 3 4 5 6 7 BEGIN_MESSAGE_MAP(CdrawView, CView) ON_COMMAND(ID_FILE_PRINT, &CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_DIRECT, &CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_PREVIEW, &CView::OnFilePrintPreview) ON_WM_LBUTTONDOWN() END_MESSAGE_MAP()
3、消息响应函数的定义
1 2 3 4 5 6 7 void CdrawView::OnLButtonDown (UINT nFlags, CPoint point) { MessageBox("click down" ); CView::OnLButtonDown(nFlags, point); }
MFC消息映射方式的具体实现:在每个能接受和处理消息的类中,定义一个消息和消息函数静态对照表,即消息映射表。在消息映射表中,消息和对应的消息处理函数指针成对出现。 某个类能处理的所有信息及其对应的消息处理函数地址都列在这个类对应的静态表中。当有消息需要处理时,程序只能搜索该消息静态表,查看表中是否含有该消息,就知道该类能否处理此消息。如果能处理,则同样依照静态表能很容易找到并调用对应的消息处理函数。
4.2 绘制线条 在基类中添加成员变量的原因:可以一直存在;而在子类中,程序运行到右},变量的生命周期就结束了。
1 2 3 4 class CdrawView : public CView{...private: CPoint m_ptOrigin; ...};
在构造函数中将m_ptOrigin赋0以初始化后,在消息响应函数中将鼠标按下的信息保存到基类的成员变量m_ptOrigin中
1 2 3 4 5 6 7 8 void CdrawView::OnLButtonDown (UINT nFlags, CPoint point) { m_ptOrigin = point; CView::OnLButtonDown(nFlags, point); }
最后添加WM_LButtonUp.
4.2.1 利用SDK全局函数实现划线功能 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void CdrawView::OnLButtonUp (UINT nFlags, CPoint point) { HDC hdc; hdc = ::GetDC(m_hWnd); MoveToEx(hdc , m_ptOrigin.x , m_ptOrigin.y , NULL ); LineTo(hdc , point.x , point.y ); ::ReleaseDC(m_hWnd , hdc); CView::OnLButtonUp(nFlags, point);
1、获取设备描述表(平台SDK的GetDC)
1 2 3 4 5 HDC GetDC ( HWND hWnd ) ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 HDC hdc; hdc = ::GetDC(m_hWnd); ::ReleaseDC(m_hWnd , hdc);
2、鼠标移动到线条的起点
1 2 3 4 5 6 7 8 BOOL MoveToEx ( HDC hdc, int x, int y, LPPOINT lppt ) ;MoveToEx(hdc , m_ptOrigin.x , m_ptOrigin.y , NULL );
3、划线
1 2 3 4 5 6 7 BOOL LineTo ( HDC hdc, int x, int y ) ;LineTo(hdc , point.x , point.y );
4.2.2 利用MFC的CDC类实现画图功能 MFC为我们提供了一个设备描述表的封装类CDC,该类封装了所有绘图操作。该类中的数据成员m_hDC保存与CDC类相关的DC句柄,因此不需要DC句柄作为参数,其作用类似m_hWnd。而平台SDK提供的全局TextOut()则不然。
1 2 3 4 5 6 7 8 9 10 11 void CdrawView::OnLButtonUp (UINT nFlags, CPoint point) { CDC* pDC = GetDC(); pDC->MoveTo(m_ptOrigin); pDC->LineTo(point); ReleaseDC(pDC); CView::OnLButtonUp(nFlags, point); }
此处的GetDC是CWnd()的而不是平台SDK的。对比4.2.1的GetDC
1 2 3 CWnd::GetDC CDC* GetDC ( ) ;
4.2.3 利用MFC的CClientDC类实现画图功能 CClientDC类派生于CDC类,并在构造函数时自动调用GetDC和ReleaseDC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void CdrawView::OnLButtonUp (UINT nFlags, CPoint point) { CClientDC dc (this) ; dc.MoveTo(m_ptOrigin); dc.LineTo(point); CView::OnLButtonUp(nFlags, point); }
4.2.4 利用MFC的CWindowDC类实现画图功能 CWindowDC类派生于CDC类,并在构造函数时自动调用GetDC和ReleaseDC ,调用方式与CClientDC一样。
不同于CClientDC的是,CWindowDC可以访问整个窗口区域,包括框架窗口的客户区(菜单栏以下)和非客户区(标题栏和菜单栏);而CClientDC最多只能到客户区。
1 2 3 4 5 6 7 8 9 10 void CdrawView::OnLButtonUp (UINT nFlags, CPoint point) { CWindowDC dc (GetDesktopWindow()) ; dc.MoveTo(m_ptOrigin); dc.LineTo(point); CView::OnLButtonUp(nFlags, point); }
4.2.5 在桌面窗口划线 见4.2.4。
4.2.6 绘制线条色彩 1 2 3 4 5 6 7 8 9 10 11 12 13 void CdrawView::OnLButtonUp (UINT nFlags, CPoint point) { CPen pen (PS_SOLID, 1 , RGB(255 , 0 , 0 )) ; CClientDC dc (this) ; CPen* pOldPen = dc.SelectObject(&pen); dc.MoveTo(m_ptOrigin); dc.LineTo(point); dc.SelectObject(pOldPen); CView::OnLButtonUp(nFlags, point); }
1、Cpen类创建画笔对象,该类封装了画笔的相关操作
CPen (int nPenStyle , int Width , COLORREF crColor);
三个参数分别代表线形、线宽和颜色。
颜色用RGB(r,g,b)表示,每个参数都是0~255之间。黑色全0,白色全255.
2、在程序中构造GDI对象后,该对象不会立即生效,必须(作用1)选入设备描述表 后,才会生效。SelectObject()
函数除了实现以上操作,还会(作用2)返回指向先前被选中对象的指针 ,以在完成绘画后,还原设备描述表。
小结:需要使用SelectObject()
选入设备描述表:CPen类、Rectangle函数
4.3 画刷 4.3.1 简单画刷 利用CBrush类创建画刷对象,以填充一块区域。
CBrush(COLORREF crColor )
1 2 3 4 5 6 7 8 9 void CdrawView::OnLButtonUp (UINT nFlags, CPoint point) { CBrush brush (RGB(255 , 0 , 0 )) ; CClientDC dc (this) ; dc.FillRect(CRect(m_ptOrigin, point), &brush); CView::OnLButtonUp(nFlags, point); }
1、FillRect函数:利用画刷填充鼠标拖拽过程中产生的矩形区域,包括左边和上部边界,不填充右边和底部边界。
void FillRect( LPCRECT lpRect, CBrush* pBrush );
1、lpRect
:指向一个RECT结构体或CRect对象指针。本例使用CRect()函数,即 CRect( POINT topLeft, POINT bottomRight );
第一个参数是左上角点,第二个参数是右下角点,以确定一个矩形。
2、pBrush
:指向用于填充矩形的画刷对象的指针。
4.3.2 位图画刷 CBrush类中有如下构造函数:CBrush (CBitmap* pBitMap);
其中,CBitmap类是位图类。创建CBitmap对象时,仅调用其构造函数,并不能得到有用的位图对象,还需要调用初始化函数来初始化该位图对象。本例中用加载位图的初始化函数:LoadBitmap(UINT nIDResource);
。其中,nIDResource
是资源ID号,在资源视图中查看,如图4.6所示。
1 2 3 4 5 6 7 8 9 10 11 void CdrawView::OnLButtonUp (UINT nFlags, CPoint point) { CBitmap bitmap; bitmap.LoadBitmap(IDB_BITMAP1); CBrush brush (&bitmap) ; CClientDC dc (this) ; dc.FillRect(CRect(m_ptOrigin , point) , &brush); CView::OnLButtonUp(nFlags, point); }
1、位图的创建过程如下所示。
_4EcJoeM6pn.png>)
2、资源ID在资源视图中查看
4.3.3 透明画刷 1 2 3 4 5 6 7 8 9 10 11 12 13 void CdrawView::OnLButtonUp (UINT nFlags, CPoint point) { CClientDC dc (this) ; CBrush* pBrush = CBrush::FromHandle( (HBRUSH) GetStockObject(NULL_BRUSH)); CBrush* pOldBrush = dc.SelectObject(pBrush); dc.Rectangle(CRect(m_ptOrigin, point)); dc.SelectObject(pBrush); CView::OnLButtonUp(nFlags, point); }
1、GetStockObject()函数,获取一个黑点或白色的画刷句柄
GetStockObject(NULL_BRUSH);获取一个空画刷
1 2 3 HGDIOBJ GetStockObject ( int fnObject ) ;
再利用强制类型转换,将HGDIOBJ类型转换为HBRUSH类型
1 (HBRUSH) GetStockObject(NULL_BRUSH);
2、FromHandle()函数将画刷句柄转化为画刷对象
1 2 3 4 5 6 7 8 9 static CBrush* PASCAL FromHandle ( HBRUSH hBrush ) ;
static静态成员函数 P126
(1)静态成员 函数和静态成员变量属于类本身 ,而不属于某一个变量,在类加载的时候,既为他们分配了空间 ,所以可以通过类名::函数名 或类名:变量名 来访问。而非静态成员函数和非静态成员变量属于对象的方法和数据,即应先产生类的对象,再通过类的对象去引用。
(2)在静态成员函数中是不能调用非静态成员的 ,包括非静态成员函数和非静态成员变量;静态成员函数只能访问静态成员变量 。而非静态成员函数中是可以调用静态成员函数的 。
(1)和(2)小结:也就是说,无论采用什么样的操作,代码都是在内存中运行的。程序只有在内存中占有一席之地,才能够访问它。
(3)对于静态成员变量,必须对其初始化 ,并且应在类的定义之外 .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <iostream> using namespace std ; class Point { public: static void init () ; { cout <<"x = " <<x<<endl ; } static int x; }; int Point::x = 0 ;void main () { Point::init(); }
4.4 绘制连续线条 为捕获鼠标移动过程的每一个点,可以通过捕获鼠标移动消息(WM_MouseMove)来实现。只要鼠标在程序窗口中移动,都会进入这个消息响应函数中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class CdrawView : public CView{ ... private: BOOL m_bdraw; }; void CdrawView::OnLButtonDown (UINT nFlags, CPoint point) { m_ptOrigin = point; m_bdraw = 1 ; CView::OnLButtonDown(nFlags, point); } void CdrawView::OnLButtonUp (UINT nFlags, CPoint point) { m_bdraw = 0 ; CView::OnLButtonUp(nFlags, point); } void CdrawView::OnMouseMove (UINT nFlags, CPoint point) { CClientDC dc (this) ; CPen pen (PS_SOLID , 1 , RGB(255 , 0 , 0 )) ; CPen* pOldPen = dc.SelectObject(&pen); if (m_bdraw == 1 ) { dc.MoveTo(m_ptOrigin); dc.LineTo(point); m_ptOrigin = point; dc.SelectObject(pOldPen); } CView::OnMouseMove(nFlags, point); }
4.5 绘制扇形效果线条 1 2 3 4 5 6 7 8 void CdrawView::OnMouseMove (UINT nFlags, CPoint point) { ... dc.LineTo(m_ptOld); m_ptOld = point; ... }
问题及反思 [1]位图创建过程见4.3.2
参考文献 [1]孙鑫.VC++深度详解修订版[M]. 北京:电子工业出版社, 2012. 100-134.
第五章 5.1 插入符 5.1.1 创建插入符 在窗口创建之后的WM_CREATE消息响应函数OnCreate中添加创建插入符代码:
void CreateSolidCaret(int nWidth , int nHeight);
若参数为0,则使用系统定义。
接着,使用ShowCaret();
显示插入符。
对于CreateSolidCaret()的两个参数,可以根据字体度量信息 自动调整插入符:
BOOL GetTextMetrics( LPTEXTMATRIC lpMetrics ) const;
其中,参数要求一个指向TEXTMETRIC结构体的指针,函数从DC中获取字体度量信息后填充进这个结构体。
经常用到的TEXTMETRIC结构体变量:
int tmHeight; //tmAscent+tmDescent
int tmAscent; //升序高度
int tmDescent; //降序高度
int tmAveCharWidth; //平均宽度
int tmMaxCharWidth; //最大字符宽度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int CtextView::OnCreate (LPCREATESTRUCT lpCreateStruct) { if (CView::OnCreate(lpCreateStruct) == -1 ) return -1 ; CClientDC dc (this) ; TEXTMETRIC tm; dc.GetTextMetrics(&tm); CreateSolidCaret(tm.tmAveCharWidth / 8 , tm.tmHeight); ShowCaret(); return 0 ; }
5.1.2 创建图形插入符 void CreateCaret(CBitmap* pBitmap);
创建图形插入符
1 2 3 4 5 6 7 8 9 10 11 12 13 int CtextView::OnCreate (LPCREATESTRUCT lpCreateStruct) { if (CView::OnCreate(lpCreateStruct) == -1 ) return -1 ; bitmap.LoadBitmap(IDB_BITMAP1); CreateCaret(&bitmap); ShowCaret(); return 0 ; }
5.2 窗口重绘 5.2.1 OnDraw函数 窗口重绘时,已输入的文字或图形就会被擦除。如想要保留,则需要WM_PAINT的消息响应函数OnCreate将内容再次输出(重绘)。
字符串类CString,其对象可以当作普通变量赋值、相加。
CDC类封装的TetOut(x,y,str);在指定位置显示字符串。
5.2.2 添加字符串资源 BOOL LoadString(UINT nID);
其中,nID为字符串资源标识,如下图所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void CtextView::OnDraw (CDC* pDC) { CtextDoc* pDc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return ; CString str; str = "VC++深入编程" ; pDC->TextOut(0 ,0 ,str); str.LoadString(IDS_STRINGVC); pDC->TextOut(0 ,50 ,str); }
5.3 路径层 1、步骤:
打开路径层:BeginPath
利用GDI提供的绘图函数绘图
关闭路径层:EndPath
2、CSize GetTextExtent(str)
:获取某个特定字符串的宽度和高度 ,返回CSize对象。和GetTextMetrics(字体度量)区分。
3、裁剪区域:可以理解为一个绘图区域,大小可以由我们来控制,以后的绘图操作仅限于这个矩形区域内。
CDC类提供了BOOL SelectClipPath(int nMod);
用于把当前设置的路径层和DC中已有的剪裁区域按照一定指定的模式进行一个互操作。
nMod是用于指定互操作的模式,如:
RGN_DIFF:新的剪裁区域包含当前剪裁区域,但排除当前路径层区域。
RGN_AND:剪裁区域和路径层的交集
小结:如果希望整幅图形中的某一部分与其他部分有所区别,就可以把这个部分的图形放置在一个路径层中,然后利用SelectClipPath函数设置一种模式,让路径层和剪裁区域进行互操作以达到一种特殊的效果。
5.4 字符输入 利用CString类对象可以直接字符输入。
输入字符,就会有WM_CHAR消息,在OnChar消息处理函数中写代码,说明要如何处理该消息。
设置插入符位置函数:CWnd类中的SetCaretPos(POINT point);
函数。将插入符移动到鼠标左键单击处。
清除字符可以直接用CString类中的Empty()
来实现。
1、回车处理(ASCII为0x0d)
插入符横坐标不变,纵坐标发生变化:当前插入点的纵坐标加上字体高度
1 2 3 4 5 6 if (0x0d == nChar){ m_strLine.Empty(); m_ptOrigin.y += tm.tmHeight; dc.TextOut(m_ptOrigin.x , m_ptOrigin.y , m_strLine); }
2、退格键(ASCII为0x08)
步骤:
设置文本颜色为背景色:virtual COLORREF SetTextColor( COLORREF crColor );
返回原来的颜色。CDC::GetBkColor()
获取背景色。
输出
在字符串变量中删除要删除的文字CString Left( int nCount ) const;
其中,nCount指字符串左边指定数目的字符。int GetLength( ) const;
获取长度。
还原文字颜色
输出
1 2 3 4 5 6 7 8 if (0x08 == nChar){ COLORREF clr = dc.SetTextColor(dc.GetBkColor()); dc.TextOut(m_ptOrigin.x , m_ptOrigin.y , str); m_strLine = m_strLine.Left(m_strLine.GetLength() - 1 ); dc.SetTextColor(clr); dc.TextOut(m_ptOrigin.x , m_ptOrigin.y , m_strLine); }
3、设置字体
1、创建字体对象;
2、CFont类设置字体
BOOL CreatePointFont( int nPointSize, LPCTSTR lpszFaceName, CDC* pDC = NULL );
(字体高度,字体名称,NULL)
3、将字体选入DC,SelectObject(CFont *font);
4、字幕变色功能
CDC::DrawText函数 在指定矩形范围内输出文字。
int DrawText(const CString& str, LPRECT lpRect, UINT nFormat );
(要输出的字符串,文字显示范围的矩形,文本的输出格式)
CWnd::SetTimer函数 定时器函数UINT SetTimer( UINT nIDEvent, UINT nElapse, void (CALLBACK EXPORT* lpfnTimer)( HWND, UINT, UINT, DWORD) );
UINT nIDEvent:非零定时器标识,函数调用成功,则返回标识;
UINT nElapse:以毫秒 为单位的时间间隔,即每隔多长时间发送一次定时器消息WM_TIMER 。程序在OnTimer函数中编写。
CALLBACK EXPORT* lpfnTimer:如果设置了该回调函数,则系统调用该回调函数处理消息。如果NULL,则窗口对象CWnd来处理
参考文献: [1]孙鑫.VC++深度详解修订版[M]. 北京:电子工业出版社, 2012. 135-161.
第六章 菜单 一、创建菜单项 资源管理器→menu项→双击IDR_MAINFRAME打开菜单编辑器
pop-up类型的菜单是弹出式菜单,不能进行命令响应。
ID号的命名规则:
IDM菜单资源
IDC光标资源
IDI图标资源
二、为菜单项添加响应函数 打开ClassWizard【类向导】,
区别Message中的COMMAND和UPDATE_COMMAND_UI COMMAND处理该菜单对应的功能 。 UPDATE_COMMAND_UI处理菜单应对的用户界面 。
菜单项响应顺序:view→doc→mainframe→App
Windows消息的分类 1、标准消息:除 WMCOMMAND 之外,**所有以 WM 开头的消息都是标准消息。 CWnd类,都可以接收到这类消息。 2、命令消息:来自菜单、加速键或工具栏按钮的消息。这类消息都 以 WM_COMMAND 形式呈现。 在 MFC 中,通过菜单项的标识(ID)来区分不同的命令消息;在 SDK 中,通过消息的wParam 参数识别。从 CCmdTarget 派生的类,都可以接收到这类消息。 3、通告消息:由控件产生的消息,例如按钮的单击、列表框的选择等。目的是为了向其父窗口(通常是对话框)通知事件的发生。这类消息也是 以 WM_COMMAND 形式呈现。从CCmdTarget 派生的类,都可以接收到这类消息。 实际上,CWnd 类派生于 CCmdlTarget类。也就是说 凡是从 CWnd 派生的类,它们既可以接收标准消息,也可以接收命令消息和通告消息。而对于那些从 CCmdTarget 派生的类,则只能接收命令消息和通告消息,不能接收标准消息。**
菜单消息响应过程:mainframe类接收消息→view首先处理,若不行→doc→mainframe→App
三、基本菜单操作 1、了解菜单结构
程序的主菜单属于框架窗口,因此要在框架类的OnCreate中编程。
2、标记菜单 获取程序菜单栏:CWnd类 CMenu* GetMenu( ) const;
获取子菜单:CMenu类CMenu* GetSubMenu( int nPos ) const;
参数是子菜单的索引号,注意分隔栏也占据索引号。
添加或移除标记:CMenu类UINT CheckMenuItem( UINT nIDCheckItem, UINT nCheck );
ParametersnIDCheckItem :Specifies the menu item to be checked, as determined by nCheck .nCheck_Specifies :how to check the menu item and how to determine the position of the item in the menu. The _nCheck parameter can be a combination of MF_CHECKED or MF_UNCHECKED with MF_BYPOSITION or MF_BYCOMMAND flags.
MF_BYCOMMAND 指定第一个参数通过ID
MF_BYPOSITION 指定第一个参数通过索引号
MF_CHECKED 设置复选标记
MF_UNCHECKED 移走复选标记
1 GetMenu()->GetSubMenu(0 )->CheckMenuItem(0 ,MF_BYPOSITION | MF_CHECKED);
3、默认菜单项(粗体) 注意子菜单只能有一个默认菜单项。
CMenu类BOOL SetDefaultItem( UINT uItem, BOOL fByPos = FALSE );
1 GetMenu()->GetSubMenu(0 )->SetDefaultItem(2 ,MF_BYPOSITION);
4、图形标记菜单 CMenu类:BOOL SetMenuItemBitmaps( UINT nPosition, UINT nFlags, const CBitmap* pBmpUnchecked, const CBitmap* pBmpChecked );
pBmpUnchecked是取消选中时的位图;pBmpchecked是选中状态时的位图。
获取系统的信息度量:int GetSystemMetrics( int nIndex);
其中,参数用于指定希望获取哪部分的系统信息。SM_CXMENUCHECK和SM_CYMENUCHECK用于获取菜单项标记图形的默认尺寸。
CString类的Format函数类似C中的printf。
1 2 3 4 5 6 7 8 CBitmap m_bitmap; m_bitmap.LoadBitmap(IDB_BITMAP1); GetMenu()->GetSubMenu(0 )->SetMenuItemBitmaps(0 , MF_BYPOSITION, &m_bitmap, &m_bitmap); Ctring str; str.Format("x = %d , y = %d" , GetSystemMetrics(SM_CXMENUCHECK), GetSystemMetrics(SM_CYMENUCHECK)); MessageBox(str);
5、禁用菜单项 CMenu类:UINT EnableMenuItem( UINT nIDEnableItem, UINT nEnable );
nEnable:
MF_BYCOMMAND Specifies that the parameter gives the command ID of the existing menu item. This is the default.
MF_BYPOSITION Specifies that the parameter gives the position of the existing menu item. The first item is at position 0.
MF_DISABLED Disables the menu item so that it cannot be selected but does not dim it.
MF_ENABLED Enables the menu item so that it can be selected and restores it from its dimmed state.
MF_GRAYED Disables the menu item so that it cannot be selected and dims it.
常把MF_GRAYED 和MF_DISABLED 放在一起使用。
发现不能正常禁用?
// NOTE: m_bAutoMenuEnable is set to FALSE in the constructor of
// CMainFrame so no ON_UPDATE_COMMAND_UI or ON_COMMAND handlers are
// needed, and CMenu::EnableMenuItem() will work as expected.
即在CMainFrame构造函数中添加m_bAutoMenuEnable = FALSE;
6、移除和装载菜单 CWnd类:BOOL SetMenu( CMenu* pMenu );
CMenu* pMenu 指向一个新的菜单对象,若值为NULL,则移除当前菜单。
1 2 3 4 5 6 7 8 9 SetMenu(NULL ); Cmenu menu; menu.LoadMenu(IDR_MAINFRAME); SetMenu(&menu); menu.Detach();
四、MFC菜单命令更新机制 菜单项状态的维护依赖于CN_UPDATE_COMMAND_UI消息,该消息智能用于菜单项,不能用于永久显示的顶级菜单(即弹出式菜单)。
在BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之间添加宏以捕获消息
1 2 3 4 BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) ON_WM_CREATE() ON_UPDATE_COMMAND_UI(ID_FILE_NEW, &CMainFrame::OnUpdateFileNew) END_MESSAGE_MAP()
1 2 3 4 5 6 7 void CMainFrame::OnUpdateEditCut (CCmdUI* pCmdUI) { pCmdUI->Enable(); }
如果只是利用禁用菜单项,菜单项变灰禁用,但工具栏按钮依然能够使用。而如果使用命令更新机制,则都不能使用。
五、快捷菜单 (上下文菜单、右键菜单)
为CView类添加一个WM_CONTEXTMENU消息处理函数,当鼠标右键单击窗口时,就会调用该函数。
显示快捷菜单函数: BOOL TrackPopupMenu( UINT nFlags, int x, int y, CWnd* pWnd, LPCRECT lpRect = NULL );
1 2 3 4 5 6 7 8 9 10 void CmenuView::OnContextMenu (CWnd* pWnd, CPoint point) { CMenu popmenu; popmenu.LoadMenu(IDR_POPMENU1); popmenu.GetSubMenu(0 )->TrackPopupMenu( TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_RIGHTBUTTON, point.x, point.y, this); }
六、动态菜单操作 1、针对子菜单的动态操作 1、把一个新的子菜单添加到 菜单栏末尾 。CMenu类:BOOL AppendMenu ( UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL);
CMenu类:CreateMenu()创建一个弹出菜单,并与CMenu对象关联
1 2 3 4 CMenu m_menu; m_menu.CreateMenu(); GetMenu()->AppendMenu(MF_POPUP, (UINT)m_menu.m_hMenu, "TEST" );
2、两个子菜单中间插入 子菜单。CMenu类: BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL );
用法类似AppendMenu
3、删除 子菜单或菜单项。CMenu类:BOOL DeleteMenu( UINT nPosition, UINT nFlags );
1 2 3 4 GetMenu()->DeleteMenu(0 ,MF_BYPOSITION ); GetMenu()->GetSubMenu(1 )->DeleteMenu(0 ,MF_BYPOSITION );
2、针对菜单项的动态操作 1、插入菜单项同样使用AppendMenu和InsertMenu
1 2 3 m_menu.AppendMenuA(MF_STRING, 111 , "hello" ); GetMenu()->GetSubMenu(0 )->AppendMenu(MF_STRING, 113 , "hello1" ); GetMenu()->GetSubMenu(0 )->InsertMenu(1 , MF_BYPOSITION | MF_STRING , 112 , "VC编程" );
3、为 动态添加的菜单项 添加响应函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #define IDM_HELLO 111 m_menu.AppendMenuA(MF_STRING, IDM_HELLO, "hello" ); protected: afx_msg int OnCreate (LPCREATESTRUCT lpCreateStruct) ; afx_msg void OnHello () ; DECLARE_MESSAGE_MAP() BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) ON_WM_CREATE() ON_COMMAND(IDM_HELLO,OnHello) END_MESSAGE_MAP() void CMainFrame::OnHello () { MessageBox("Hello" ); }
第七章 对话框(一) 1、基本知识 控件:通常作为对话框的子窗口创建
对话框的种类:
模态对话框:对话框显示时,程序暂停执行,直到关闭对话框。
非模态:显示对话框时,可以执行其他任务。
2、创建和显示 CDialog对话框资源类
1.新建一个MFC类与该对话框关联:双击新建的对话框。
新建对话框资源后有两个按钮,功能一样(关闭对话框),返回值不同。
OK IDOK OnOK
Cancel IDCANCEL OnCancel
2.**DoDataExchange()**
:完成对话框数据的交换和校验。
3.在视类中
CDialog::DoModal
创建并显示模态对话框
CDialog::EndDialog
关闭模态对话框
3、创建动态按钮 1.视类→工具箱→对话框编辑器 创建一个按钮。
2.为该按钮添加功能
类向导→选择该对象对应的BN_CLICKED(按钮被单击的消息)消息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 在CTestDlg类添加 private: CButton m_btn; BOOL isbtncreate; void CTestDlg::OnClickedBtnAdd () { if (isbtncreate == 0 ) { m_btn.Create(_T("new" ), WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, CRect(0 , 0 , 100 , 100 ), this, 123 ); isbtncreate = 1 ; } else { m_btn.DestroyWindow(); isbtncreate = 0 ; } }
初始化变量的两种方法
1.创建成员变量,并在构造函数中初始化;
2.创建静态局部变量同时初始化。
4、对话框控件 1.静态文本控件 IDC_STATIC不能响应消息,可通过修改ID来响应消息
CWnd::GetDlgItem
获取对话框子控件的指针
CWnd::GetWindowText
获取窗口文本
CWnd::SetWindowText
设置窗口文本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void CTestDlg::OnClickedNumber1 () { CString str; if (GetDlgItem(IDC_NUMBER1)->GetWindowText(str),str == "数值1" ) { GetDlgItem(IDC_NUMBER1)->SetWindowText("Number1" ); } else { GetDlgItem(IDC_NUMBER1)->SetWindowText("数值1" ); } }
最后在属性中选中通知(Notify)选项,否则不能向其父窗口发送鼠标事件。
2.编辑框控件 🌟GetDlgItem()获取编辑框框窗口对象的指针
1.法一:**GetDlgItem→Get(Set)WindowText**
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void CTestDlg::OnClickedBtnAdd () { int num1, num2, num3; char ch1[10 ], ch2[10 ], ch3[10 ]; GetDlgItem(IDC_EDIT1)->GetWindowText(ch1, 10 ); GetDlgItem(IDC_EDIT2)->GetWindowText(ch2, 10 ); num1 = atoi(ch1); num2 = atoi(ch2); num3 = num1 + num2; _itoa_s(num3, ch3, 10 ); GetDlgItem(IDC_EDIT3)->SetWindowText(ch3); }
2.法二:**Get(Set)DlgItemText**
=GetDlgItem+Get(Set)WindowText
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void CTestDlg::OnClickedBtnAdd () { int num1 = 0 , num2 = 0 , num3 = 0 ; char ch1[10 ], ch2[10 ], ch3[10 ]; GetDlgItemText(IDC_EDIT1, ch1,10 ); GetDlgItemText(IDC_EDIT2, ch2,10 ); num1 = atoi(ch1); num2 = atoi(ch2); num3 = num1 + num2; _itoa_s(num3, ch3, 10 ); SetDlgItemText(IDC_EDIT3, ch3); }
改写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 void CTestDlg::OnClickedBtnAdd () { int num1 = 0 , num2 = 0 , num3 = 0 ; CString str1, str2, str3; GetDlgItemText(IDC_EDIT1, str1); GetDlgItemText(IDC_EDIT2, str2); num1 = atoi(str1); num2 = atoi(str2); num3 = num1 + num2; str3.Format("%d" ,num3); SetDlgItemText(IDC_EDIT3, str3); }
3.CWnd::Get(Set)DlgItemInt
返回指定控件的文本并将其转换为一个整数值。
1 2 3 4 5 6 7 8 9 10 void CTestDlg::OnClickedBtnAdd () { int num1 = 0 , num2 = 0 , num3 = 0 ; num1 = GetDlgItemInt(IDC_EDIT1); num2 = GetDlgItemInt(IDC_EDIT2); num3 = num1 + num2; SetDlgItemInt(IDC_EDIT3, num3); }
4.控件与整型变量相关联(注意DoDataExchange和UpdateData一起用)
步骤:项目→类向导→成员变量→IDC_EDIT1→添加变量→类别(值)→变量名称→变量类型→其他(最大值、最小值等)
系统为我们创建的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 int m_num1; int m_num2; int m_num3; CTestDlg::CTestDlg(CWnd* pParent ) : CDialog(IDD_DIALOG1, pParent) , m_num1(0 ) , m_num2(0 ) , m_num3(0 ) { } void CTestDlg::DoDataExchange (CDataExchange* pDX) { CDialog::DoDataExchange(pDX); DDX_Text(pDX, IDC_EDIT1, m_num1); DDV_MinMaxInt(pDX, IDC_EDIT1, 0 , 100 ); DDX_Text(pDX, IDC_EDIT2, m_num2); DDX_Text(pDX, IDC_EDIT3, m_num3); }
自己编写的代码:
1 2 3 4 5 6 7 8 9 void CTestDlg::OnClickedBtnAdd () { UpdateData(); m_num3 = m_num1 + m_num2; UpdateData(FALSE); }
自动消息提示框:
5.控件与控件变量相关联
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 系统自动添加 CEdit m_edit1; CEdit m_edit2; CEdit m_edit3; void CTestDlg::DoDataExchange (CDataExchange* pDX) { CDialog::DoDataExchange(pDX); DDX_Control(pDX, IDC_EDIT1, m_edit1); DDX_Control(pDX, IDC_EDIT2, m_edit2); DDX_Control(pDX, IDC_EDIT3, m_edit3);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 手动添加 int num1 = 0 , num2 = 0 , num3 = 0 ; char ch1[10 ], ch2[10 ], ch3[10 ]; m_edit1.GetWindowText(ch1, 10 ); m_edit2.GetWindowText(ch2, 10 ); num1 = atoi(ch1); num2 = atoi(ch2); num3 = num1 + num2; _itoa_s(num3, ch3, 10 ); m_edit3.SetWindowText(ch3);
6.SendMessage()
发送消息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int num1 = 0 , num2 = 0 , num3 = 0 ; char ch1[10 ], ch2[10 ], ch3[10 ]; ::SendMessage(GetDlgItem(IDC_EDIT1)->m_hWnd, WM_GETTEXT, 10 , (LPARAM)ch1); ::SendMessage(m_edit2.m_hWnd, WM_GETTEXT, 10 , (LPARAM)ch2); num1 = atoi(ch1); num2 = atoi(ch2); num3 = num1 + num2; _itoa_s(num3, ch3, 10 ); m_edit3.SendMessage(WM_SETTEXT, 0 , (LPARAM)ch3);
7.SendDlgItemMessage()
直接给对话框的子控件发送消息
1 2 3 4 5 6 7 8 9 10 11 12 13 int num1 = 0 , num2 = 0 , num3 = 0 ;char ch1[10 ], ch2[10 ], ch3[10 ];SendDlgItemMessage(IDC_EDIT1, WM_GETTEXT, 10 , (LPARAM)ch1); SendDlgItemMessage(IDC_EDIT2, WM_GETTEXT, 10 , (LPARAM)ch2); num1 = atoi(ch1); num2 = atoi(ch2); num3 = num1 + num2; _itoa_s(num3, ch3, 10 ); SendDlgItemMessage(IDC_EDIT3, WM_SETTEXT, 0 , (LPARAM)ch3);
补充:
1 2 3 4 5 6 7 8 SendDlgItemMessage(IDC_EDIT3, EM_SETSEL, 1 , 3 ); m_edit3.SetFocus();
5、对话框的伸缩 CWnd::SetWindowPos BOOL SetWindowPos (const CWnd* pWndInsertAfter , int x , int y , int cx , int cy , UINT nFlags)// 设置窗口位置、大小、z次序等
CWnd::GetDlgItem This method retrieves a pointer to the specified control or child window in a dialog box or other window. The pointer returned is usually cast to the type of control identified by nID .CWnd GetDlgItem (int nID *)const;
CWnd::SetDlgItemText void SetDlgItemText(int nID , LPCTSTR lpszString);// 将nID中的文本设置为lpszString
CWnd::GetWindowRect void GetWindowRect(LPRECT lpRect)const;// 获得窗口尺寸
CRect::IsRectNull
CRect::IsRectEmpty
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 void CTestDlg::OnBnClickedButton1 () { CString str; if (GetDlgItemText(IDC_BUTTON1,str),str == "收缩<<" ) { SetDlgItemText(IDC_BUTTON1, "括展>>" ); } else { SetDlgItemText(IDC_BUTTON1, "收缩<<" ); } static CRect rectlarge; static CRect rectsmall; if (rectlarge.IsRectNull()) { CRect rectseparator; GetDlgItem(IDC_SEPARATOR)->GetWindowRect(rectseparator); GetWindowRect(rectlarge); rectsmall.top = rectlarge.top; rectsmall.left = rectlarge.left; rectsmall.right = rectlarge.right; rectsmall.bottom = rectseparator.bottom; } if (str == "收缩<<" ) { SetWindowPos(NULL , 0 , 0 , rectsmall.Width(), rectsmall.Height(), SWP_NOMOVE | SWP_NOZORDER); } else { SetWindowPos(NULL , 0 , 0 , rectlarge.Width(), rectlarge.Height(), SWP_NOMOVE | SWP_NOZORDER); } }
6、输入焦点的传递 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 void CTestDlg::OnBnClickedOk () { GetNextDlgTabItem(GetFocus())->SetFocus(); } WNDPROC prevproc; LRESULT CALLBACK WinEnterProc ( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { if (uMsg == WM_CHAR && wParam == 0x0d ) { ::SetFocus(::GetNextDlgTabItem(::GetParent(hwnd), hwnd, 0 )); } else { return prevproc(hwnd,uMsg,wParam,lParam); } } BOOL CTestDlg::OnInitDialog () CDialog::OnInitDialog () ; prevproc = (WNDPROC)SetWindowLong(GetDlgItem(IDC_EDIT1)->m_hWnd, GWL_WNDPROC, (LONG)WinEnterProc); return TRUE; }
7、默认按钮的说明 收缩按钮设置为默认按钮,即在其属性对话框中选择default button选项,就不会再由CTestDlg类的OnOK函数来响应。 当用户按下回车键时,windows将查看对话框中是否存在默认按钮, 如果没有默认按钮,就会调用虚拟的OnOK函数, 即没有默认ok按钮。这个默认OnOK函数的ID是IDOK。而不是IDC_OK.
第八章 对话框2 1、逃跑按钮 /逃跑按钮创建过程 1、创建新类(专注于逃跑功能)
存储地址的变量OnMouseMove() 2、CTestDlg.h控件与控件变量关联,即创建了新类的对象 3、CTestDlg.cpp的OnInitDialog()中两个对象存储对方首地址 4、鼠标移动OnMouseMove()接收WM_MOUSEMOVE消息,执行程序
/
2、属性表单的创建 一个属性表单是由一个或多个属性页组成,属性页对应MFC中的CPropertyPage类,一个属性页的标题就是选项卡的名称。
步骤:
1、创建属性页
2、添加控件
组框Group Box
单选按钮Radio Button
复选框Check Box
列表框控件List Box
静态文本控件Static Box
组合框Combo Box
simple,能输入,总是显示列表框
dropdown,能输入只有单击下拉列表后,列表框才弹出
droplist,只读,只能从下来列表选择内容
3、为对话框资源创建新类
1.创建新类步骤:参考1 ,在“添加MFC类”中添加类名与ID号一致,基类中选择相应的类型
属性页CPropertyPage
属性表单CPropertySheet
2.在属性表单(CPropSheet)中,创建三个属性页对象,并在构造函数中初始化。用AddPage将属性页添加进属性表单,构造函数指明了2中实例化对象的方法(ID/字符串)
CPropSheet::AddPage将属性页添加到属性表单中
OnPropertysheet::DoModal()创建模态属性表单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 PROP1 m_prop1; PROP2 m_prop2; PROP3 m_prop3; CPropSheet::CPropSheet(UINT nIDCaption, CWnd* pParentWnd, UINT iSelectPage) :CPropertySheet(nIDCaption, pParentWnd, iSelectPage) { AddPage(&m_prop1); AddPage(&m_prop2); AddPage(&m_prop3); } CPropSheet::CPropSheet(LPCTSTR pszCaption, CWnd* pParentWnd, UINT iSelectPage) :CPropertySheet(pszCaption, pParentWnd, iSelectPage) { AddPage(&m_prop1); AddPage(&m_prop2); AddPage(&m_prop3); }
3.在视类中创建菜单命令响应函数,用字符串实例化属性表单对象,并用DoModal创建模态属性表单
1 2 3 4 5 6 7 void CpropView::OnPropertysheet () { CPropSheet propsheet ("属性表单" ) ; propsheet.DoModal(); }
3、向导的创建 1、更改向导底部按钮CPropertySheet::SetWizardButtons
1 2 3 4 5 6 7 8 9 10 BOOL PROP1::OnSetActive () { CPropertySheet* psheet = (CPropertySheet*)GetParent(); psheet->SetWizardButtons(PSWIZB_NEXT); return CPropertyPage::OnSetActive(); } PROP2,PROP3过程类似,SetWizardButtons参数不同。
1、设置第一个属性页
将第一个单选按钮设置为Group属性以添加成员变量。第一个单选按钮设置为Group后,之后的按钮和这个按钮属于同一组,直到遇到下一个Group。同一组内的控件关联的成员变量的值依次为1、2、3…
单击【下一步】,调用OnWizardNext()虚函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 int m_occupation; PROP1::PROP1() : CPropertyPage(IDD_PROP1) , m_occupation(-1 ) { } void PROP1::DoDataExchange (CDataExchange* pDX) { CPropertyPage::DoDataExchange(pDX); DDX_Radio(pDX, IDC_RADIO1, m_occupation); } LRESULT PROP1::OnWizardNext () { UpdateData(); if (m_occupation == -1 ) { MessageBox("请选择职业!" ); return -1 ; } if (m_workaddress == "" ) { MessageBox("请选择工作地点!" ); return -1 ; } return CPropertyPage::OnWizardNext(); }
1 2 3 4 5 6 7 8 9 10 11 12 13 BOOL PROP1::OnInitDialog () { CPropertyPage::OnInitDialog(); CListBox* listbox = (CListBox*)GetDlgItem(IDC_LIST1); listbox->AddString("北京" ); listbox->AddString("天津" ); listbox->AddString("上海" ); return TRUE; }
2、设置第二个属性页
操作类似第一个属性页
1 2 3 4 5 6 7 8 9 10 11 12 13 14 LRESULT PROP2::OnWizardNext () { UpdateData(); if (m_football || m_basketball || m_volleyball || m_swim) { return CPropertyPage::OnWizardNext(); } else { MessageBox("请选择那你的兴趣爱好!" ); return -1 ; } }
3、设置第三个属性页
CComboBox::AddString向组合框的列表框中添加字符串选项
CComboBox::SetCurSel选择列表框中的一个字符串,并将其显示在该组合框的编辑框中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 BOOL PROP3::OnInitDialog () { CPropertyPage::OnInitDialog(); CComboBox* combobox = (CComboBox*)GetDlgItem(IDC_COMBO1); combobox->AddString("1000元以下" ); combobox->AddString("1000元-2000元" ); combobox->AddString("2000元-3000元" ); combobox->AddString("3000元以上" ); combobox->SetCurSel(0 ); return TRUE; }
CComboBox::GetCurSel得到当前选项的索引值
CComboBox::GetLBText得到对应索引值的文本并存储在字符串中
1 2 3 4 5 6 7 8 9 10 11 BOOL PROP3::OnWizardFinish () { int index; CComboBox* combobox = (CComboBox*)GetDlgItem(IDC_COMBO1); index = combobox ->GetCurSel(); combobox->GetLBText(index, m_strsalary); return CPropertyPage::OnWizardFinish(); }
4、将结果在视类显示
memset初始化数组
Invalidate();//让视类窗口无效,从而引起重绘,然后在OnDraw中完成信息的输
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 int m_ioccupation; CString m_strworkaddress; BOOL m_blike[4 ]; CString m_strsalary; CpropView::CpropView() noexcept { m_ioccupation = -1 ; m_strworkaddress = "" ; memset (m_blike , 0 , sizeof (m_blike)); m_strsalary = "" ; } void CpropView::OnPropertysheet () { CPropSheet propsheet ("属性表单" ) ; propsheet.SetWizardMode(); if (ID_WIZFINISH == propsheet.DoModal()) { m_ioccupation = propsheet.m_prop1.m_occupation; m_strworkaddress = propsheet.m_prop1.m_workaddress; m_blike[0 ] = propsheet.m_prop2.m_football; m_blike[1 ] = propsheet.m_prop2.m_basketball; m_blike[2 ] = propsheet.m_prop2.m_volleyball; m_blike[3 ] = propsheet.m_prop2.m_swim; m_strsalary = propsheet.m_prop3.m_strsalary; Invalidate(); } } void CpropView::OnDraw (CDC* pDC) { CpropDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return ; CFont font; font.CreatePointFont(100 , "宋体" ,pDC); CFont* pfont; pfont = pDC->SelectObject(&font); CString position; position = "你的职业:" ; switch (m_ioccupation) { case 0 : position += "程序员" ; break ; case 1 : position += "系统工程师" ; break ; case 2 : position += "项目经理" ; break ; } pDC->TextOut(0 , 0 , position); TEXTMETRIC tm; pDC->GetTextMetrics(&tm); CString workplace; workplace = "你的工作地点:" ; workplace += m_strworkaddress; pDC->TextOut(0 , tm.tmHeight, workplace); CString hobby; hobby = "你的爱好:" ; if (m_blike[0 ]) hobby += "足球 " ; if (m_blike[1 ]) hobby += "篮球 " ; if (m_blike[2 ]) hobby += "排球 " ; if (m_blike[3 ]) hobby += "游泳 " ; pDC->TextOut(0 , tm.tmHeight*2 , hobby); CString salary; salary = "你的薪资水平:" ; salary += m_strsalary; pDC->TextOut(0 , tm.tmHeight*3 , salary); pDC->SelectObject(pfont); }
第九章 定制应用程序外观 1.在工具栏上添加、删除按钮
创建按钮:按钮和菜单项用一样的ID
按钮之间的分隔符:用鼠标把按钮向右拖动一段距离
删除按钮:将按钮脱出工具栏
2.创建工具栏
分析系统创建的工具栏
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 cmainframe头文件中 CToolBar m_wndToolBar; cmainframe.cpp中 int CMainFrame::OnCreate (LPCREATESTRUCT lpCreateStruct) { ...... if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) || !m_wndToolBar.LoadToolBar(IDR_MAINFRAME)) { TRACE0("未能创建工具栏\n" ); return -1 ; } .... m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY); EnableDocking(CBRS_ALIGN_ANY); DockControlBar(&m_wndToolBar);
创建自定义工具栏(参照系统工具栏)
1.新建ToolBar资源
2.构造ToolBar对象
3.调用Create函数创建工具栏,并将其与ToolBar对象关联
4.LoadToolBar加载工具栏
5.设置停靠位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 头文件 CToolBar m_newtoolbar; 源文件 if (!m_newtoolbar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_RIGHT | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) || !m_newtoolbar.LoadToolBar(IDR_TOOLBAR1)) { TRACE0("未能创建工具栏\n" ); return -1 ; } m_newtoolbar.EnableDocking(CBRS_ALIGN_ANY); DockControlBar(&m_newtoolbar);
改进方法CFrameWnd::ShowControlBar
隐藏或显示指定的控制条
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 void CMainFrame::OnViewNewtoolbar () { ShowControlBar(&m_newtoolbar, !m_newtoolbar.IsWindowVisible(), 1 ); } void CMainFrame::OnUpdateViewNewtoolbar (CCmdUI* pCmdUI) { pCmdUI->SetCheck(m_newtoolbar.IsWindowVisible()); }
5、状态栏StatusBar 步骤:
1.在资源视图String Table中添加响应的ID
2.在源文件中将响应的ID添加进indicator(状态行指示器)中
系统创建的状态栏
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 CStatusBar m_wndStatusBar; static UINT indicators[] ={ ID_SEPARATOR, ID_INDICATOR_CAPS, ID_INDICATOR_NUM, ID_INDICATOR_SCRL, }; if (!m_wndStatusBar.Create(this)) { TRACE0("未能创建状态栏\n" ); return -1 ; } m_wndStatusBar.SetIndicators(indicators, sizeof (indicators)/sizeof (UINT));
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 在OnTimer()中添加 CTime time; CString str; CClientDC dc (this) ; CSize sz; int indicatorindex = 0 ; time = CTime::GetCurrentTime(); str = time.Format("%H:%M:%S" ); indicatorindex = m_wndStatusBar.CommandToIndex(IDS_TIMER); sz = dc.GetTextExtent(str); m_wndStatusBar.SetPaneInfo(indicatorindex, IDS_TIMER, SBPS_NORMAL, sz.cx); m_wndStatusBar.SetPaneText(indicatorindex, str);
6、进度栏 CProgressCtrl::Create//进度栏类CProgressCtrl BOOL Create( DWORD dwStyle , const RECT& rect , CWnd* pParentWnd , UINT nID);// 创建进度栏
CProgressCtrl::SetPos int SetPos( int nPos);// 设置进度栏当前进度
CStatusBar::GetItemRect void GetItemRect(int nIndex ,LPRECT lpRect)const;// 得到状态栏小窗格大小
🌟补充消息响应过程
1.头文件声明(定义)函数afx_msg LRESULT OnProgress(WPARAM, LPARAM);
2.消息与函数关联ON_MESSAGE(UM_PROGRESS, &OnProgress)
3.实现函数LRESULT CMainFrame::OnProgress(WPARAM wParam, LPARAM lParam)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void CMainFrame::OnPaint () { CPaintDC dc (this) ; CRect rect; m_wndStatusBar.GetItemRect(5 , &rect); if (!m_progress.m_hWnd) { m_progress.Create(WS_VISIBLE | WS_CHILD | PBS_SMOOTH, rect, &m_wndStatusBar, 123 ); } else { m_progress.MoveWindow(rect); } m_progress.SetPos(50 ); }
7、在状态栏上显示鼠标当前位置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void CstyleView::OnMouseMove (UINT nFlags, CPoint point) { CString str; str.Format("x = %d y = %d" ,point.x, point.y); ((CMainFrame*)GetParent())->m_wndStatusBar.SetWindowText(str); CView::OnMouseMove(nFlags, point); }
十、绘图控制 1、简单绘图 CPen pen
SelectObject
GetStockObject
FromHandle
SetPixel
Rectangle
Ellipse
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 private: UINT m_nDrawType; CPoint m_ptOrigin; CGraphicView::CGraphicView() noexcept { m_nDrawType = 0 ; m_ptOrigin = 0 ; } void CGraphicView::OnDot () { m_nDrawType = 1 ; } void CGraphicView::OnLine () { m_nDrawType = 2 ; } void CGraphicView::OnRectangle () { m_nDrawType = 3 ; } void CGraphicView::OnEllipse () { m_nDrawType = 4 ; } void CGraphicView::OnLButtonDown (UINT nFlags, CPoint point) { m_ptOrigin = point; CView::OnLButtonDown(nFlags, point); } void CGraphicView::OnLButtonUp (UINT nFlags, CPoint point) { CClientDC dc (this) ; CPen pen (PS_SOLID, 10 , RGB(255 ,0 ,0 )) ; dc.SelectObject(&pen); CBrush* pBrush = CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH)); dc.SelectObject(pBrush); switch (m_nDrawType) { case 1 : dc.SetPixel(point, RGB(255 ,0 ,0 )); break ; case 2 : dc.MoveTo(m_ptOrigin); dc.LineTo(point); break ; case 3 : dc.Rectangle(CRect(m_ptOrigin,point)); break ; case 4 : dc.Ellipse(CRect(m_ptOrigin, point)); default : break ; } CView::OnLButtonUp(nFlags, point); }
注意将CDC对象加入设备miaoshub;
注意静态成员函数的调用方式:要声明是哪个类的。
2、线宽线型(设置对话框) 1、将设置对话框创建一个新的对话框类
2、在主菜单添加设置菜单项并设置消息响应函数
3、控件关联无符号整型变量,View类创建同类型变量以保存其值
1 2 3 4 5 6 7 8 9 10 11 12 13 void CGraphicView::OnSetting () { CSettingDlg dlg; dlg.m_nLineWidth = m_nLineWidth; dlg.m_nLineStyle = m_nLineStyle; if (IDOK == dlg.DoModal()) { m_nLineWidth = dlg.m_nLineWidth; m_nLineStyle = dlg.m_nLineStyle; } }
3、颜色对话框 CColorDialog默认颜色对话框类了解(m_cc、CHOOSECOLOR、rgbResult、Flags (CC_RGBINIT))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void CGraphicView::OnColor () { CColorDialog colordlg; colordlg.m_cc.Flags |= CC_RGBINIT; colordlg.m_cc.rgbResult = m_ctr; if (IDOK == colordlg.DoModal()) { m_ctr = colordlg.m_cc.rgbResult; } }
4、字体对话框 DeleteObject释放这个字体资源
CFont::CreateFontIndirect利用lpLogFont指向的LOGFONT结构体中的一些特征初始化CFont对象
Invalidate()引起窗口重绘
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 头文件 CFont m_font; CString m_strFontName; 源文件 void CGraphicView::OnFont () { CFontDialog fontdlg; if (IDOK == fontdlg.DoModal()) { if (m_font.m_hObject) { m_font.DeleteObject(); } m_font.CreateFontIndirect(fontdlg.m_cf.lpLogFont); m_strFontName = fontdlg.m_cf.lpLogFont->lfFaceName; Invalidate(); } }
5、示例对话框 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 void CSettingDlg::OnChangeLineWidth () { Invalidate(); } void CSettingDlg::OnClickedRadio1 () { Invalidate(); } void CSettingDlg::OnClickedRadio2 () { Invalidate(); } void CSettingDlg::OnClickedRadio3 () { Invalidate(); } void CSettingDlg::OnPaint () { CPaintDC dc (this) ; UpdateData(); CPen pen (m_nLineStyle, m_nLineWidth, m_clr) ; dc.SelectObject(&pen); CRect rect; GetDlgItem(IDC_SAMPLE)->GetWindowRect(&rect); ScreenToClient(&rect); dc.MoveTo(rect.left+20 ,rect.top+rect.Height()/2 ); dc.LineTo(rect.right - 20 , rect.top + rect.Height() / 2 ); }
6、改变对话框及其控件的背景和文本WM_CTLCOLOR 1、改变对话框及其控件的背景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 CBrush m_brush; m_clr = RGB(255 ,0 ,0 ); m_brush.CreateSolidBrush(RGB(0 ,0 ,255 )); HBRUSH CSettingDlg::OnCtlColor (CDC* pDC, CWnd* pWnd, UINT nCtlColor) { HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor); if (IDC_LINE_STYLE == pWnd->GetDlgCtrlID()) { pDC->SetTextColor(RGB(255 ,0 ,0 )); pDC->SetBkMode(TRANSPARENT); return m_brush; } if (IDC_LINE_WIDTH == pWnd->GetDlgCtrlID()) { pDC->SetTextColor(RGB(255 , 0 , 0 )); pDC->SetBkColor(RGB(255 , 0 , 0 )); return m_brush; } return hbr; }
2、改变控件的文本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 private: CFont m_font; m_font.CreatePointFont(200 ,"华文行楷" ); HBRUSH CSettingDlg::OnCtlColor (CDC* pDC, CWnd* pWnd, UINT nCtlColor) { HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor); if (IDC_TEXT == pWnd->GetDlgCtrlID()) { pDC->SelectObject(&m_font); } return hbr; }
3、改变按钮的背景色及文本
7、位图的显示 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 BOOL CGraphicView::OnEraseBkgnd (CDC* pDC) { CBitmap bitmap; bitmap.LoadBitmap(IDB_BITMAP1); BITMAP bmp; bitmap.GetBitmap(&bmp); CDC dcCompatible; dcCompatible.CreateCompatibleDC(pDC); dcCompatible.SelectObject(&bitmap); CRect rect; GetClientRect(&rect); pDC->StretchBlt(0 , 0 , rect.Width(), rect.Height(), &dcCompatible, 0 , 0 ,bmp.bmWidth,bmp.bmHeight, SRCCOPY); return TRUE; }
//构造函数初始化