当前位置:   article > 正文

孙鑫VC++深入详解

孙鑫vc++

前言

3年前的学习笔记,学到第十章,有私信说想看,故转载到CSDN。
[原文](https://www.yuque.com/he-zexian/reading_note/ll86qc0tkazz?singleDoc# 《孙鑫VC++深入详解》)

第一章 Windows程序内部运行机制

1.1 API和SDK

  • API:Windows操作系统提供给应用程序编程的接口。
  • SDK(软件开发包):用于开发的所有资源的集合。

1.2 窗口和句柄

  • 窗口
  • 句柄:系统在创建资源时会为他们分配内存,并返回这些资源的标识号,即句柄。(类似于指针)
    窗口句柄(HWND)、图标(HICON)、光标(HCURSOR)、画刷(HBRUSH)

1.3 消息和队列

消息(先进先出)

typedef struct tagMSG {
  HWND   hwnd;           //HWND:窗口变量,hwnd:消息所属的窗口
  UINT   message;        //message消息标识符,数值定义为WM_XXX  (P4)
  WPARAM wParam; 
  LPARAM lParam;         //指定消息的附加信息,ASCII等
  DWORD  time;           //消息投递到消息队列中的时间
  POINT  pt;             //当前鼠标的位置
} MSG, *PMSG;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

1.4 WinMain

  • Win32实现的步骤
    1. 定义WinMain函数;
    2. 创建窗口;
    3. 消息循环;
    4. 窗口过程函数。

1.4.1 WinMain函数的定义

winmain函数是程序入口函数,由系统调用

int WINAPI WinMain(
  HINSTANCE hInstance,      // handle to current instance
  HINSTANCE hPrevInstance,  // handle to previous instance,always NULL in Win32
  LPSTR lpCmdLine,          // 空终止的字符串,指定传递给应用程序的命令行参数
  int nCmdShow              // 指定窗口应该如何显示
  );
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

1.4.2 窗口的创建

  • 步骤
    1. 设计窗口类;
    2. 注册窗口;
    3. 创建窗口;
    4. 显示及更新窗口;
1.4.2.1 设计窗口类
typedef struct _WNDCLASS                //窗口类
{ 
    UINT       style;                      /*(P7知识点方框)窗口样式;
                                             CS_XXX(都只有一位是1,且1位各不相同:位标志);
                                             多种特点组合用(|)号;
                                             去掉style中的样式用(&~)*/
    WNDPROC    lpfnWndProc;             //指向回调函数的指针;在特定的事件发生时,用于对该事件的响应
    int        cbClsExtra;               
    int        cbWndExtra;              //cbClsExtra和cbWndExtra两个附加内存,一般都为0
    HINSTANCE  hInstance;               //实例句柄
    HICON      hIcon;                  //图标句柄;HICON LoadIcon(HINSTANCE hInstance, LPCTSTR lpIconName);
    HCURSOR    hCursor;                  //光标句柄;HCURSOR LoadCursor(HINSTANCE hInstance,LPCTSTR lpCursorName);
    HBRUSH     hbrBackground;        //画刷句柄,背景颜色;wndcls.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    LPCTSTR    lpszMenuName;           //菜单名字,NULL
    LPCTSTR    lpszClassName;             //类名,和创建窗口CreateWindow函数中的lpClassName一致
} WNDCLASS, *PWNDCLASS;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
1.4.2.2 注册窗口
ATOM RegisterClass(  CONST WNDCLASS *lpWndClass ); // 注册窗口
  • 1
1.4.2.3 创建窗口
HWND CreateWindow(  
  LPCTSTR lpClassName,  // registered class name,与窗口类WNDCLASS中的lpszClassName一致
  LPCTSTR lpWindowName, // window name,
  DWORD dwStyle,        /*window style;和WNDCLASS中的style不同,style是指定具体窗口的样式,dwstyle是窗口都具有的样式
                        常用WS_OVERLAPPEDWINDOW*/
  int x,                // horizontal position of window
  int y,                // vertical position of window
  int nWidth,           // window width
  int nHeight,          // window height;
                        /*如果x设为CW_USEDEFAULT,系统为窗口选择左上角并忽略y;
                        nWidth设为CW_USEDEFAULT,系统为默认大小并忽略nHeight。
                        窗口之间的父子关系,子窗口必须有WS_CHILD*/
  HWND hWndParent,      // handle to parent or owner window(父窗口句柄);NULL
  HMENU hMenu,          // menu handle or child identifier;NULL
  HINSTANCE hInstance,  // handle to application instance;hInstance
  LPVOID lpParam        // window-creation data;NULL
);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

注意:创建窗口成功,函数返回系统为该窗口分配的句柄,否则返回NULL。创建窗口之前应先定义一个窗口句柄变量来接收创建窗口之后返回的句柄值。

1.4.2.4 显示及更新窗口

显示窗口

BOOL ShowWindow(
  HWND hWnd,     // 哪一个窗口?
  int nCmdShow   // 如何显示?;常用SW_SHOWNORMAL
);
  • 1
  • 2
  • 3
  • 4

更新窗口

BOOL UpdateWindow(
    HWND hWnd   // handle to window
);
  • 1
  • 2
  • 3

1.4.3 消息循环

BOOL GetMessage(
  LPMSG lpMsg,         // 指向一个消息结构体MSG,GetMessage取出的消息放入该结构体对象中
  HWND hWnd,           // handle to window,设为NULL接收所用窗口
  UINT wMsgFilterMin,  // first message
  UINT wMsgFilterMax   // last message;范围过滤,获得所有消息则设为0
);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

注意:GetMessage函数除了接收到WM_QUIT(退出消息)外都返回非零值,出错返回-1。

通常编写的消息循环代码如下:

MSG msg;//声明消息结构体变量
while(GetMessage(&msg,NULL,0,0))//没接收到WM_QUIT时一致循环
{
    TranslateMessage(&msg);//将WM_KEYDOWN和WM_KEYUP转化为WM_CHAR,不修改原消息
    DispatchMessage(&msg);//将消息发送至操作系统,后者用窗口过程函数对消息响应; Dispatch:派遣
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

IMG_20210623_120844.jpg

1.4.2窗口创建和1.4.3消息循环都包含在WinMain函数体内

1.4.4 窗口过程函数

回调函数

LRESULT CALLBACK WindowProc(
  HWND hwnd,      // 窗口句柄
  UINT uMsg,      // 消息代码
  WPARAM wParam,  // first message parameter
  LPARAM lParam   // second message parameter
);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  1. 过程函数名WindowProc可以换,但要和声明保持一致。
  2. 使用switch/case来对不同消息作出不同反应。

如下方例子中的过程函数所示。

课后程序

#define _CRT_SECURE_NO_DEPRECATE
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <windows.h>
#include <stdexcept>

/*******************函数声明****************************/
LRESULT CALLBACK WinSunProc(
  HWND hwnd,      // handle to window
  UINT uMsg,      // message identifier
  WPARAM wParam,  // first message parameter
  LPARAM lParam   // second message parameter
);

/*******************WinMain函数*************************/
int WINAPI WinMain(
  HINSTANCE hInstance,      // handle to current instance
  HINSTANCE hPrevInstance,  // handle to previous instance
  LPSTR lpCmdLine,          // command line
  int nCmdShow              // show state
)
{
  /*设计一个窗口类*/
  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);//将WM_KEYDOWN和WM_KEYUP转化为WM_CHAR,不修改原消息
    DispatchMessage(&msg);//将消息发送至操作系统,后者用窗口过程函数对消息响应
  }
  return msg.wParam;

}

/*******************窗口过程函数*************************/
LRESULT CALLBACK WinSunProc(
  HWND hwnd,      // handle to window
  UINT uMsg,      // message identifier
  WPARAM wParam,  // first message parameter
  LPARAM lParam   // second message parameter
)
{
  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;                    //要在窗口中输出文字或显示图形,要用到设备描述表(DC)。DC是一个包含设备信息的结构体,所有的图形操作都是利用DC来完成。
                                //定义类型为HDC的变量hdc
        hdc = GetDC(hwnd);          //GetDC返回与特定窗口相关联的DC句柄
    TextOut(hdc, 0, 50, "何泽贤之家",strlen("何泽贤之家"));
    ReleaseDC(hwnd, hdc);          //在使用完GetDC后一定要注意释放
    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);//WM_PAINT、BeginPaint、EndPaint一起用,而不能用GetDC
    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
  • 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

补充:
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 结构体的定义

  1. C++相比于C的特性:封装性、继承性、多态性;
  2. 对象具有状态和行为,状态保存在成员变量中,行为通过函数实现;
  3. 标准输入输出流对象:cin(>>)默认键盘 和 cout(<<)、cerr(<<)默认显示器;自动根据数据类型调整输入输出格式;
  4. 结构体中的函数称为成员函数。

程序2.1

#include <iostream>
using namespace std;   //error C1083: 无法打开包括文件:“iostream.h”: No such file or directory

struct point
{
  int x, y;           //状态
  void output()       //行为
  {
    cout << "x="<< x << endl << "y=" << y << endl;//endl是换行符
  }
}pt;//注意分号

int main()
{
  cout << "请输入x和y的值:" << endl;
  cin >> pt.x;
  cin >> pt.y;
  pt.output();    //注意括号
  return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

2.1.2 结构体与类

在C++中,结构体(struct)和类(class)可以通用。
区别在于访问控制的差异:

  1. struct默认访问控制标识符public;public:可以在类的外部进行访问
  2. class默认访问控制标识符private;private:只能在类的内部进行访问
  3. protective。

程序2.2:将程序2.1中的struct point修改为class point

class point   //类:抽象出一些事物共有的属性
{
public:
  int x, y;
  void output()
  {
    cout << "x="<< x << endl << "y=" << y << endl;
  }
}pt;   //实例化了一个对象==类的实例化:具有具体的属性值
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

2.2 C++的特性

2.2.1 类与对象

类:抽象出一些事物共有的属性;
对象:有具体的属性值。

2.2.2 构造函数

作用:在定义对象的同时,对成员变量进行初始化;
创建对象本身(分配内存)
规定:构造函数的名字和类名相同(唯一性);
没用返回值;
可以有参数。
注意:

  1. 如果一个类没有定义任何构造函数,那么C会提供一个默认的不带参构造函数。而只要类中定义了一个构造函数,C便不再提供任何其他构造函数;
  2. 每个类必须有一个构造函数,没有构造函数不能创建任何对象。构造函数如代码2.3所示。

2.2.3 析构函数

~类名(); //对象生命周期结束,释放其占用资源;是一个对象最后调用的成员函数
注意:析构函数不允许有返回值,更不允许有参数,并且一个类中只有一个构造函数。
代码2.3:构造函数和析构函数

class point       //类名:point
{
public:
  int x , y;
    point()      //构造函数  point()
    {
        x = 0;    //在类中定义成员变量时,不能直接给其赋值(如int x=0;),而是在构造函数中赋值(P37提示)
        y = 0;
    }
    ~point()    //析构函数   ~point()
    {
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

2.2.4 函数的重载

条件:函数参数类型、个数不同才能构成重载。
注意:

  1. 只有函数的返回值类型不同,不能重载;
  2. 重载时,注意函数带有默认参数的情况。(P38)
  3. 对比覆盖:重载是发生在同一个类当中;覆盖是发生在父类和子类之间

代码2.4 函数的重载

class point
{
public:
  int x , y;
    point()      //函数1
    {
        x = 0;
        y = 0;
    }
    point(int a, int b)    //重载,函数2
    {
        x = a;
        y = b;
    }    
};

void main()
{
    point pt(5,5);   //C++根据参数类型和个数,执行函数2
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

2.2.5 this指针

  1. this指针是一个隐含的指针,指向对象本身,代表对象的地址。
  2. 用法:当形参的变量名和成员变量的变量名冲突时,可用this->来区别对哪一个变量进行赋值。(P40)

this->x是对成员变量进行赋值;
x是对形参赋值。

2.2.6 类的继承

2.2.6.1 继承

例如:class fish : public animal{};

  1. animal是父类,fish是子类。子类fish以public(公有)的方式继承父类animal。
  2. 子类除了自己的成员变量和成员方法外,还可以继承父类的成员变量和成员方法。
  3. 构造函数和析构函数的调用次序:

image.png

2.2.6.2 在子类中调用父类带参数的构造函数

例如:父类构造函数: animal(int h , int w){}
则在构造子类时,应该显式地去调用父类的带参数构造函数: fish():animal(400,300){}

2.2.6.3 类的继承及类中成员的访问特性

3种访问权限修饰符:
public:定义的成员可以在任何地方被访问;
protected:定义的成员只能在该类及其子类中访问;
private:定义的成员只能在该类自身中访问。==>不能被子类继承

3种继承方式:

QQ图片20210627115117.jpg

2.2.6.4 多重继承

定义形式:class B: public C , public D
了解父类表顺序对调用构造函数和析构函数的影响。对于上面的例子,先构造C再构造D,先析构D再析构C。

2.2.7 虚函数与多态性、纯虚函数

2.2.7.1虚函数与多态性

**程序 2.5 **

(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;  //指向animal类的指针pan
  fish fi;
  pan = &fi;    //将fish类的对象fi的地址直接赋给指向animal类的指针变量pan
  breathetest(pan);
}

/*输出animal breathe*/
  • 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

1、fish对象也是一个animal类,C++自动进行类型转换;反之,不能把animal对象看成fish对象。

2、当我们将fish转化为animal时,该对象就会被认为是原对象内存模型的上半部分。

IMG_20210627_153537.jpg

3、将父类函数修改为虚函数,输出结果为fish bubble。

(P49)...
    virtual void breathe()    //虚函数
    ...
/*输出fish bubble*/
  • 1
  • 2
  • 3
  • 4

虚函数:用virtual关键字申明的函数–>多态性;
迟邦定:编译时并不确定具体调用的函数,而是在运行时依据对象的类型来确认调用的是哪一个函数。

概括(用法):在父类的函数前加上virtual关键字,在子类重写该函数,运行时将会根据对象的实际类型来调用相应的函数。

2.2.7.2 纯虚函数
  1. 纯虚函数不被具体实现,是抽象类,不能实例化对象。
  2. 纯虚函数可以让类先具有一个操作名称,而没有操作内容,在子类继承时再去具体定义。

写法:

virtual void breathe() = 0 ;    //1、等于0;2、无函数体
  • 1

注意:子类如果有对父类虚函数的覆盖定义,无论该覆盖定义是否有virtual,都是虚函数。

2.2.8函数的覆盖和隐藏

2.2.8.1 覆盖

1、覆盖(P52):发生在父类和子类之间 的 函数完全一样(函数名、参数列表),编译器根据实际类型确定要调用的函数。
2、对比重载:重载是发生在同一个类当中;覆盖是发生在父类和子类之间

程序2.6 函数的覆盖

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();
}
/*输出fish bubble*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
2.2.8.2 隐藏

两种父类函数隐藏的情况:

  1. 子类和父类函数完全相同(函数名、参数列表),但父类函数没有virtual,则父类函数被隐藏。
  2. 子类和父类函数同名,但参数列表不同,则父类函数被隐藏。

区别隐藏与覆盖:覆盖是发生在父类与子类之间,两个函数必须完全相同且都是虚函数。否则就是隐藏。

2.2.9 引用

定义形式:

int a;
int &b = a;  //引用必须在申明时就初始化,与a绑定;后面不会再与其他变量绑定
  • 1
  • 2

注意:

  1. 引用只是一个别名,不占用内存空间;此处要和指针区分开来。
  2. 引用多用于函数的形参定义。如程序2.4所示。

程序2.7 引用示例

#include <iostream>
using namespace std;                //换成引用意思表达更清晰:

/*以下为用指针交换数据*/
void change(int* a,int* b);         //void change(int& a,int& b);

int main()
{
  int x = 5, y = 3;
  cout << "x=" << x << endl;
  cout << "y=" << y << endl;
  change(&x,&y);                //是对x和y的地址互换还是对x和y互换?     change(x,y);
  cout << "x=" << x << endl;
  cout << "y=" << y << endl;
  return 0;
}

void change(int* a, int* b)        //void change(int a,int b)
{                                   //{
  int c;                            // int c;
  c = *a;                          //  c = a;
  *a = *b;                          // a = b;
  *b = c;                           // b = c;
}                                   //}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

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 条件预处理指令解决类重复定义的问题

#ifndef ANIMAL_H_H     //如果没有定义ANIMAL_H_H,就定义ANIMAL_H_H,接着申明animal类
                       //如果定义过了ANIMAL_H_H,直接跳至endif
#define ANIMAL_H_H

class animal
{
};

#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

2.2.11 VC++程序编译连接的原理与过程

1、编译:头文件不参与编译
源文件单独编译
XXX.obj目标文件
2、链接

编译链接过程如下。

IMG_20210627_171323.jpg

课后程序

animal.h

/*animal.h*/

#ifndef ANIMAL_H_H     //如果没有定义ANIMAL_H_H,就定义ANIMAL_H_H,接着申明animal类
                       //如果定义过了ANIMAL_H_H,直接跳至endif
#define ANIMAL_H_H

class animal
{
public:
  animal();
  ~animal();
  void eat();
  void sleep();
  virtual void breathe();
};

#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

animal.cpp

/*animal.cpp*/

#include <iostream>
#include "animal.h"

using namespace std;

animal::animal()
{
}

animal::~animal()
{
}

void animal::eat()
{
}

void animal::sleep()
{
}

void animal::breathe()      //在头文件中有virtual,则在源文件中不用加virtual
{
  cout << "animal breathe" << endl;
}
  • 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

fish.h

/*fish.h*/

#include "animal.h"  //fish类从animal类继承

#ifndef FISH_H_H

#define FISH_H_H
class fish :public animal     //结构体、类名后面没有括号()
{
public:
  void breathe();
};

#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

fish.cpp

/*fish.cpp*/

#include <iostream>
#include "fish.h"

using namespace std;

void fish::breathe()
{
  cout << "fish bubble" << endl;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

main.cpp

/*main.cpp*/

#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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

运行结果

image(1).png

问题及反思

vs2019编程出现的问题:
1、建立C++空项目后,要在右侧工程的源文件的文件夹下创建XXX.cpp文件;

否则编译时无法启动程序XXX.exe,系统找不到指定文件。

2、头文件#include <iostream.h>后添加using namespace std;
否则报错:error C1083: 无法打开包括文件:“iostream.h”: No such file or directory
3、定义结构体或类时,别忘记最后的分号;结构体名或类名后面没有小括号;
4、引用成员函数别忘记括号:pt.output();
5、.sln为项目文件

image(2).png

image(3).png

参考文献

[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单文档应用程序

屏幕截图 2021-06-28 101949.jpg

[2]开启类视图窗口

image(4).png

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

extern "C" int WINAPI
_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,     //右键_tWinMain,单击转到定义:#define _tWinMain   WinMain
  _In_ LPTSTR lpCmdLine, int nCmdShow)
#pragma warning(suppress: 4985)
{
  // call shared/exported WinMain
  return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
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全局对象

/*test.cpp*/
// 唯一的 CtestApp 对象
CtestApp theApp;    //theApp是CtestApp的一个对象,注意其是一个全局对象

/*test.h*/
class CtestApp : public CWinApp   //CtestApp继承于CWinApp,后者表示应用程序类
{
......
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

文件路径(在安装路径下直接搜索MFC,找到mfc):D:\Program Files (x86)\visualstudio\VC\Tools\MSVC\14.29.30037\atlmfc\src,打开appcore.cpp 查找CWinApp(184行)其中,
程序3.2

CWinApp::CWinApp(LPCTSTR lpszAppName)  //注意此处有参数
{ ...   
pModuleState->m_pCurrentWinApp = this; //此处this代表子类CTestApp的对象,即theApp
    ...
}
  • 1
  • 2
  • 3
  • 4
  • 5

程序3.3

class CWinApp : public CWinThread
{...
  explicit CWinApp(LPCTSTR lpszAppName = NULL);     // app name defaults to EXE name;构造函数形参有默认值默认值
    ...
};
  • 1
  • 2
  • 3
  • 4
  • 5

补充:如果某个函数的参数有默认值,那么在调用该函数时可以传参,也可以不传参直接使用默认值。

由程序3.3可见,来CWinApp类的定义时,CWinApp的构造函数的形参有默认值NULL。因此,在调用CWinApp类的构造函数时,不用显式地传参。

3.2.1.2 AfxWinMain函数

WinMain函数实际上是通过调用AfxWinMain函数来完成它的功能的。
其中,Afx前缀的函数代表应用程序框架函数,辅助我们生成应用程序框架的应用模型。在MFC中,Afx为前缀的函数都是全局函数,可以在程序的任何位置调用。

在AfxWinMain函数的定义中,有
程序3.4

/*pThread和pApp这两个指针是一致的,这两个指针都指向CTestApp类的对象,即theApp全局对象*/  
    CWinThread* pThread = AfxGetThread();
  CWinApp* pApp = AfxGetApp();

/*MFC内部管理所调用的函数*/
  if (pApp != NULL && !pApp->InitApplication())
    goto InitFailure;

/*调用的是子类InitInstance():因为在父类CWinApp中的InitInstance()是虚函数*/
  if (!pThread->InitInstance()){}

/*消息循环*/
  nReturnCode = pThread->Run();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
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

// 唯一的一个窗口已初始化,因此显示它并对其进行更新
  m_pMainWnd->ShowWindow(SW_SHOW);//显示
  m_pMainWnd->UpdateWindow();//更新
  • 1
  • 2
  • 3

3.2.3 消息循环

见程序3.4的第12行。在thrdcore.cpp中/*消息循环*/ nReturnCode = pThread->Run();

书本p85例3-16,该函数主要结构是一个for循环,在收到WM_QUIT时退出。在循环中的PumpMessage()与第一章的SDK编程的消息处理代码一致。

3.2.4 MFC运行过程梳理

IMG_20210628_164535.jpg

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

/*afxwin.h*/
class CWnd : public CCmdTarget
{
  DECLARE_DYNCREATE(CWnd)
protected:
  static const MSG* PASCAL GetCurrentMessage();

// Attributes
public:
  HWND m_hWnd;            // must be first data member
    ......
    };


/*lesson3:\\wainmain.cpp*/
int WINAPI WinMain(  HINSTANCE hInstance,      // handle to current instance
  HINSTANCE hPrevInstance,  // handle to previous instance
  LPSTR lpCmdLine,          // command line
  int nCmdShow              // show state
)
{
    //设计窗口类
    ...
    //注册窗口类
    ...
        
    //创建窗口类
    CWnd wnd;
    wnd.CreateEx(...);
    wnd.ShowWindow(SW_SHOWNORMAL);
    wnd.UpdateWindow();
    
    /*
    对比第一章:
    创建窗口
  HWND hwnd;
  hwnd = CreateWindowEx();

  显示及刷新窗口
  ::ShowWindow(hwnd, SW_SHOWNORMAL);
  ::UpdateWindow(hwnd);
    
    注意ShowWindow和UpdateWindow的参数,原因是:CWnd类定义过了一个HWND类型的成员变量m_hWnd用于保存这个窗口的句柄,
    在调用CWnd类中的ShowWindow显示窗口时,就不在需要传递这个句柄了,因为它已经是成员变量了,该函数可以直接使用它。
    
    在窗口销毁后,CWnd的成员变量m_hWnd设为NULL,并没有被销毁;而在C++窗口类对象析构时,窗口被销毁。
    */
    
    //消息循环
     ...   
         
       return 0; 
}
  • 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

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()函数内,按钮的父窗口是主框架窗口,此时按钮遮挡住了保存等按钮。

image(5).png

改为在CTestView.cpp中创建button,首先在testView.cpp中创建OnCreate函数,步骤如问题及反思[3]所示。运行结果如下。

屏幕截图 2021-06-29 103602.jpg

而将m_btn.Create()中的this改为GetParent(),运行结果变为

image(6).png

可见,按钮的位置与其父窗口有关,与创建它的代码所在的类无关。

将按钮窗口销毁,m_btn并没有销毁,因为m_btn是CTestView类的一个成员变量,其生命周期与CTestView对象一致。

课后程序

/*testview.h*/
class CtestView : public CView
{
...
private:
  CButton m_btn;  //在定义类的成员变量时都以"m_"为前缀,表明这个变量是类的成员变量
};

/*testview.cpp*/
int CtestView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
  if (CView::OnCreate(lpCreateStruct) == -1)
    return -1;

  /*CButton的Create函数声明:BOOL Create( LPCTSTR lpszCaption, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );*/

//  m_btn.Create(_T("button"), WS_CHILD | BS_DEFPUSHBUTTON, CRect(0, 0, 100, 100), this, 123);
//  m_btn.ShowWindow(SW_SHOWNORMAL);//窗口显示

  m_btn.Create(_T("button"), WS_CHILD | BS_DEFPUSHBUTTON |WS_VISIBLE , CRect(0, 0, 100, 100), this, 123);
//  m_btn.Create(_T("button"), WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, CRect(0, 0, 100, 100), GetParent(), 123);
  
    /*
  "按钮":名称; CRect(0,0,100,100):矩形区域; 123:ID号。
  *
  WS_CHILD(窗口风格):The window is a child window. A window with this style cannot have a menu bar.
  BS_DEFPUSHBUTTON(按钮风格):下按按钮风格
  WS_VISIBLE:The window is initially visible.
  *
  this指针(代表对象本身)
  GetParent():Call this function to get a pointer to a child window's parent window
  */

  /*
  C2664  “BOOL CButton::Create(LPCTSTR,DWORD,const RECT &,CWnd *,UINT)”: 无法将参数 1 从“const char [7]”转换为“LPCTSTR”  test  E:\VCProject\Lesson3\test\test\MainFrm.cpp  68

  解决方法:
  方法1、"button"改为_T("button")[2]
  方法2、调试>>XXX调试属性>>配置属性>>高级>>高级属性>>字符集,改为:使用多字节字符集[3]
  */
  return 0;
}
  • 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

运行结果:

屏幕截图 2021-06-29 103602(1).jpg

问题及反思

[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。

屏幕截图 2021-06-29 103208.jpg

参考文献

[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> . 问题及反思[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> . 问题及反思[2]

[4]孙鑫.VC++深度详解修订版[M]. 北京:电子工业出版社, 2012. 63-99.


第四章

4.1 MFC消息映射机制

4.1.1 classwizard

所有操作,包括鼠标点击鼠标移动等操作都只能由视类窗口捕获。因为视类窗口在框架窗口之上,如果在框架窗口操作。就会被视类窗口覆盖而看不见了。

删除通过向导添加的消息响应函数时,应在向导中删除。

4.1.2 消息映射机制

一个MFC消息响应函数在程序中有3处相关信息:

1、消息响应函数原型

/*drawView.h*/
// 生成的消息映射函数
protected:
  DECLARE_MESSAGE_MAP()
public:
  afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2、消息映射宏

消息映射宏ON_WM_LBUTTONDOWN()的作用:把消息(WM_LBUTTONDOWN)与消息响应函数(OnLButtonDown)关联起来。

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()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

3、消息响应函数的定义

/*drawView.cpp*/
void CdrawView::OnLButtonDown(UINT nFlags, CPoint point)
{
  // TODO: 在此添加消息处理程序代码和/或调用默认值
  MessageBox("click down");
  CView::OnLButtonDown(nFlags, point);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

MFC消息映射方式的具体实现:在每个能接受和处理消息的类中,定义一个消息和消息函数静态对照表,即消息映射表。**在消息映射表中,消息和对应的消息处理函数指针成对出现。**某个类能处理的所有信息及其对应的消息处理函数地址都列在这个类对应的静态表中。当有消息需要处理时,程序只能搜索该消息静态表,查看表中是否含有该消息,就知道该类能否处理此消息。如果能处理,则同样依照静态表能很容易找到并调用对应的消息处理函数。

4.2 绘制线条

在基类中添加成员变量的原因:可以一直存在;而在子类中,程序运行到右},变量的生命周期就结束了。

class CdrawView : public CView{...
private:
  CPoint m_ptOrigin;
  ...};
  • 1
  • 2
  • 3
  • 4

在构造函数中将m_ptOrigin赋0以初始化后,在消息响应函数中将鼠标按下的信息保存到基类的成员变量m_ptOrigin中

void CdrawView::OnLButtonDown(UINT nFlags, CPoint point)
{//CPoint point接收按下鼠标左键的位置
  m_ptOrigin = point;
  /*保存该位置到基类的成员变量;
  **当前的鼠标坐标也就是划线的起点坐标。
  */
  CView::OnLButtonDown(nFlags, point);
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

最后添加WM_LButtonUp.

4.2.1 利用SDK全局函数实现划线功能

//具体实现如下:
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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

1、获取设备描述表(平台SDK的GetDC)

//字典
//平台SDK的GetDC
HDC GetDC(
  HWND hWnd
);
  • 1
  • 2
  • 3
  • 4
  • 5

//本例
HDC hdc;//HDC:A handle to a device context (DC).
/*device context (DC):There are four types of DCs: display, 
printer, memory (or compatible), and information. 
Each type serves a specific purpose*/

hdc = ::GetDC(m_hWnd);
/*用自己的话说(暂时的理解是),HWND hWnd就是当前窗口,HDC hdc就是用来画画的,
GetDC()就是把当前窗口中用来画画的一部分(DC)取出来赋给hdc变量,
意思是接下来的画的东西都会保存在hdc上,也就是保存在了hWnd当前窗口的DC上
*/ 

//注意最后释放 
::ReleaseDC(m_hWnd , hdc);//将m_hWnd窗口的hdc释放
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

2、鼠标移动到线条的起点

BOOL MoveToEx(
  HDC     hdc, //设备描述表句柄
  int     x,
  int     y, //起点坐标
  LPPOINT lppt //指向point结构体的指针,保存移动操作前鼠标的位置坐标
);

MoveToEx(hdc , m_ptOrigin.x , m_ptOrigin.y , NULL);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

3、划线

BOOL LineTo(
  HDC hdc,
  int x,
  int y//终点坐标
);

LineTo(hdc , point.x , point.y );
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

4.2.2 利用MFC的CDC类实现画图功能

MFC为我们提供了一个设备描述表的封装类CDC,该类封装了所有绘图操作。该类中的数据成员m_hDC保存与CDC类相关的DC句柄,因此不需要DC句柄作为参数,其作用类似m_hWnd。而平台SDK提供的全局TextOut()则不然。

void CdrawView::OnLButtonUp(UINT nFlags, CPoint point)
{
  CDC* pDC = GetDC(); //此处的GetDC是CWnd()的而不是平台SDK的。
  pDC->MoveTo(m_ptOrigin);//从什么位置
  /*pDC是指向CDC类的指针,MoveTo()是CDC类的函数,所以用指向符->*/
  /*如果是CDC类的对象的话就用“.”号*/
  pDC->LineTo(point);//画线到什么位置
  ReleaseDC(pDC);

  CView::OnLButtonUp(nFlags, point);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

此处的GetDC是CWnd()的而不是平台SDK的。对比4.2.1的GetDC

//字典
CWnd::GetDC 
CDC* GetDC( );
  • 1
  • 2
  • 3

4.2.3 利用MFC的CClientDC类实现画图功能

CClientDC类派生于CDC类,并在构造函数时自动调用GetDC和ReleaseDC

void CdrawView::OnLButtonUp(UINT nFlags, CPoint point)
{
  CClientDC dc(this);
/*CClientDC dc(CWnd* pWnd);
**CWnd* pWnd是指向窗口的指针,此处需要指向视类窗口对象,即CdrawView。
**在CdrawView::OnLButtonUp(...)中使用this指向CdrawView自己。
*/

/*DetParent()返回父类窗口的指针*/

  dc.MoveTo(m_ptOrigin);
  dc.LineTo(point);
/*这里CClientDC类型的变量dc是一个对象,因此使用点操作符来调用该对象的函数;
**4.2.2中,pDC是指向CDC类的指针,MoveTo()是CDC类的函数,所以用指向符->  
*/
  
  CView::OnLButtonUp(nFlags, point);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

4.2.4 利用MFC的CWindowDC类实现画图功能

CWindowDC类派生于CDC类,并在构造函数时自动调用GetDC和ReleaseDC,调用方式与CClientDC一样。

不同于CClientDC的是,CWindowDC可以访问整个窗口区域,包括框架窗口的客户区(菜单栏以下)和非客户区(标题栏和菜单栏);而CClientDC最多只能到客户区。

void CdrawView::OnLButtonUp(UINT nFlags, CPoint point)
{
  CWindowDC dc(GetDesktopWindow());//整个桌面窗口
  //CWindowDC dc(GetParent());//框架窗口,包括客户区和非客户区
  //CWindowDC dc(this);//视类窗口
  dc.MoveTo(m_ptOrigin);
  dc.LineTo(point);
  
  CView::OnLButtonUp(nFlags, point);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

4.2.5 在桌面窗口划线

见4.2.4。

4.2.6 绘制线条色彩

void CdrawView::OnLButtonUp(UINT nFlags, CPoint point)
{
  CPen pen(PS_SOLID, 1, RGB(255, 0, 0));
  CClientDC dc(this);
  //只创建CPen不会生效,还要将其选入设备描述表
  CPen* pOldPen = dc.SelectObject(&pen);
  dc.MoveTo(m_ptOrigin);
  dc.LineTo(point);
  //还原设备描述表
  dc.SelectObject(pOldPen);

  CView::OnLButtonUp(nFlags, point);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

图4.2 绘制彩色线条

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 )

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

图4.3 红色简单画刷

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所示。

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

图4.3 位图画刷运行结果

1、位图的创建过程如下所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图4.5 位图的创建

2、资源ID在资源视图中查看

图4.6 查看资源ID

4.3.3 透明画刷

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

图4.7 透明画刷

1、GetStockObject()函数,获取一个黑点或白色的画刷句柄

GetStockObject(NULL_BRUSH);获取一个空画刷

HGDIOBJ GetStockObject(
  int fnObject   // stock object type
);
  • 1
  • 2
  • 3

再利用强制类型转换,将HGDIOBJ类型转换为HBRUSH类型

(HBRUSH) GetStockObject(NULL_BRUSH);
  • 1

2、FromHandle()函数将画刷句柄转化为画刷对象

static CBrush* PASCAL FromHandle( HBRUSH hBrush );
/*Parameters(参数)
hBrush 
Specifies the HANDLE to a Windows CE GDI brush. 
*/

/*Return Value
A pointer to a CBrush object if successful; otherwise, it is NULL
 */
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
static静态成员函数

P126

(1)静态成员函数和静态成员变量属于类本身,而不属于某一个变量,在类加载的时候,既为他们分配了空间,所以可以通过类名::函数名类名:变量名来访问。而非静态成员函数和非静态成员变量属于对象的方法和数据,即应先产生类的对象,再通过类的对象去引用。

(2)在静态成员函数中是不能调用非静态成员的,包括非静态成员函数和非静态成员变量;静态成员函数只能访问静态成员变量。而非静态成员函数中是可以调用静态成员函数的

(1)和(2)小结:也就是说,无论采用什么样的操作,代码都是在内存中运行的。程序只有在内存中占有一席之地,才能够访问它。

(3)对于静态成员变量,必须对其初始化,并且应在类的定义之外.

#include <iostream>
using namespace std;

class Point
{
public:
  static void init();//静态成员函数
  {
    cout<<"x = "<<x<<endl;
  }
  static int x;//静态成员变量
}; 

int Point::x = 0;//知识点(3):1初始化2在类外初始化

void main()
{
  Point::init();//知识点(1):用类名::函数名来直接调用
} 

/*结果输出x = 0*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

4.4 绘制连续线条

为捕获鼠标移动过程的每一个点,可以通过捕获鼠标移动消息(WM_MouseMove)来实现。只要鼠标在程序窗口中移动,都会进入这个消息响应函数中。

/*drawView.h*/
class CdrawView : public CView
{
...
private:
  BOOL m_bdraw;//判断鼠标左键是否按下去,按下为真,否则为假
  };

/*drawView.cpp*/
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);
}
  • 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

图4.8 连续绘图运行结果

4.5 绘制扇形效果线条

void CdrawView::OnMouseMove(UINT nFlags, CPoint point)
{
  ...
    dc.LineTo(m_ptOld);//表示鼠标移动过程中的连线中,每个极小的线段
    //m_ptOrigin = point;//修改起点信息
    m_ptOld = point;//m_ptOld用于保存鼠标上一个移动点
    ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

图4.9 带边扇形

问题及反思

[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; //最大字符宽度
int CtextView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
  if (CView::OnCreate(lpCreateStruct) == -1)
    return -1;

  // TODO:  在此添加您专用的创建代码
  CClientDC dc(this);
  TEXTMETRIC tm;
  dc.GetTextMetrics(&tm);
  CreateSolidCaret(tm.tmAveCharWidth / 8 , tm.tmHeight); //除以8是经验值
  ShowCaret();

  return 0;
}//创建插入符
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

5.1.2 创建图形插入符

void CreateCaret(CBitmap* pBitmap);创建图形插入符

int CtextView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
  if (CView::OnCreate(lpCreateStruct) == -1)
    return -1;

  // TODO:  在此添加您专用的创建代码
//CBitmap对象在基类CView中创建
  bitmap.LoadBitmap(IDB_BITMAP1);
  CreateCaret(&bitmap);
  ShowCaret();

  return 0;
}//位图插入符
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

5.2 窗口重绘

5.2.1 OnDraw函数

窗口重绘时,已输入的文字或图形就会被擦除。如想要保留,则需要WM_PAINT的消息响应函数OnCreate将内容再次输出(重绘)。

字符串类CString,其对象可以当作普通变量赋值、相加。

CDC类封装的TetOut(x,y,str);在指定位置显示字符串。

5.2.2 添加字符串资源

BOOL LoadString(UINT nID);

其中,nID为字符串资源标识,如下图所示。

void CtextView::OnDraw(CDC* pDC)
{
  CtextDoc* pDc = GetDocument();
  ASSERT_VALID(pDoc);
  if (!pDoc)
    return;

  // TODO: 在此处为本机数据添加绘制代码
  CString str;
  str = "VC++深入编程";
  pDC->TextOut(0,0,str);

  str.LoadString(IDS_STRINGVC);
  pDC->TextOut(0,50,str);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

5.3 路径层

1、步骤:

  1. 打开路径层:BeginPath
  2. 利用GDI提供的绘图函数绘图
  3. 关闭路径层: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)

插入符横坐标不变,纵坐标发生变化:当前插入点的纵坐标加上字体高度

if(0x0d == nChar)
{
  m_strLine.Empty();//清空当前字符串
  m_ptOrigin.y += tm.tmHeight;//移动插入点
  dc.TextOut(m_ptOrigin.x , m_ptOrigin.y , m_strLine);//在新的插入点输出
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2、退格键(ASCII为0x08)

步骤:

  1. 设置文本颜色为背景色:virtual COLORREF SetTextColor( COLORREF crColor );返回原来的颜色。CDC::GetBkColor()获取背景色。
  2. 输出
  3. 在字符串变量中删除要删除的文字CString Left( int nCount ) const;其中,nCount指字符串左边指定数目的字符。int GetLength( ) const;获取长度。
  4. 还原文字颜色
  5. 输出
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);//输出
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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【类向导】,

  1. 区别Message中的COMMAND和UPDATE_COMMAND_UI
    COMMAND处理该菜单对应的功能
    UPDATE_COMMAND_UI处理菜单应对的用户界面
  2. 菜单项响应顺序:view→doc→mainframe→App
  3. Windows消息的分类
    1、标准消息:除 WM_COMMAND 之外,所有以 WM_开头的消息都是标准消息。
    CWnd类,都可以接收到这类消息。
    2、命令消息:来自菜单、加速键或工具栏按钮的消息。这类消息都以 WM_COMMAND 形式呈现
    在 MFC 中,通过菜单项的标识(ID)来区分不同的命令消息;在 SDK 中,通过消息的wParam 参数识别。从 CCmdTarget 派生的类,都可以接收到这类消息。
    3、通告消息:由控件产生的消息,例如按钮的单击、列表框的选择等。目的是为了向其父窗口(通常是对话框)通知事件的发生。这类消息也是以 WM_COMMAND 形式呈现。从CCmdTarget 派生的类,都可以接收到这类消息。
    实际上,CWnd 类派生于 CCmdlTarget类。也就是说凡是从 CWnd 派生的类,它们既可以接收标准消息,也可以接收命令消息和通告消息。而对于那些从 CCmdTarget 派生的类,则只能接收命令消息和通告消息,不能接收标准消息。
  4. 菜单消息响应过程: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 );

Parameters
nIDCheckItem: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 移走复选标记
GetMenu()->GetSubMenu(0)->CheckMenuItem(0,MF_BYPOSITION | MF_CHECKED);
  • 1

3、默认菜单项(粗体)

注意子菜单只能有一个默认菜单项。

CMenu类BOOL SetDefaultItem( UINT uItem, BOOL fByPos = FALSE );

GetMenu()->GetSubMenu(0)->SetDefaultItem(2,MF_BYPOSITION);
  • 1

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。

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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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_GRAYEDMF_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,则移除当前菜单。

/*移除菜单*/
SetMenu(NULL);

/*装载菜单资源*/
Cmenu menu;//1、框架类成员变量2、若是局部变量,结尾一定要加Detach函数

menu.LoadMenu(IDR_MAINFRAME);
SetMenu(&menu);
menu.Detach();//menu是局部变量的情况,将菜单句柄与菜单对象分离
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

四、MFC菜单命令更新机制

菜单项状态的维护依赖于CN_UPDATE_COMMAND_UI消息,该消息智能用于菜单项,不能用于永久显示的顶级菜单(即弹出式菜单)。

在BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之间添加宏以捕获消息

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)//自动添加的
  ON_WM_CREATE()
  ON_UPDATE_COMMAND_UI(ID_FILE_NEW, &CMainFrame::OnUpdateFileNew)
END_MESSAGE_MAP()
  • 1
  • 2
  • 3
  • 4
void CMainFrame::OnUpdateEditCut(CCmdUI* pCmdUI)//消息响应函数
{
  // TODO: 在此添加命令更新用户界面处理程序代码
  pCmdUI->Enable();
}
/*CCmdUI类决定一个菜单项是否可以使用(Enable),是否有标记(SetCheck),改变菜单项文本(SetText)*/
/*CCmdUI类有成员变量m_nID保存对象ID和m_nIndex保存对象索引*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

如果只是利用禁用菜单项,菜单项变灰禁用,但工具栏按钮依然能够使用。而如果使用命令更新机制,则都不能使用。

五、快捷菜单

(上下文菜单、右键菜单)

为CView类添加一个WM_CONTEXTMENU消息处理函数,当鼠标右键单击窗口时,就会调用该函数。

显示快捷菜单函数: BOOL TrackPopupMenu( UINT nFlags, int x, int y, CWnd* pWnd, LPCRECT lpRect = NULL );

void CmenuView::OnContextMenu(CWnd* pWnd, CPoint point)
{
  // TODO: 在此处添加消息处理程序代码
  CMenu popmenu;
  popmenu.LoadMenu(IDR_POPMENU1); //读取资源
  popmenu.GetSubMenu(0)->TrackPopupMenu(
  TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_RIGHTBUTTON, point.x, point.y, this);
}
/*
TPM_LEFTALIGN定位弹出菜单,使其左侧与 x 指定的坐标对齐。*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

六、动态菜单操作

1、针对子菜单的动态操作

1、把一个新的子菜单添加到菜单栏末尾。CMenu类:BOOL AppendMenu ( UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL);

CMenu类:CreateMenu()创建一个弹出菜单,并与CMenu对象关联

 CMenu m_menu;//成员变量
 
 m_menu.CreateMenu();
 GetMenu()->AppendMenu(MF_POPUP, (UINT)m_menu.m_hMenu, "TEST");
  • 1
  • 2
  • 3
  • 4

2、两个子菜单中间插入子菜单。CMenu类: BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL );

用法类似AppendMenu

3、删除子菜单或菜单项。CMenu类:BOOL DeleteMenu( UINT nPosition, UINT nFlags );

//删除子菜单
  GetMenu()->DeleteMenu(0,MF_BYPOSITION );
//删除菜单项
GetMenu()->GetSubMenu(1)->DeleteMenu(0,MF_BYPOSITION );
  • 1
  • 2
  • 3
  • 4

2、针对菜单项的动态操作

1、插入菜单项同样使用AppendMenu和InsertMenu

  m_menu.AppendMenuA(MF_STRING, 111, "hello");//往弹出菜单TEST中添加菜单项
  GetMenu()->GetSubMenu(0)->AppendMenu(MF_STRING, 113, "hello1");//往文件子菜单末尾添加菜单项
  GetMenu()->GetSubMenu(0)->InsertMenu(1, MF_BYPOSITION | MF_STRING , 112 , "VC编程");
  • 1
  • 2
  • 3

3、为 动态添加的菜单项 添加响应函数

//在头文件resource.h中创建菜单资源ID
#define  IDM_HELLO   111

//源文件中
m_menu.AppendMenuA(MF_STRING, IDM_HELLO, "hello");

//遵循消息映射机制:增加3处代码实现消息响应
//1、在响应这个菜单命令的类中添加响应函数原型,位于声明消息映射宏之上,(第11行所示)
protected:
  afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
  afx_msg void OnHello();
  DECLARE_MESSAGE_MAP()
  
//2、在源文件的消息映射表中添加消息映射,消息映射宏是ON_COMMAND宏,(第17行所示)
//注意:消息映射表中,代码结尾不加分号
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
  ON_WM_CREATE()
  ON_COMMAND(IDM_HELLO,OnHello)
END_MESSAGE_MAP()

//3、添加消息响应函数定义
void CMainFrame::OnHello()
{
  MessageBox("Hello");
}
  • 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

第七章 对话框(一)

1、基本知识

控件:通常作为对话框的子窗口创建

对话框的种类:

模态对话框:对话框显示时,程序暂停执行,直到关闭对话框。

非模态:显示对话框时,可以执行其他任务。

2、创建和显示

CDialog对话框资源类

1.新建一个MFC类与该对话框关联:双击新建的对话框。

新建对话框资源后有两个按钮,功能一样(关闭对话框),返回值不同。

OK IDOK OnOK

Cancel IDCANCEL OnCancel

2.**DoDataExchange()**:完成对话框数据的交换和校验。

3.在视类中

**CDialog::DoModal**创建并显示模态对话框

**CDialog::EndDialog**关闭模态对话框

3、创建动态按钮

1.视类→工具箱→对话框编辑器 创建一个按钮。

2.为该按钮添加功能

类向导→选择该对象对应的BN_CLICKED(按钮被单击的消息)消息

在CTestDlg类添加
private:
  CButton m_btn;//动态按钮对象
  BOOL isbtncreate;//判断动态按钮是否被创建,在构造函数中初始化为0

void CTestDlg::OnClickedBtnAdd()
{
  // TODO: 在此添加控件通知处理程序代码

  //动态创建按钮
  //static BOOL isbtncreate = 0;
  
  if (isbtncreate == 0)//判断动态按钮是否被创建,
  {
    //CButton::Create创建按钮
    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
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

初始化变量的两种方法

1.创建成员变量,并在构造函数中初始化;

2.创建静态局部变量同时初始化。

4、对话框控件

1.静态文本控件

IDC_STATIC不能响应消息,可通过修改ID来响应消息

**CWnd::GetDlgItem**获取对话框子控件的指针

**CWnd::GetWindowText**获取窗口文本

**CWnd::SetWindowText**设置窗口文本

void CTestDlg::OnClickedNumber1()
{
  // TODO: 在此添加控件通知处理程序代码
  CString str;
  if (GetDlgItem(IDC_NUMBER1)->GetWindowText(str),str == "数值1")
  //逗号表达式,返回最后一个表达式的值
  {
    GetDlgItem(IDC_NUMBER1)->SetWindowText("Number1");
  }
  else
  {
    GetDlgItem(IDC_NUMBER1)->SetWindowText("数值1");
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

最后在属性中选中通知(Notify)选项,否则不能向其父窗口发送鼠标事件。

2.编辑框控件

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/690685
推荐阅读
相关标签