vc++(vs2010) windows编程与绘图程序设计

11 Vc++2010开发平台概述 3
1.1 Vc++2010概述 3
1.2 Visual Studio 2010 集成开发环境 3
1.2.1安装Visual Studio 2010 3
1.2.2配置Visual Studio 2010 3
1.2.3解决方案和项目 3
1.2.4生成和调试工具 3
1.2.5部署工具 3
1.2.6 Ribbon界面设计可视化 4
2 Visual Studio 2010 MFC基本程序设计 4
2.1 MFC应用程序概述 4
2.1.1简单的MFC应用程序 4
2.1.2 MFC应用程序分析 4
2.2 MFC应用程序的创建 4
2.2.1 用AppWizard创建CLR控制台应用程序 4
2.2.2 用AppWizard创建win32控制台应用程序 4
2.2.3 用AppWizard创建一个MFC应用程序 5
2.3 MFC的类 9
2.3.1 MFC应用程序中包含的类 9
2.3.2 应用程序类 10
2.3.3 主框架类 10
2.3.4 视图类 10
2.3.5 文档类 10
2.3.6 对话框类 10
2.3.7 菜单类 10
2.3.8 线程基类 10
3 Wndows窗体应用程序 11
3.1 创建Wndows窗体应用程序 11
3.2 窗体属性及设置 12
3.3 窗体事件及处理 13
3.4 窗体控件添加与编辑 13
4 用户界面设计 13
4.1 菜单设计 13
4.1.1 菜单的类型 13
4.1.2 菜单设计 13
4.1.3 创建弹出式菜单 20
4.1.4 创建动态菜单 20
4.1.5 创建基于对话框的菜单 20
4.2 工具栏设计 20
4.3 状态栏设计 20
5 对话框设计 20
5.1 静态对话框 20
5.1.1静态对话框的建立过程 20
5.1.2静态对话框的应用 21
5.2 非静态对话框 31
5.3 通用对话框 31
6常用控件 31
6.1 公共控件 31
6.2 容器 31
6.3 数据控件 31
7 键盘鼠标事件 32
7.1键盘事件 32
7.2 鼠标事件 42
7.2.1客户区鼠标消息 42
7.2.2非客户区鼠标消息 45
7.2.3实例 46
8 图形文本与图像处理 51
8.1图形设备接口GDI(Graphics Device Interface) 51
8.2 设备上下文DC(device context) 54
8.2.1 设备上下文概述 54
8.2.2 CDC类 57
8.3 绘图环境 60
8.3.1绘图环境介绍 60
8.3.2映射模式与坐标系 61
8.3.3绘图颜色 71
8.3.4设置绘图属性 72
8.3.6几何对象的结构和类 79
8.4 画笔和画刷 85
8.4.1画笔 85
8.4.2画刷 92
8.5 绘图 99
8.5.1 绘图步骤 99
8.5.2 画像素点 99
8.5.3 画线状图 100
8.5.4 拖放画动态直线 104
8.5.5 画填充图 109
8.5.6 清屏 111
8.5.7 在控件上绘图 112
8.6 文本绘制 115
8.7 位图 115
8.8 图标和光标 116
8.9 图像处理 116
9 文件处理 116
10 数据库编程 116

1 Vc++2010开发平台概述
1.1 Vc++2010概述

1.2 Visual Studio 2010 集成开发环境

1.2.1安装Visual Studio 2010

1.2.2配置Visual Studio 2010

1.2.3解决方案和项目

1.2.4生成和调试工具

1.2.5部署工具

1.2.6 Ribbon界面设计可视化

2 Visual Studio 2010 MFC基本程序设计
2.1 MFC应用程序概述

2.1.1简单的MFC应用程序

2.1.2 MFC应用程序分析

2.2 MFC应用程序的创建

2.2.1 用AppWizard创建CLR控制台应用程序

2.2.2 用AppWizard创建win32控制台应用程序

2.2.3 用AppWizard创建一个MFC应用程序
1.打开vc++2010,从起始页中选择“新建项目”如图2.2.3.1。

图2.2.3..1
弹出图2.2.3.2所示界面,左侧选择“viaual c++语言”,中间选择“MFC 应用程序”,
图2.2.3.2
名称为huitu,位置为桌面,点击“确定”,弹出图2.2.3.3界面。

图2.2.3`.3
然后一路点击下一步(或者直接点击“完成”),则自动生成一个MFC 应用程序,此时查看生成的程序文件夹内包含的文件,如图2.2.3.4

图2.2.3`.4
Huitu文件夹内文件如图2.2.3.5

图2.2.3.5

点击启动调试工具拦(绿色三角,图2.2.3.6)或者按F5则系统调试运行,在弹出界面中选择是,弹出图2.2.3.7的程序运行界面。

图2.2.3.6

图2.2.3.7
此时再看程序文件夹内的文件如图2.8

图2.2.3.8
可以看见多了debug文件夹,分别打开debug文件夹以及huitu文件夹内的debug文件夹看看都多了啥文件。
至此,一个文正的MFC应用程序建立完成。保存退出vc++2010,要再次进入刚才建立的应用程序,直接点击程序文件夹内的“huitu.sln”即可。

2.3 MFC的类

2.3.1 MFC应用程序中包含的类

2.3.2 应用程序类

2.3.3 主框架类

2.3.4 视图类

2.3.5 文档类

2.3.6 对话框类

2.3.7 菜单类

2.3.8 线程基类

3 Wndows窗体应用程序
3.1 创建Wndows窗体应用程序
1.启动vc++2010,如图3.1.1。

图3.1.1
2.选择新建项目
在打开的窗口中,左侧语言选择vc++,窗口中间选择“windows窗体应用程序”,窗口下面“名称”填写form,工程位置为桌面(图3.1.2),点击确定,建成的应用程序界面如图3.1.3。

图3.1.2

图3.1.3
3.2 窗体属性及设置

3.3 窗体事件及处理

3.4 窗体控件添加与编辑

4 用户界面设计
4.1 菜单设计
4.1.1 菜单的类型

4.1.2 菜单设计
建立菜单的过程如下:
1.打开工程。打开前面建立的工程绘图(双击huitu工程文件夹中的huitu.sln)。
2.打开资源视图。通过“视图-其他窗口-资源视图”菜单打开资源视图窗口,如图4.1.2.1,在vc++界面左侧显示资源视图窗口。

图4.1.2.1
3.添加菜单。点击资源视图的menu,展开如图4.1.2.2所示

图4.1.2.2
可以看到在工程中有多个菜单,其中IDR_huituTYPE菜单是显示在各子文档界面中的菜单;IDR_MAINFRAME建立的菜单是程序主框架中显示的菜单(把所有子文档关闭后会显示);
在此双击IDR_huituTYPE,打开窗口如图4.1.2.3所示。

图4.1.2.3
在请在此处键入位置点击,光标激活后输入“直线”,同时会在文本框右边和下面分别出现写有“请在此键入”字样的文本框,右边的代表键入的直线菜单的子菜单,下面代表直线菜单同级的菜单,其他以此类推,选择直线菜单后,查看vc++界面中右下角的属性面板,可以看到“popup”属性设置的是“true”,代表直线菜单是弹出式菜单,它下面还有下一级菜单,否则,该属性如果设置为“false”,则它不能再添加子菜单了。按照以上方法,在直线菜单下设置“直线1”,“直线2”两个子菜单,建立与直线同级的“圆”菜单,其下设置“圆1”,“圆2”两个子菜单,如图4.1.2.4。

图4.1.2.4
4.运行查看菜单。按F5或者点击启动调试工具(绿色三角箭头),在探出的窗口中选择是,启动运行后测绘看到建立的菜单(图4.1.2.5)。

图4.1.2.5
5.为菜单添加函数并且编写代码
菜单添加后还不能运行,要想让它起作用,需要为其添加函数并在函数里添加代码,为菜单添加函数过程为(以直线1为例):选择直线1,在属性面板中把ID属性右边的值改为ID_line1,然后通过“项目-类向导”打开类向导窗口(图1.1.2.6)

图4.1.2.6
类名处选择huituView,在命令标签列表中找到并点击ID_line1,在消息标签中点击COMMAND,如图4.1.2.7。
I

图4.1.2.7
再点击“添加处理程序”按钮,弹出添加函数对话框

图4.1.2.8
成员函数名称默认(也可以修改),点确定,则与菜单直线1对应的函数生成,此时可通过点击图4.1.2.7中的“编辑代码”进入函数,或者在vc界面右上角的“解决资源管理器”面板中展开源代码(图4.1.2.8),找到huituView.CPP双击该文件也可进入函数。

图4.1.2.8
在函数中的// TODO: 在此添加命令处理程序代码
下面添加
CClientDC dc(this);
dc.LineTo(500,500);
两句,如图4.1.2.9

图4.1.2.9
6.运行检验菜单功能
按F5运行程序后点击直线1菜单,可以看到在用户区画出一条直线。
4.1.3 创建弹出式菜单

4.1.4 创建动态菜单

4.1.5 创建基于对话框的菜单

4.2 工具栏设计

4.3 状态栏设计

5 对话框设计

5.1 静态对话框
5.1.1静态对话框的建立过程
建立静态对话框的过程如下:
1.打开或建立工程。按照前面方法打开或者建立一个工程(MFC工程或者窗体应用程序均可,此处以mfc工程为例)
2.打开资源窗口。通过“视图-其他窗口-资源视图”打开资源视图窗口。
3.建立对话框。在资源视图的DIALOG文件夹上单击右键选择insert dialog,弹出新建的对话框,根据需要可以修改对话框属性面板中的属性值。
4.添加对话框类。在建立的该对话框上双击鼠标左键,弹出添加类向导,根据提示设置好参数后确认,则建立了与对话框对应的对话框类。
5.1.2静态对话框的应用
对话框的应用一般是实现人机交互,以下结合huitu工程中的直线2菜单说明对话框的应用,功能为点击直线2菜单,弹出对话框,在对话框里设置好直线的起点与终点坐标并确认后在客户区画出设置的起点到终点的直线。
1.打开工程。双击huitu.sln打开工程。
2.添加对话框。通过“视图-其他窗口-资源视图”打开资源视图,在资源视图的DIALOG文件夹上单击右键选择insert dialog,弹出新建的对话框(图5.1.2.1),在属性面板中把对话框的ID属性修改为:IDD_line2_dlg。

图5.1.2.1
3.建立对话框类。在建立的对话框上双击鼠标左键,弹出建立对话框类向导,名称中输入line2dlg,基类选择CDIalog(图5.1.2.2),点击完成,完成对话框类的建立,可以看到在解决资源管理器的头文件和源文件中已经建立了相应的件。

图5.1.2.2
4.添加控件。
在对话框上从工具箱中添加四个静态文本框、四个编辑文本框、一个按钮,静态文本框的caption属性分别改为:起点x坐标,起点y坐标,终点x坐标,终点y坐标,按钮caption属性改为“确定”。如图5.1.2.3.

图5.1.2.3
5.为编辑文本框添加变量。
要使用文本框,必须为其添加对应的变量,选择第一个文本框,点击“项目-类向导”菜单打开类向导,选择“成员变量”标签(图5.1.2.4),鼠标点击IDC_EDIT1(编辑文本框1对应的ID),点击右边的添加变量按钮弹出添加变量窗口。

图5.1.2.4

图5.1.2.5
在变量设置窗口中,成员变量名称输入m_line1x,类别选择value,类型选择long(图5.1.2.5).点确定。用同样的方法为其他三个编辑框添加变量,结果如图 5.1.2.6所示。

图5.1.2.6
6.为按钮添加函数,获取数据。
为了点击按钮确定后获取坐标数据以便于画直线,需要为按钮添加函数,选择前面添加的按钮“确定”,通过“项目-类向导”菜单打开类向导,选择命令标签,点击确定按钮的ID(IDC_BUTTON1),点击消息列表中的BN_CLICKED(代表鼠标点击时触发函数或者事件运行),见图5.1.2.7,击添加处理程序,弹出处理程序对话框,默认之后点击确定则为按钮添加了对应的函数,双击line2dlg.cpp会看见添加了如下函数:
void line2dlg::OnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码

}

图5.1.2.7
7.编写代码获取数据。在前面添加的按钮函数中编写如下代码获取数据:
UpdateData(true);
CDialog::EndDialog(0); //结束对话框

该函数的作用是把输入文本框的坐标数据赋值到对应的变量中,即四个文本框对应的变量line1x,line1y,line1endx,line1endy。如果代码为
UpdateData(false);
则是指把变量中的数值显示到变量对应的文本框中。
CDialog::EndDialog(0);
是为了结束对话框。
8.为菜单添加代码,画对话框设置坐标的的直线。
点击资源视图中的menu文件夹展开,双击IDR_huituTYPE菜单,选择直线2菜单,把属性中的ID属性改为ID_line2,通过“项目-类向导”打开类向导,类名选择Chuituview,命令标签中的对象选择ID_line2,消息选择COMMAND(图5.1.2.8),点击添加处理程序,弹出窗口中默认确定,则自动打开ChuituView.cpp,并且光标定位到该函数中,代码如下:
void ChuituView::OnLine2()
{
// TODO: 在此命令处处理程序代码

}

图5.1.2.8
定位到该代码窗口的最上端头文件处,添加以下一句:

#include “line2dlg.h”
目的是把对话框类包含进来,否则系统不认对话框中的信息。然后再online2()函数中添加如下代码:
line2dlg dlg; //生成对话框对象
dlg.DoModal();//显示对话框
CClientDC dc(this);//生成设备环境对象,获取设备环境
dc.MoveTo(dlg.m_line1x,dlg.m_line1y);//定位到起点坐标
dc.LineTo(dlg.m_line1endx,dlg.m_line1endy);//从起点到终点画直线

9.运行程序。编译运行,点击直线2菜单会弹出对话框,在对话框中设置好起点终点坐标后点确定,则在客户区画出指定坐标的直线。
5.2 非静态对话框

5.3 通用对话框

6常用控件
6.1 公共控件

6.2 容器

6.3 数据控件

7 键盘鼠标事件
7.1键盘事件
为了使用户掌握键盘消息及其处理消息,介绍一个键盘消息处理实例。
1.实例说明
本实例通过工程向导创建一个单文档工程,在其中响应键盘的WM_KEYDOWN、WM_KEYUP、WM_CHAR消息,实现下面的功能:
当用户按下了Shift键,在视图窗口中显示提示信息“用户按下了Shift键”;
当用户释放了Shift键时,在视图窗口中显示提示信息“用户释放了Shift键!”;
当用户按下了Shift键后又按下了字符“B”键,在视图窗口中显示提示信息“用户同时按下了Shift键和B键”(即输入B键或b键)。
2.开发过程
(1)创建工程。
利用MFC AppWizard[EXE]建立一个单文档的MFC工程“KeyboardDemo”。
(2)在ClassView选项卡上用鼠标右键单击该类,并从弹出的快速菜单中选择[Add Member Variable…]
菜单命令,为KeyboardDemoView类添加一个新的成员变量bShiftdown,将此成员变量的类型设置为BOOL,并将其访问权限设置为Private,
单击[OK]按钮,完成成员变量的添加操作。按照同样的方法,再添加2个BOOL型private成员变量bShiftup和bShiftB,
接下来在KeyboardDemoView构造函数中给三个指示变量赋初值”false”。代码如下:
CKeyboardDemoView::CKeyboardDemoView()
{
bShiftdown=bShiftup=bShiftB=false;//赋初值
}
(3)利用“建立类向导”添加键盘消息及处理函数,步骤如下:
在”ClassName”列表框中,选择键盘消息的处理函数”CKeyboardDemoView”,在”Object IDs”列表框中选择“CKeyboardDemoView”,
则在“Message”列表框中,列出了MFC为其预定义的消息,分别选择WM_KEYDOWN、WM_KEYUP、WM_CHAR消息,
单击“Add Function”按钮,MFC就会为其自动添加相应的消息映射宏和消息处理函数。
(4)添加实现代码:在资源文件“KeyboardDemoView.cpp”中添加各键盘消息函数的实现代码。
OnKeyDown函数的代码如下:
void CKeyboardDemoView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if(nChar==VK_SHIFT)//判断Shift键是否被按下
{
bShiftdown=true;
bShiftup=false;
Invalidate(true); //显示信息
}
CView::OnKeyDown(nChar, nRepCnt, nFlags);
}
注:Invalidate(TRUE)将整个窗口设置为需要重绘的无效区域,它会产生WM_PAINT消息,这样OnDraw将被调用。
OnKeyUp函数的代码如下:
void CKeyboardDemoView::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if(nChar==VK_SHIFT)//判断Shift键是否被释放
{
bShiftup=true;
bShiftdown=false;
Invalidate(true);//显示信息
}
CView::OnKeyUp(nChar, nRepCnt, nFlags);
}
OnChar函数的代码如下:
void CKeyboardDemoView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
//判断是否同时敲击了字符键B键和Shift键
if((nChar==’b’)|(nChar==’B’)) //if((nChar==66)|(nChar==98))
{
if(bShiftdown)
{
bShiftB=true;
bShiftdown=false;
Invalidate(true);
}
}
CView::OnChar(nChar, nRepCnt, nFlags);
}
在资源文件”KeyboardDemoView.cpp”的OnDraw函数中,实现在客户区窗口输出按键提示信息。代码如下:
void CKeyboardDemoView::OnDraw(CDC* pDC)
{
if(bShiftdown)//按下了Shift键
{
pDC->TextOut(20,20,”用户按下了Shift键!”);
}
if(bShiftup)//释放了Shift键
{
pDC->TextOut(20,20,”用户释放了Shift键!”);
}
if(bShiftB)//同时按下了Shift键和B键
{
pDC->TextOut(20,20,”用户同时按下Shift键和B键!”);
bShiftB=false;
}
}

举例:
CDC pDC =GetDC();
if (nChar==VK_UP)
{
pDC->Ellipse(40,40,200,200);
}
键盘事件总结:
Vc++中键盘事件中函数原型如下(以键盘按下为例,其他类似)
OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
其中:
nChar :表示按键的字符代码。
nRepCnt:表示按键的重复次数。
nFlags:表示扫描码、先前键状态、键转换状态等。
nChar的取值表:
虚拟键码 对应值 对应键
VK_LBUTTON 1 鼠标左键
VK_RBUTTON 2 鼠标右键
VK_CANCEL 3 Cancel
VK_MBUTTON 4 鼠标中键
VK_XBUTTON1 5
VK_XBUTTON2 6
VK_BACK 8 Backspace
VK_TAB 9 Tab
VK_CLEAR 12 Clear
VK_RETURN 13 Enter
VK_SHIFT 16 Shift
VK_CONTROL 17 Ctrl
VK_MENU 18 Alt
VK_PAUSE 19 Pause
VK_CAPITAL 20 Caps Lock
VK_KANA 21
VK_HANGUL 21
VK_JUNJA 23
VK_FINAL 24
VK_HANJA 25
VK_KANJI 25

VK_ESCAPE 27 Esc
VK_CONVERT 28
VK_NONCONVERT 29
VK_ACCEPT 30
VK_MODECHANGE 31
VK_SPACE 32 Space
VK_PRIOR 33 Page Up
VK_NEXT 34 Page Down
VK_END 35 End
VK_HOME 36 Home
VK_LEFT 37 Left Arrow
VK_UP 38 Up Arrow
VK_RIGHT 39 Right Arrow
VK_DOWN 40 Down Arrow
VK_SELECT 41 Select
VK_PRINT 42 Print
VK_EXECUTE 43 Execute
VK_SNAPSHOT 44 Snapshot
VK_INSERT 45 Insert
VK_DELETE 46 Delete
VK_HELP 47 Help
48 0
49 1
50 2
51 3
52 4
53 5
54 6
55 7
56 8
57 9
65 A
66 B
67 C
68 D
69 E
70 F
71 G
72 H
73 I
74 J
75 K
76 L
77 M
78 N
79 O
80 P
81 Q
82 R
83 S
84 T
85 U
86 V
87 W
88 X
89 Y
90 Z
VK_LWIN 91
VK_RWIN 92
VK_APPS 93
VK_SLEEP 95
VK_NUMPAD0 96 小键盘 0
VK_NUMPAD1 97 小键盘 1
VK_NUMPAD2 98 小键盘 2
VK_NUMPAD3 99 小键盘 3
VK_NUMPAD4 100 小键盘 4
VK_NUMPAD5 101 小键盘 5
VK_NUMPAD6 102 小键盘 6
VK_NUMPAD7 103 小键盘 7
VK_NUMPAD8 104 小键盘 8
VK_NUMPAD9 105 小键盘 9
VK_MULTIPLY 106 小键盘 *
VK_ADD 107 小键盘 +
VK_SEPARATOR 108 小键盘 Enter
VK_SUBTRACT 109 小键盘 -
VK_DECIMAL 110 小键盘 .
VK_DIVIDE 111 小键盘 /
VK_F1 112 F1
VK_F2 113 F2
VK_F3 114 F3
VK_F4 115 F4
VK_F5 116 F5
VK_F6 117 F6
VK_F7 118 F7
VK_F8 119 F8
VK_F9 120 F9
VK_F10 121 F10
VK_F11 122 F11
VK_F12 123 F12
VK_F13 124
VK_F14 125
VK_F15 126
VK_F16 127
VK_F17 128
VK_F18 129
VK_F19 130
VK_F20 131
VK_F21 132
VK_F22 133
VK_F23 134
VK_F24 135
VK_NUMLOCK 144 Num Lock
VK_SCROLL 145 Scroll
VK_LSHIFT 160
VK_RSHIFT 161
VK_LCONTROL 162
VK_RCONTROL 163
VK_LMENU 164
VK_RMENU 165
VK_BROWSER_BACK 166
VK_BROWSER_FORWARD 167
VK_BROWSER_REFRESH 168
VK_BROWSER_STOP 169
VK_BROWSER_SEARCH 170
VK_BROWSER_FAVORITES 171
VK_BROWSER_HOME 172
VK_VOLUME_MUTE 173 VolumeMute
VK_VOLUME_DOWN 174 VolumeDown
VK_VOLUME_UP 175 VolumeUp
VK_MEDIA_NEXT_TRACK 176
VK_MEDIA_PREV_TRACK 177
VK_MEDIA_STOP 178
VK_MEDIA_PLAY_PAUSE 179
VK_LAUNCH_MAIL 180
VK_LAUNCH_MEDIA_SELECT 181
VK_LAUNCH_APP1 182
VK_LAUNCH_APP2 183
VK_OEM_1 186 ; :
VK_OEM_PLUS 187 = +
VK_OEM_COMMA 188
VK_OEMMINUS 189 -
VK_OEM_PERIOD 190
VK_OEM_2 191 / ?
VK_OEM_3 192 ` ~
VK_OEM_4 219 [ {
VK_OEM_5 220 \ |
VK_OEM_6 221 ] }
VK_OEM_7 222 ‘ “
VK_OEM_8 223
VK_OEM_102 226
VK_PACKET 231
VK_PROCESSKEY 229
VK_ATTN 246
VK_CRSEL 247
VK_EXSEL 248
VK_EREOF 249
VK_PLAY 250
VK_ZOOM 251
VK_NONAME 252
VK_PA1 253
VK_OEM_CLEAR 254

7.2 鼠标事件
7.2.1客户区鼠标消息
当鼠标通过客户区时,就会触发客户区鼠标消息。
常用的客户区鼠标消息
消息名称 消息说明
WM_LBUTTONDBLCLK 鼠标左键被双击
WM_LBUTTONDOWN 鼠标左键被按下
WM_LBUTTONUP 鼠标左键被释放
WM_MBUTTONDBLCLk 鼠标中键被双击
WM_MBUTTONDOWN 鼠标中键被按下
WM_MBUTTONUP 鼠标中键被释放
WM_MOUSEMOVE 鼠标移动穿过客户区域
WM_RBUTTONDBLCLK 鼠标右键被双击
WM_RBUTTONDOWN 鼠标右键被按下
WM_RBUTTONUP 鼠标右键被释放
WM_MOUSEWHEEL 在客户区内鼠标滚轮滚动
应用程序一般是处理客户区鼠标消息。非客户区鼠标消息可以被Windows本身妥善的处理。
客户区鼠标消息处理函数的一般形式为:
void OnXxxYyy(UINT nFlag, CPoint point);
如WM_LBUTTONDOWN的消息处理函数,其声明如下:
void OnLButtonDown(UINT nFlag, CPoint point);
函数的两个参数说明如下:
point:为CPoint类对象,记录了当前光标的x,y坐标,这个坐标是以相对于窗口客户区左上角的设备坐标而言的。
nFlags:为鼠标动作的条件标志,取值是以下各种取值的组合。各取值含义如下:
MK_LBUTTON: 按下了鼠标的左键。
MK_MBUTTON: 按下了鼠标的中键。
MK_RBUTTON: 按下了鼠标的右键。
MK_CONTROL: 按下了键盘上的Ctrl键。
MK_SHIFT: 按下了键盘上的Shift键。
在实际编程中,常使用nFlags参数与以上可能取值进行位与运算,来判断消息生成时的鼠标键以及Shift以及Ctrl的状态。
如检测Shift键和Ctrl键的状态,如下:
void OnLButtonDown(UINT nFlags,CPoint point)
{
//判断是否Ctrl键和Shift键同时按下
if((nFlags&MK_CONTROL)&&(nFlags&MK_SHIFT))
……
}
void CMouseDemoView::OnMouseMove(UINT nFlags, CPoint point)
{
//移动鼠标时鼠标左键是按下的
if((nFlags&MK_LBUTTON) == MK_LBUTTON)
{

}

}
鼠标捕捉
如果你的鼠标移出了某个窗口的客户区范围,那么此窗口将不再继续收到客户区鼠标消息。
SetCapture和ReleaseCapture
如果你用SetCapture进行了鼠标捕捉,那么,即使你的鼠标移出了窗口客户区,仍然可以收到客户区鼠标消息,直到调用ReleaseCapture释放鼠标捕捉或在某个窗口上进行点击为止。
注意:
一旦某个窗口捕获了鼠标,其他窗口将无法得到鼠标消息。
因此,当窗口不再需要捕获鼠标消息时,应及时使用ReleaseCapture函数将鼠标释放。

7.2.2非客户区鼠标消息
当鼠标通过非客户区时,就会触发非客户区鼠标消息。
常见的非客户区鼠标消息
消息名称 消息说明
WM_NCLBUTTONDBLCLK 鼠标左键被双击
WM_NCLBUTTONDOWN 鼠标左键被按下
WM_NCLBUTTONUP 鼠标左键被释放
WM_NCMBUTTONDBLCLK 鼠标中键被双击
WM_NCMBUTTONDOWN 鼠标中键被按下
WM_NCMBUTTONUP 鼠标中键被释放
WM_NCMOUSEMOVE 鼠标移动穿过客户区域
WM_NCRBUTTONDBLCLK 鼠标右键被双击
WM_NCRBUTTONDOWN 鼠标右键被按下
WM_NCRBUTTONUP 鼠标右键被释放

7.2.3实例
1.鼠标消息的处理及鼠标相关的操作。
实例说明:
通过工程向导创建一个单文档工程,在其中响应鼠标的WM_LBUTTONDOWN、WM_LBUTTONUP、WM_MOUSEMOVE消息
实现功能:
当按下鼠标左键并移动鼠标时,在客户区窗口内将绘制鼠标的移动轨迹,同时光标变为“十字”光标或自定义光标。
当按下鼠标左键时,鼠标的移动范围被限制的整个客户区窗口范围时,即鼠标不能移到客户区外。
当释放了鼠标左键后,鼠标恢复原来的活动区域。
开发步骤:
(1)创建工程。启动Visual C++,利用MFC APPWizard[EXE]建立一个新的MFC工程,工程名为“MouseDemo”,
在MFC AppWizard Step1的时候选择“Single documents”即基于单文档的MFC工程,之后的步骤使用默认值。
(2)利用“ClassWizard”添加鼠标消息及处理函数:
在”ClassName”列表框中,选择鼠标消息的处理类”CMouseDemoView”,在“Message”列表框中, 列出了MFC预定义的消息,分别选择WM_LBUTTONDOWN、WM_LBUTTONUP、WM_MOUSEMOVE消息,单击“Add Function”按钮,
MFC就会为其自动添加相应的消息映射宏和消息处理函数。
(3)添加实现代码
首先在头文件”MouseDemoView.h”中声明变量startpoint、rcOldClip。
代码如下:
public:
CPoint startpoint;
RECT rcOldClip;
添加WM_LBUTTONDOWN的消息处理代码如下:
void CMouseDemoView::OnLButtonDown(UINT nFlags, CPoint point)
{
GetClipCursor(&rcOldClip);//获取原鼠标活动的有效区域
startpoint=point; //鼠标所在点为起始点
SetCapture(); //进行鼠标捕捉
CView::OnLButtonDown(nFlags, point);
}
添加WM_LBUTTONUP的消息处理代码如下:
void CMouseDemoView::OnLButtonUp(UINT nFlags, CPoint point)
{
ClipCursor(&rcOldClip);//恢复原来的鼠标活动区域
ReleaseCapture(); //释放鼠标捕捉
CView::OnLButtonUp(nFlags, point);
}
添加WM_MOUSEMOVE的消息处理代码如下:
void CMouseDemoView::OnMouseMove(UINT nFlags, CPoint point)
{
CDC * pDC=GetDC(); //获得DC
HCURSOR cursor; //鼠标光标句柄
RECT rcClip; //限制矩形区域
if((nFlags&MK_LBUTTON) == MK_LBUTTON) //移动鼠标时鼠标左键是按下的
{
GetWindowRect(&rcClip); //获取客户区窗口区域
ClipCursor(&rcClip); //将鼠标的移动限制的客户区
cursor=AfxGetApp()->LoadStandardCursor(IDC_CROSS); //载入标准十字光标
SetCursor(cursor); //使用新光标
pDC->MoveTo(startpoint.x,startpoint.y); //开始画线
pDC->LineTo(point.x,point.y);
startpoint=point;
}
CView::OnMouseMove(nFlags, point);
}
(4)编译运行
程序解释:
(1)在鼠标消息响应函数中,根据参数nFlags可以判断鼠标的左、右、中键以及键盘上的Shift键和Ctrl键的按下状态。
在本例的OnMouseMove()函数中,就利用nFlags参数判断鼠标左键是否被按下来完成画线功能。
(2)当按下鼠标左键并移动鼠标时,在OnMouseMove()函数中通过调用API函数ClipCursor()将鼠标的活动区域限制在客户区窗口中。
而当鼠标左键释放时,在函数OnLButtonUp()中,通过ClipCursor()将鼠标的活动区域恢复为原来状态。

2.实例2:
CDC* pDC =GetDC();
if (nFlags==MK_LBUTTON)
{
pDC->LineTo(point.x,point.y);
}
CView::OnLButtonDown(nFlags, point);

3.实例3:
CDC pDC =GetDC();
CPen pen;
pen.CreatePen(PS_SOLID,5,RGB(255,0,0));
CPen
pOldPen=pDC->SelectObject(&pen);
if (nFlags==MK_LBUTTON)
{
pDC->MoveTo(400,300);
pDC->LineTo(point.x,point.y);
}
pDC->SelectObject(pOldPen);
4.实例4:
CDC pDC =GetDC();
CPen pen;
pen.CreatePen(PS_SOLID,2,RGB(255
rand(),255rand(),255rand()));
CPen pOldPen=pDC->SelectObject(&pen);
int i;
if (nFlags==MK_LBUTTON)
{
for(i=1;i<50;i++) {="" pdc-="">MoveTo(point.x,point.y);
pDC->LineTo(400
rand(),400rand());
}
}
pDC->SelectObject(pOldPen);
ReleaseDC(pDC); //释放DC
实例5
CDC
pDC =GetDC();
CPen pen;
pen.CreatePen(PS_SOLID,2,RGB(255rand(),255rand(),255rand()));
CPen
pOldPen=pDC->SelectObject(&pen);
CRect rect(30,40,point.x,point.y);
pDC->Rectangle(&rect); // 绘制矩形

pDC->SelectObject(pOldPen);
ReleaseDC(pDC); //释放DC

判断是哪一个字符键也可以:
Char keychar;
Keychar = char(nChar)
If (keychar=”a”)
…..
其中nChar为键盘事件中的一个参数
8 图形文本与图像处理
8.1图形设备接口GDI(Graphics Device Interface)
GDI(Graphics Device Interface,图形设备接口)是Windows操作系统的传统图形子系统,负责与设备无关的图形绘制,Win32 API为应用程序提供了丰富的绘图函数和功能,MFC对它们进行了C++类封装,参见图8-1。
Windows应用程序
MFC
Win32 API
执行程序 用户界面(User) GDI+
图形子系统(GDI)
设备驱动程序 内核(Kernel) 图形驱动程序
硬件抽象层(HAL)
计算机硬件
图8-1 GDI与Windows操作系统(其中彩色部分为操作系统)
传统GDI是随Windows 1.0于1985年11月推出的,新式GDI+则是随Windows XP于2001年10月推出的GDI的改进版,增加了α混色、渐变画刷、样条曲线、矩阵变换、图像处理、持久路径等新功能。随Windows Vista及.NET框架3.0微软于2006年11月又推出了基于DirectX和.NET框架的全新图形子系统WPF(Windows Presentation Foundation,视窗显示/展现基础),它统一了桌面和浏览器等客户端应用程序的图形界面,采用XAML声明式编程,将用户界面的设计和编程彻底分离开来,是Windows的下一代GUI显示系统。
GDI+是建立在GDI之上的。WPF则是以.NET框架为基础的。
在MFC中,CGdiObject类是GDI对象的基类,而CGDIObject则是直接从CObject类派生的抽象基类,通过查阅MSDN我们可以看到,CGdiObject类有六个直接的派生类,GDI对象主要也是这六个,分别是:CBitmap、CBrush、CFont、CPalette、CPen和CRgn。

CRgn为区域(region)类,对应于窗口中的一个矩形、多边形或(椭)圆区域,可用于移动、拷贝、合并、判断和裁剪。
GDI负责系统与用户或者绘图程序之间的信息交换,并控制在输出设备上显示图形或文字。
将GDI对象用SelectObject函数选入DC后,就可利用对应的绘图工具进行图形绘制。
可用CDC类的多态成员函数SelectObject,将绘图工具对象选入DC,以供绘图时使用:
CPen SelectObject( CPen pPen );
CBrush SelectObject( CBrush pBrush );
virtual CFont SelectObject( CFont pFont );
CBitmap SelectObject( CBitmap pBitmap );
int SelectObject( CRgn pRgn );
CGdiObject
SelectObject( CGdiObject* pObject );

8.2 设备上下文DC(device context)
8.2.1 设备上下文概述
在GDI中,DC(Device context)是一个非常重要的概念。
有的书中,将DC翻译为设备描述表(《Windows 程序设计 第五版》作者Charles Petzold),也有的书将DC翻译为设备上下文。
到底什么是DC?
用现实中的例子来理解可能更容易些。
如果你喜欢画画,你得先准备了画布,画笔,颜料……
画画的环境搭建好了,你就可以画画了。
这个画画的环境,就是DC。
在图形环境下,一切都是画出来的,所以,你要准备好一个DC,才能在屏幕上画画。——写字也是画画。
在画画的环境中,有哪些对象呢?
画布——GDI对象之一:区域
画笔——GDI对象之一:画笔
颜料盒——GDI对象之一:调色板
如果要在画笔上写字的话,写什么样的字体呢?方正字体?—字体也是GDI对象之一。
有的画笔比较粗,专用来刷大面积背景色的,这是刷子——GDI对象之一:刷子
如果你不想画了,只想把别人画好的画,贴到你的画布上,这也是可以的。——GDI对象之一:位图。
所以,这里就有6种GDI对象可以用于DC。
现在开始画画了,你拿起了一只笔。——在Windows环境里,这叫选择了一个画笔对象:使用SelectOBject函数。当然,如果你没带笔也没关系,Windows为你准备了几只画笔,你可以这样申请系统提供的缺省画笔:hPen = GetStockObject(WHITE_PEN);
如果你画着画着,觉得手中的笔用着不爽,可以换一只啊,没关系的。——依旧是SelectObject()换笔。
当然,如果你走出了画室,别完了把你的画笔清除掉,要不画室里全是笔啊,刷子啊,太乱了。——DeleteObject()
所以对DC可以这样理解:
每一笔用GDI画的都通过它与设备交流最终画在屏幕上不同屏幕/设备它不同,所以可以保证画出的效果相同,增加程序的可移植性也就是GDI是画在DC上的,DC再显示在屏幕上DC为GDI基础通过GDI绘制出来时看不到的也就是不显示的,只在内存上的一副图片,这幅图可以通过DC绘制在设备上有几种常见环境:
1)显示设备环境
主要用于显示设备上的绘制操作,当应用绘制客户区时,它需要调用BeginPaint、GetDC或GetDCEx函数获取显示设备上下文。绘制结束后,需要调用EndPaint或ReleaseDC函数释放它。
2)打印机设备环境
应用程序以合适的参数(如打印机驱动名、打印机名等)调用函数CreateDC,完成打印任务后,就会调用DeleteDC,以删除打印机设备上下文。
3)内存设备环境
主要为特定的设备存储位图,它支持在位图上进行绘制操作。通常通过调用CreateCompatibleDC来获取内存设备上下文,当系统处理这个调用时,它将在内存中创建一个和原始设备颜色格式兼容的位图。
内存设备上下文将系统内存用作显示表面,通常使用内存设备上下文预先在系统内存中绘制复杂的图形,然后再快速地将其复制到实际的设备上下文的显示表面上,而绘制图形的结果仍保存在内存设备上下文的DDB中。
4)信息设备环境
主要用于获取默认设备的数据。如调用函数CreateIC,以便为打印机的特定模型创建信息设备上下文,然后调用函数GetCurrentObject和GetObject来获取默认的画笔和画刷的属性。在使用完信息设备上下文之后,需调用DeleteDC以删除设备上下文。
设备描述表(DC)是Windows中的一种数据结构,它包含GDI需要的所有关于显示界面情况的描述字段,包括相连的物理设备和各种各样的状态信息。在Windows画图之前,Windows程序从GDI获取设备描述表句柄(HDC),并在每次调用完GDI输出函数后将句柄返回给GDI。
GDI函数中使用该句柄的数据信息来在设备上下文环境中绘图DC好比画图用的画纸,GDI就是画图时用的工具。
在Windows中,绘图包括绘制图形、显示位图和输出文字,它们都需要使用绘图环境DC(Device-Context, 设备上下文)。MFC将DC结构和所有的绘图函数、绘图对象的访问函数、绘图模式与参数的设置函数都封装到了CDC类中。
8.2.2 CDC类
CDC类是MFC对DC结构及其相关绘图和状态设置函数的C++类封装。CDC是CObject的直接派生类,CDC类自己也有若干派生类,其中包括:窗口客户区DC所对应的CClientDC类、OnPaint和OnDraw消息响应函数的输入参数中使用的CPaintDC类、图元文件对应的CMetaFileDC类和整个窗口所对应的CWindowDC类,参见图8-3。在普通的MFC绘图中,一般直接使用CDC类即可。

CDC类
CDC类中有许多成员函数,可以用来设置各种绘图环境、属性和参数,以及绘制各种图形和图像等,将在后面陆续加以介绍。
1.获得DC
在OnDraw函数中应该使用输入参数pDC,在其他函数中则可调用从CWnd类继承的成员函数GetDC来获得当前窗口(如客户区/视图类)DC的指针,该函数的原型为:
CDC GetDC( );
注意,每次从OnDraw函数的输入参数或调用GetDC所获得的DC,都是一个全新的临时默认DC,具有默认的绘图环境和设置。它不能用类变量来长期保存,而且原来选入的各种GDI对象全都被作废、原来设置的各种状态也失效,一切都必须从头再来。
2.释放DC
因为Windows限制可用DC的数量,所以DC属于稀缺的公用资源。因此,对每次获得的DC,在使用完成后必须立即释放。
从OnDraw函数的输入参数pDC获得的DC,在该函数运行结束后,系统会自动释放。但由GetDC所获得的DC,则必须自己来手工释放,这可以通过调用从CWnd类继承的成员函数ReleaseDC来完成。该函数的原型为:
int ReleaseDC( CDC
pDC ); // 成功返回非0
例如:
void CDrawView::OnLButtonUp(UINT nFlags, CPoint point)
{
CDC pDC = GetDC(); // 获取DC
pDC->SelectObject(&pen); // 选入笔
pDC->Rectangle(&rect); // 绘制矩形
ReleaseDC(pDC); // 释放DC
CView::OnLButtonUp(nFlags, point);
}
3.安全DC句柄
可以用CDC类的成员函数GetSafeHdc来获取DC所对应窗口(如客户区)的安全DC句柄,该函数的原型为:
HDC GetSafeHdc();
与用GetDC函数所得到的临时DC不同,该安全DC句柄在窗口的存在期间内一直是有效的。例如,可先定义类变量HDC m_hDC;,再在适当的地方给它赋值m_hDC = GetDC()->GetSafeHdc();,然后就可以放心地使用了。还可以使用CDC类的成员函数Attach来将一空CDC对象与此安全DC句柄连接在一起,该函数的原型为:
BOOL Attach(HDC hDC); // 成功返回非0
4.屏幕DC
可以以NULL为输入参数,调用全局的API函数GetDC:
HDC GetDC(HWND hWnd);
来可获取屏幕DC的句柄。例如:
HDC hdc = ::GetDC(NULL); // 获取屏幕DC的句柄
Ellipse(hdc, 0, 0, 150, 100); // 用API函数在屏幕左上角画一椭圆
也可以利用CDC类的成员函数Attach,将屏幕DC的句柄选入自定义的CDC类中,来 构造屏幕CDC类的对象。例如:
HDC hdc = ::GetDC(NULL); // 获取屏幕DC的句柄
CDC sDC; // 定义屏幕DC(空)对象
sDC.Attach(hdc); // 粘上屏幕DC句柄
sDC.Ellipse(0, 0, 150, 100); // 在屏幕左上角画一椭圆
8.3 绘图环境
8.3.1绘图环境介绍
在Windows中,绘图一般在框架窗口的客户区(对应于视图类C
View)进行,使用的是封装在MFC的设备上下文(Device-Context,DC)类CDC中的各种绘图函数。
在绘图前,一般需先得到客户区大小和CDC对象、设置绘图颜色,然后再根据文档数据或用户操作来绘制各种图形。当然,如果没有特殊要求,最简单的就是直接生成CDC对象,通过对象调用画图函数画图,只是此时颜色是默认黑色,线宽为一个像素的实线图。
8.3.2映射模式与坐标系
在Windows中,绘图一般在视图窗口的客户区进行,使用的是设备上下文类CDC中各种绘图函数。为了达到理想的绘图效果,有时需要先利用CDC类设置绘图所用的坐标系(映射模式)和各种绘图属性。
在Windows中,绘图都是在当前的映射模式(map mode)下(坐标系中)进行的。而当前映射模式可以用CDC类的成员函数SetMapMode来设置。
一、默认映射模式
映射模式影响所有的图形和文本绘制函数,它定义(将逻辑单位转换为设备单位所使用的)度量单位和坐标方向,Windows总是用逻辑单位来绘图。
默认情况下,绘图的默认映射模式为MM_TEXT,其绘图单位为像素(只要不打印输出,屏幕绘图使用该模式就够了)。若窗口客户区的宽和高分别为w和h像素,则其x坐标是从左到右,范围为0 ~ w-1;y坐标是从上到下,范围为0 ~ h-1,参见图8-7。

图8-7 默认的MM_TEXT 图8-8 非自定义且非MM_TEXT映射
映射模式下的窗口坐标系 模式下的(初始)窗口坐标系
(注意y在当前窗口中的坐标值为负)
二、设置映射模式(坐标模式)
可使用CDC类的成员函数GetMapMode和SetMapMode来获得和设置当前映射模式:
int GetMapMode( ) const; // 返回当前的映射模式
virtual int SetMapMode( int nMapMode ); // 返回先前的映射模式
映射模式nMapMode的取值见表8-7。
表8-7 映射模式的nMapMode取值与含义
符号常量 数值 x方向 y方向 逻辑单位
MM_TEXT 1 向右 向下 像素
MM_LOMETRIC 2 向右 向上 0.1 mm
MM_HIMETRIC 3 向右 向上 0.01 mm
MM_LOENGLISH 4 向右 向上 0.01 in
MM_HIENGLISH 5 向右 向上 0.001 in
MM_TWIPS 6 向右 向上 1/1440 in
MM_ISOTROPIC 7 自定义 自定义 自定义
MM_ANISOTROPIC 8 自定义 自定义 自定义
其中,in为英寸,1 in = 2.54 cm;twips(缇)是一种打印标准单位;isotropic(各向同性的),指x与y方向的单位相同;和anisotropic(各向异性的),则是指x与y方向的单位可不同。
可见,除了两种自定义映射模式外,x方向都是向右,y方向也只有MM_TEXT的向下,其余的都是向上,与数学上坐标系一致,参见图8-8。除了MM_ANISOTROPIC外,其他所有映射模式的x与y方向的单位都是相同的。所有映射模式的逻辑坐标的原点(0, 0)最初都是在窗口的左上角,但在CScrollView的派生类中,MFC会随用户滚动文档而自动调整逻辑原点的相对位置(改变视点的原点属性)。
三、单位转换
对所有非MM_TEXT映射模式,有如下重要规则:
 CDC的成员函数(如各种绘图函数)具有逻辑坐标参数。
 CWnd的成员函数(如各种响应函数)具有设备坐标参数(如鼠标位置point)。
 位置的测试操作(如CRect的PtInRect函数)只有使用设备坐标时才有效。
 长期使用的值应该用逻辑坐标保存(如窗口滚动后保存的设备坐标就无效了)。
因此,为了使应用程序能够正确工作,除MM_TEXT映射模式外,其他映射模式都需要进行单位转换。Windows的GDI负责逻辑坐标和设备坐标之间的转换,这可以调用CDC类的成员函数LPtoDP和DPtoLP来进行:
void LPtoDP( LPPOINT lpPoints, int nCount = 1 ) const;
void LPtoDP( LPRECT lpRect ) const;
void LPtoDP( LPSIZE lpSize ) const;
void DPtoLP( LPPOINT lpPoints, int nCount = 1 ) const;
void DPtoLP( LPRECT lpRect ) const;
void DPtoLP( LPSIZE lpSize ) const;
例如:
void CDrawView::OnLButtonDown(UINT nFlags, CPoint point) {
CRect rect = m_rect; // 逻辑坐标
CClientDC dc(this);
dc.SetMapMode(MM_LOENGLISH);
dc.LPtoDP(rect); // 转化成设备坐标
// 位置的测试操作只有在使用设备坐标时才有效
if (rect.PtInRect(point)) ……
}
void CDrawView:: OnMouseMove (UINT nFlags, CPoint point) {
float t,y;
char buf[40];
CDC pDC = GetDC();
pDC->SetMapMode(MM_HIMETRIC);
pDC->DPtoLP(&point); // 转化成逻辑坐标
t = t1 + (point.x
dt) / w; sprintf(buf, “%.4fs”, t);
pSB->SetPaneText(xV, buf);
y = (y0 - point.y) / dy; sprintf(buf, “%.4f”, y);
pSB->SetPaneText(yV, buf);
……
}
四、建立自己需要的坐标系
建立一个合适的坐标系可以为我们的绘图带来很大的方便。下面介绍一下如何在VC中建立我们想要的坐标系。
(一)设备坐标和逻辑坐标
设备坐标(Device Coordinate)又称为物理坐标(Physical Coordinate),是指输出设备上的坐标。通常将屏幕上的设备坐标称为屏幕坐标。设备坐标用对象距离窗口左上角的水平距离和垂直距离来指定对象的位置,是以像素为单位来表示的,设备坐标的X轴向右为正,Y轴向下为正,坐标原点位于窗口的左上角。
逻辑坐标(Logical Coordinate)是系统用作记录的坐标。在缺省的模式(MM_TEXT)下,逻辑坐标的方向和单位与设备坐标的方向和单位相同,也是以像素为单位来表示的,X轴向右为正,Y轴向下为正,坐标原点位于窗口的左上角。逻辑坐标和设备坐标即使在缺省模式下其数值也未必一致,除了在以下两种情况下:

  1. 窗口为非滚动窗口
  2. 窗口为滚动窗口,但垂直滚动条位于滚动边框的最上端,水平滚动条位于最左端,但如果移动了滚动条这两种坐标就不一致了。
    在VC中鼠标坐标的坐标位置用设备坐标表示,但所有GDI绘图都用逻辑坐标表示,所以用鼠标绘图时,那么必须将设备坐标转换为逻辑坐标,可以使用CDC 函数DptoLP()将设备坐标转化为逻辑坐标,同样可以用LptoDP()将逻辑坐标转化为设备坐标。
    ///////////////////
    MFC逻辑坐标原点与设备坐标原点的移动
    ①原点概念的理解:在数学中常称点(0,0)为坐标原点,但是在windows中原点是一个坐标,但并未必是(0,0)点。原点这个说法的使用时为了联系设备坐标和逻辑坐标—设备坐标的原点一定要和逻辑坐标的原点重合。
    ②映射模式被定义为从窗口(window也即逻辑坐标)到视口(viewport也即设备坐标)的映射,具体的内容可以参考:《MFC Windows 程序设计》P36和《Windows程序设计》P147。
    ③既然用坐标来标注设备坐标系和逻辑坐标系的原点,那么二者坐标值的求取必须具有同一个参考对象,也就是说必须有一个参考点来确定设备坐标中原点和逻辑坐标中的原点的坐标。在windows中选择设备坐标点(0,0)为参考点,设备坐标点(0,0)始终都位于客户区的左上角。(《Windows程序设计》P147)
    ④传送给CDC输出函数的是逻辑坐标,《MFC Windows 程序设计》P36
    ⑤逻辑坐标:可理解成几何作业本上的直角坐标系,坐标值是无限的,Windows的绘图是以逻辑坐标作为参数,也就是说Windows绘图是在逻辑平面上进行的。
    设备坐标:是物理的,就理解成手上拿着的方形框框(如显示器),通过该方框可以看到在逻辑平面上所画的东西。设备坐标的左上角永远是(0,0)点,且X轴的正方向向右,Y轴的正方向向下。设备总是有尺寸的,只能显示某个范围的内容。
    默认情况下,逻辑坐标采用MM_TEXT映射模式,该映射模式下逻辑坐标的方向与设备坐标系的方向相同:X轴的正方向向右,Y轴的正方向向下。再加上坐标原点重合,故给人的感觉是只有一个坐标系,具体如图1所示:

图1
⑥举例说明SetWindowOrg和SetViewportOrg的使用
在默认的情况下逻辑坐标到设备坐标的映射模式为:MM_TEXT,为方便起见这里不再更改映射模式,在默认的情况下:窗口原点为(0,0)点,视口原点也为(0,0)点。
假设将设备原点移到窗口的中心点,即现有的视口原点不再是(0,0)而是窗口中心点。
设dc为一个设备描述表对象,具体实现如下:
CRect rect;
GetClientRect (&rect);//返回的是设备坐标,而SetViewportOrg需要的也是设备坐标,故此处不用转换
dc.SetViewportOrg (rect.Width ()/2, rect.Height ()/2);
图解分析如图2所示:
图2
具体分析:Ⅰ:将视口原点由(0,0)点移至(x,y)等价于把逻辑点(0,0)映射成设备点(x,y)。
Ⅱ:设备坐标原点与逻辑坐标原点必须重合。
Ⅲ:默认情况下逻辑坐标的原点为(0,0)点,且本处只移动了设备坐标原点,并没有移动逻辑坐标原点,故逻辑坐标的原点仍为(0,0)点,不过要满足Ⅱ中的条件,逻辑坐标系发生了移动,移动结果如图2所示,此时移动后的设备原点(rect.Width ()/2, rect.Height ()/2)与逻辑原点(0,0)重合。
⑦在上面的⑥中将视口原点移到视口的中点,并完成了将逻辑原点到视口中点的映射。为达到相同的效果,用
SetWindowOrg移动逻辑坐标原点也可以达到相同的效果。
为使理解更加清晰透彻,这里做了两组对比的实验
实验一:
CRect rect;
GetClientRect (&rect);
CPoint point (rect.Width () / 2, rect.Height () / 2);
dc.DPtoLP (&point);
dc.SetWindowOrg (point.x, point.y);
该实验的逻辑坐标原点移动的示意图如图3所示:
图3
在图3中若进行正常移动则逻辑原点将移至设备坐标系的中点,为保证逻辑原点与设备原点的重合,必须移动设备原点(注:由于设备原点没有移动,仍是(0,0)点),但是根据规定设备坐标点是不能移动的,由前面的③可知,作为参考点的设备点(0,0)是不能移动的,不过此处设备点(0,0)恰好又是设备原点而已,故不能移动。既然设备原点不能移动,却又要满足逻辑坐标原点(point.x, point.y)与设备坐标原点(0,0)的重合,只能进行等价移动逻辑坐标系,移动如图3所示:很明显逻辑坐标原点为(point.x, point.y)且满足改点与设备坐标原点(0,0)的重合。不过对比图2,很明显通过对设备坐标原点进行该种移动不能达到相同的效果,即实验一并未得到与⑥中同样的结果。
实验二:
CRect rect;
GetClientRect (&rect);
CPoint point (rect.Width () / 2, rect.Height () / 2);
dc.DPtoLP (&point);
dc.SetWindowOrg (-point.x, -point.y);
该实验的逻辑坐标原点移动的示意图如图4所示:
图4
具体的分析与实验一类似,这里不再赘述了,与图2对比很明显得到:对逻辑坐标原点进行这种移动可达到与移动设备坐标原点相同的效果,但有一点值得注意:达到的效果相同,但进行这种移动后设备坐标原点为(0,0),逻辑坐标原点为(-point.x, -point.y),而在图2中经过移动设备坐标原点后:设备原点为(rect.Width ()/2, rect.Height ()/2),逻辑原点为(0,0)。
总结:关于其余映射模式下的移动以及同时采用逻辑坐标原点和谁被坐标原点移动的情况同样可采用上面的分析方法,不过特别注意不同映射模式下的原点移动!
////////////////
Windows坐标系分为逻辑坐标系和设备坐标系两种,GDI支持这两种坐标系。一般而言,

GDI的文本和图形输出函数使用逻辑坐标,而在客户区移动或按下鼠标的鼠标位置是采用设备坐标。

<1>逻辑坐标系是面向DC的坐标系,这种坐标不考虑具体的设备类型,在绘图时,Windows会根据当前设置的映射模式将逻辑坐标转换为设备坐标。

<2>设备坐标系是面向物理设备的坐标系,这种坐标以像素或设备所能表示的最小长度单位为单位,X轴方向向右,Y轴方向向下。设备坐标系的原点位置(0, 0)不限定在设备显示区域的左上角。

设备坐标系分为屏幕坐标系、窗口坐标系和客户区坐标系三种相互独立的坐标系。

  屏幕坐标系以屏幕左上角为原点,一些与整个屏幕有关的函数均采用屏幕坐标,如GetCursorPos()、SetCursorPos()、CreateWindow()、MoveWindow()。弹出式菜单使用的也是屏幕坐标。

  窗口坐标系以窗口左上角为坐标原点,它包括窗口标题栏、菜单栏和工具栏等范围。

 客户区坐标系以窗口客户区左上角为原点,主要用于客户区的绘图输出和窗口消息的处理。鼠标消息的坐标参数使用客户区坐标,CDC类绘图成员函数使用与客户区坐标对应的逻辑坐标。

,它们在屏幕上用真实的物理像素表示

 屏幕坐标 Screen   coordinates:   原点(0,0)位于屏幕的左上角窗口坐标 Window   coordinates:   原点(0,0)位于窗口的左上角(包括非客户区,如标题条客户区坐标 Client-window   coordinates:   原点(0,0)位于客户窗口的左上角逻辑坐标是GDI函数在屏幕上显示数据所用的坐标,逻辑坐标除非与物理坐标相关联,否则没有义.windows依靠映射模式解释逻辑坐标.比如缺省的模式为MM_TEXT,该模式下,物理坐标与逻辑坐标是一对一的关系

   编程时,有时需要根据当前的具体情况进行三种设备坐标之间或与逻辑坐标的相互转换。

   MFC提供了两个函数CDC::DPtoLP()和CDC:: LPtoDP()用于设备坐标与逻辑坐标之间的相互转换。

   MFC提供了两个函数CWnd::ScreenToClient()和CWnd::ClientToScreen()用于屏幕坐标与客户区坐标的相互转换。

   映射模式确定了在绘制图形时所依据的坐标系,它定义了逻辑单位的实际大小、坐标增长方向,所有映射模式的坐标原点均在设备输出区域(如客户区或打印区)的左上角。此外,对于某些映射模式,用户还可以自定义窗口的长度和宽度,设置视图区的物理范围。

   映射模式使得程序员可不必考虑输出设备的具体设备坐标系,而在一个统一的逻辑坐标系中进行图形的绘制。

(二)坐标模式
为了在不同的领域使用逻辑坐标(通过SetMapMode设置),Windows提供了以下8种坐标模式(见前面介绍):
分别为
MM_TEXT
MM_HIENGLISH
MM_LOENGLISH
MM_HIMETRIC
MM_LOMETRIC
MM_TWIPS
MM_ANISOTROPIC
MM_ISOTROPIC。
(三)实例解析
1.建立以左上角为原点,X轴和Y轴为1000的坐标,如下图

我们可以用以下代码:
void CTtView::OnDraw(CDC pDC)
{
CTtDoc
pDoc = GetDocument();
ASSERT_VALID(pDoc);
CRect rect;
GetClientRect(&rect);
pDC->SetMapMode(MM_ANISOTROPIC);
pDC->SetViewportOrg(0,0);
pDC->SetViewportExt(rect.right,rect.bottom);
pDC->SetWindowOrg(0,0);
pDC->SetWindowExt(1000,1000);
//画图
pDC->MoveTo(50,50);
pDC->LineTo(50,950);
pDC->LineTo(950,950);
pDC->LineTo(50,50);
}
代码分析:
1). GetClientRect(&rect); 取得客户区矩形区域,将其存放在rect中。
2). 用pDC->SetMapMode(MM_ANISOTROPIC); 设置映射模式。
3). 通过pDC->SetViewportOrg(0,0); 与pDC->SetWindowOrg(0,0);设置设备坐标的原点与逻辑坐标的原点。
4). 通过pDC->SetViewportExt(rect.right,rect.bottom);和
pDC->SetWindowExt(1000,1000);来确定逻辑坐标下和设备坐标下的尺寸对应关系。
5). 在MM_ANISOTROPIC模式下,X轴单位和Y轴单位可以不相同。
6). 坐标方向的确定方法是如果逻辑窗范围和视口范围符号相同,则逻辑坐标的方向和视口的方向相同,即X轴向右为正,Y轴向下为正。
7). 如果将显示模式改为MM_ISOTROPIC,那么X轴单位和Y轴单位一定相同,感兴趣的读者可以自己试一下。
2.建立以视窗中心为原点的坐标并画一个三角形,如下图

用如下代码:
void CTtView::OnDraw(CDC pDC)
{
CTtDoc
pDoc = GetDocument();
ASSERT_VALID(pDoc);
CRect rect;
GetClientRect(&rect);
pDC->SetMapMode(MM_ANISOTROPIC);
pDC->SetViewportOrg(rect.right/2,rect.bottom/2);
pDC->SetViewportExt(rect.right,rect.bottom);
pDC->SetWindowOrg(0,0);
pDC->SetWindowExt(1000,-1000);
//画一个三角形
pDC->MoveTo(150,150);
pDC->LineTo(-150,-200);
pDC->LineTo(150,-150);
pDC->LineTo(150,150);
}
代码分析:
1). 用 pDC->SetViewportOrg(rect.right/2,rect.bottom/2); 设置视口的原点。
2). 用pDC->SetViewportExt(rect.right,rect.bottom);和pDC->SetWindowExt(1000,-1000);来确定设备坐标和逻辑坐标的单位对应关系。
3). 因为逻辑窗范围和视口范围的符号不一致,纵坐标取反,所以Y轴向上为正。
8.3.3绘图颜色
Windows中的颜色一般用4个字节表示(0BGR(整数序)或RGB0(字节序),因为Intel CPU的低位字节在前),Win32 API中定义了一个专门表示颜色索引值的变量类型COLORREF(windef.h):
typedef DWORD COLORREF; // 0x00bbggrr
和一个由红绿蓝三原色构造颜色值的宏RGB(wingdi.h):

#define RGB(r,g,b)
((COLORREF)(((BYTE)(r)|((WORD)((BYTE)(g))<<8))|(((dword)(byte)(b))<<16))) 1="" 2="" 3="" 4="" 5="" 6="" 7="" 8="" 9="" 10="" 11="" 12="" 13="" 14="" 15="" 16="" 其中,r、g、b为字节变量,分部对应于红、绿、蓝三原色,取值范围为0~255。rgb宏所对应的函数说明为:="" colorref="" rgb(="" byte="" bred,="" bgreen,="" bblue="" );="" 例如:="" red,="" gray;="" red="RGB(255," 0,="" 0);="" gray="RGB(128," 128,128);="" 在api中还定义了由colorref变量获取各个颜色分量的宏getxvalue(wingdi.h),它们对应的函数说明为(其中的dword="" rgb="" 等价于="" col):="" getrvalue(dword="" rgb);="" getgvalue(dword="" getbvalue(dword="" 8.3.4设置绘图属性="" 除了映射模式外,还有许多绘图属性可以设置,如背景、绘图方式、多边形填充方式、画弧方向、刷原点等。="" 一、="" 背景="" 背景(background)指绘图区域的底色,可以用cdc类中的成员函数来设置背景色和背景模式。="" 1.背景色="" 当背景模式为不透明时,背景色决定图元中空隙(如虚线中的空隙、条纹刷的空隙和文字的空隙)的颜色,可以使用cdc类的成员函数getbkcolor和setbkcolor来获得和设置当前的背景颜色:="" getbkcolor(="" )="" const;="" 返回当前的背景色="" virtual="" setbkcolor(="" crcolor="" 返回先前的背景色,若出错返回0x80000000,默认的背景色为白色。="" 2.背景模式="" 背景模式影响有空隙的图元中的空隙用什么办法填充。可以使用cdc类的成员函数getbkmode和setbkmode来获得和设置当前的背景模式(参见表8-8):="" int="" getbkmode(="" 返回当前背景模式="" setbkmode(="" nbkmode="" 返回先前背景模式="" 表8-8="" 背景模式nbkmode的取值="" nbkmode值="" 数值="" 名称="" 作用="" transparent="" 透明的="" 空隙处保持原背景图不变="" opaque="" 不透明的(默认值)="" 空隙用背景色填充="" 二、绘图模式="" 绘图模式(drawing="" mode)指前景色(及原有色)的混合方式,它决定当前画图的笔和刷的颜色(pbcol)如何与原有图的颜色(sccol)相结合而得到结果像素色(pixel)。="" 1.设置绘图模式="" 可使用cdc类的成员函数setrop2="" (其中的rop="Raster" operation,光栅操作)来设置绘图模式:="" setrop2(="" ndrawmode="" 其中,ndrawmode的可取值见表8-9。="" 表8-9="" 绘图模式ndrawmode的取值="" 符号常量="" 运算结果="" r2_black="" 黑色="" pixel="black" r2_notmergepen="" 或非="" |="" pbcol)="" r2_masknotpen="" 与反色="" &="" ~pbcol="" r2_notcopypen="" 反色覆盖="" r2_maskpennot="" 反色与="" pbcol="" r2_not="" 反色="" r2_xorpen="" 异或="" ^="" r2_notmaskpen="" 与非="" r2_maskpen="" 与="" r2_notxorpen="" 异或非="" r2_nop="" 不变="" r2_mergenotpen="" 或反色="" r2_copypen="" 覆盖="" r2_mergepennot="" 反色或="" r2_mergepen="" 或="" r2_white="" 白色="" 其中,r2_copypen(覆盖)为默认绘图模式,r2_xorpen(异或)较为常用。="" 2.画移动图形="" 为了能画移动的位置标识(如十字、一字等图案)和随鼠标移动画动态图形(如直线、矩形、椭圆),必须在不破坏原有背景图形的基础上移动这些图形。="" 移动图形采用的是异或画图方法,移动图形的过程为:异或画图、在原位置再异或化图(擦除)、在新位置异或画图、……。如:="" pgraypen="new" cpen(ps_dot,="" rgb(128,="" 128,="" 128));="" pdc-="">SetBkMode(TRANSPARENT);
pOldPen = pDC->SelectObject(pGrayPen);
pDC->SelectStockObject(NULL_BRUSH);
pDC->SetROP2(R2_XORPEN);
if (m_bErase) pDC->Ellipse(rect0);
pDC->Ellipse(rect);
pDC->SetROP2(R2_COPYPEN);
pDC->SelectObject(pOldPen);
rect0 = rect;
较完整的拖放动态画图的例子,可参照下面的“8.5.4 拖放画动态直线”部分。
三、其他属性
1.多边形填充方式
多边形填充模式(polygon-filling mode)决定复杂多边形的填充方式。
虽然GDI中没有画填充多边形的专门函数,但可以用(采用当前刷填充的)画多边形函数Polygon来代替:
BOOL Polygon( LPPOINT lpPoints, int nCount );
该函数在使用DC中的当前笔画完给定点集确定的边框后,会再用DC中的当前刷填充其内部区域。
在画填充多边形时,需要确定一个给定像素点是否在多边形内,常用的方法有两种(参见图8-12):
奇-偶规则(odd-even rule)——从任意给定位置p作一条射线,若与该射线相交的多边形边的数目为奇数,则p是多边形内部点,否则是外部点。例如图8-12中的A点的射线与多边形的边有1个交点为奇数,所以在多边形内部;而B点和C点都有2个交点为偶数,所以都在多边形外部。
非零环绕数规则(nonzero winding number rule)——使多边形的边变为矢量。将环绕数初始化为零。再从任意给定位置p作一条射线。当从p点沿射线方向移动时,对在每个方向上穿过射线的边计数,每当多边形的边从右到左穿过射线时,环绕数加1,从左到右时,环绕数减1。处理完多边形的所有相关边之后,若环绕数为非零,则p为内部点,否则,p是外部点。例如图8-12中的A点的射线穿过多边形的一条边,该边的矢量从射线的前进方向的左边穿到右边,环绕数为-1非0,所以A在多边形内部;而B点先+1后-1,最后环绕数为0,所以在多边形外部;而C点先-1再-1,最后环绕数为-2非0,所以也在多边形内部。
可见,在图2-12所示的五角星中心的C点,按奇-偶规则在多边形的外部,而按非零环绕数规则在多边形的内部,参见图8-13。
在GDI中,可以通过CDC的成员函数SetPolyFillMode来设置以这两种判断方法之一来填充多边形。下面是该函数的原型:
int SetPolyFillMode( int nPolyFillMode );
其中,多边形填充模式参数nPolyFillMode可取:
ALTERNATE(交替)——按奇偶规则填充(默认模式)
WINDING(环绕)——按非零环绕规则填充。
对简单图形,这两种模式的效果相同。但对复杂的有穿插的图形,结果可能不一样。例如(画五角星,参见图8-13):

// 定义五角星顶点数组
const int n = 5;
CPoint p1(100, 0), p2(195, 69), p3(159, 181), p4(41, 181), 
p5(5, 69), pm(0, 200)/* 用于向下平移ps2*/;
CPoint ps1[n] = {p1, p2, p3, p4, p5};
CPoint ps2[n] = {pm+p1, pm+p3, pm+p5, pm+p2, pm+p4};
CDC *pDC = GetDC(); // 获取DC
// 选入DC刷并设置成绿色
pDC->SelectStockObject(DC_BRUSH);
pDC->SetDCBrushColor(RGB(0, 128, 0));
// 用默认的交替模式画填充五角星
pDC->Polygon(ps1, n);
pDC->Polygon(ps2, n);
// 右平移点数组
pm.x = 200; pm.y = 0;
for(int i = 0; i < 5; i++) {
    ps1[i] += pm;  ps2[i] += pm;
}
// 将DC刷设置成红色
pDC->SetDCBrushColor(RGB(128, 0, 0));
// 设置环绕模式,重新画填充五角星
pDC->SetPolyFillMode(WINDING);
pDC->Polygon(ps1, n);
pDC->Polygon(ps2, n);
ReleaseDC(pDC);

还可使用CDC类的成员函数GetPolyFillMode来获取当前多边形的填充方式:
int GetPolyFillMode( ) const;
2.画弧方向
可使用CDC类的成员函数GetArcDirection和SetArcDirection来确定Arc、Chord、Pie等函数的画弧方向:
int GetArcDirection( ) const;
int SetArcDirection( int nArcDirection );
其中,nArcDirection可取值AD_COUNTERCLOCKWISE(逆时针方向,默认值)和AD_CLOCKWISE(顺时针方向)
3.刷原点
可使用CDC类的成员函数GetBrushOrg和SetBrushOrg来确定可填充绘图函数的条纹或图案刷的起点:(默认值为客户区左上角的坐标原点(0, 0))
CPoint GetBrushOrg( ) const;
CPoint SetBrushOrg( int x, int y );
CPoint SetBrushOrg( POINT point );
8.3.6几何对象的结构和类
为了使用绘图函数,应该先了解绘图所用到的几种表示几何对象的结构和类,包括点、大小和矩形,其中常用的是点和矩形。这些结构和类被分别定义在头文件windef.h和afxwin.h中。MFC中的几何对象类都是独立的类(不是CObject的派生类),是对API中对应结构的C++封装。

一、点
点(point)在API中的结构为POINT,对应的MFC类为CPoint。
1.点结构POINT
API中的点数据结构POINT用来表示一点的x、y坐标:
typedef struct tagPOINT { LONG x; LONG y; } POINT;
其中,类型LONG(32位整数)的定义为:typedef long LONG;
2.点类CPoint
MFC中的点类CPoint为一个没有基类的独立类,封装了POINT结构,有成员变量x和y,其构造函数有5种:
CPoint( ); // 默认
CPoint( int initX, int initY ); // 常用
CPoint( POINT initPt );
CPoint( SIZE initSize );
CPoint( LPARAM dwPoint ); // 低字设为x、高字设为y
CPoint类还定义了4个平移和设置的成员函数:
void Offset(int xOffset, int yOffset);
void Offset(POINT point);
void Offset(SIZE size);
void SetPoint(int X, int Y);
另外,CPoint类还重载了+、-、+=、-=、==、!= 等运算符来支持CPoint对象和CPoint、POINT、SIZE对象之间的运算。
二、大小
大小(size,尺寸)在API中的结构为SIZE,在MFC中的类为CSize。
1.大小结构SIZE
大小结构SIZE用来表示矩形的宽cx和高cy:
typedef struct tagSIZE { LONG cx; LONG cy; } SIZE;
2.大小类CSize
MFC中的大小类CSize也为一个没有基类的独立类,封装了SIZE结构,有成员变量cx和cy,其构造函数也有5种:
CSize( );
CSize( int initCX, int initCY );
CSize( SIZE initSize );
CSize( POINT initPt );
CSize( DWORD dwSize ); // 低字设为cx、高字设为cy
CSizet类也重载了+、-、+=、-=、==、!= 等运算符来支持CSize对象和CSize、POINT、SIZE、RECT对象之间的运算。
三、矩形
矩形(rectangle)在API中的结构为RECT,在MFC中的类为CRect。
1.矩形结构RECT
API中的矩形结构RECT定义了矩形的左上角与右下角的坐标:
typedef struct tagRECT { LONG left; LONG top; LONG right; LONG bottom;} RECT;
2.矩形类CRect
MFC中的矩形类CRect也为一个没有基类的独立类,封装了RECT结构,有成员变量left、top、right和bottom,其构造函数有6种:
CRect( ); // 默认
CRect( int l, int t, int r, int b ); // 常用
CRect( const RECT& srcRect );
CRect( LPCRECT lpSrcRect );
CRect( POINT point, SIZE size );
CRect( POINT topLeft, POINT bottomRight );
CRect类重载了=,+、-,+=、-=,==、!=,&、|,&=、|= 等运算符来支持CRect对象和CRect、POINT、SIZE、RECT对象之间的运算。还定义了转换符LPCRECT和LPRECT来自动完成CRect对象到矩形结构和类指针LPCRECT和LPRECT的转换。
CRect类中常用的属性和成员函数有:
int Width( ) const;
int Height( ) const;
CSize Size( ) const;
CPoint& TopLeft( );
CPoint& BottomRight( );
CPoint CenterPoint( ) const;
void SwapLeftRight();
BOOL IsRectEmpty( ) const;
BOOL PtInRect( POINT point ) const;
void SetRect( int x1, int y1, int x2, int y2 );
void SetRect(POINT topLeft, POINT bottomRight);
void OffsetRect(int x, int y);
void MoveToXY(int x, int y);
3.判断点是否在矩形中
有时需要判断某点(如鼠标位置)是否在某一矩形区域(如控件)中,这可以调用CRect类的PtInRect函数来做:
BOOL PtInRect( POINT point ) const;
该函数当点point在其矩形区域内时,返回真。注意,该矩形区域不包括矩形的右边界和底边界。例如:
CRect rect( 10, 10, 371, 267 );
void CDrawView::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
if ( rect.PtInRect( point ) ) … …
CView::OnLButtonUp(nFlags, point);
}
四、获得客户区大小
绘图一般都是在框架窗口的客户区(对应于视图类)进行,而客户区的大小在运行时可由用户动态改变,为了使绘制的图形能随窗口大小而自动改变,需先得到当前客户区大小的数据(宽和高)。
获取客户区大小的方法有如下两种:
1.OnSize
获取客户区大小的第一种方法是通过消息响应函数OnSize中获得。
可利用属性窗口的消息页,为视图类添加WM_SIZE消息的响应函数OnSize。该函数会在窗口第一次显示或窗口大小被改变时被系统调用。其输入参数中的cx和cy就是当前客户区的宽和高,可将它们赋值给类变量(如m_iW和m_iH)供绘图时使用。例如:
void CDrawView::OnSize(UINT nType, int cx, int cy) {
CView::OnSize(nType, cx, cy);
// TODO: 在此处添加消息处理程序代码
m_iW = cx; m_iH = cy;
}
其中,nType的值为:
 SIZE_MAXIMIZED(窗口已被最大化)
 SIZE_MINIMIZED(窗口已被最小化)
 SIZE_RESTORED(窗口已被改变大小)
 SIZE_MAXHIDE(其他窗口被最大化)
 SIZE_MAXSHOW(其他窗口从最大化还原)
2.GetClientRect
获取客户区大小的第二种方法是调用窗口类的获取客户区矩形成员函数GetClientRect来得到。具体做法是,在绘图前定义一个矩形结构变量rect,然后再调用CWnd类的成员函数GetClientRect来得到当前客户区矩形的数据,该函数的原型为:
void GetClientRect( LPRECT lpRect ) const;
其中,矩形结构的右(right)与底(bottom)就是客户区的宽与高(其左left与顶top都为0)。例如:
RECT rect;
GetClientRect(&rect);
int iW = rect.right, iH = rect.bottom;
8.4 画笔和画刷
8.4.1画笔
在Windows中,线状图必须用笔(pen)来画,所以线的颜色就由笔色来确定。在MFC中,笔的属性和功能由CPen类提供(CPen是CGDIObject的派生类)。
笔的创建与使用的步骤为:
1.创建笔对象
创建CPen类笔对象的方法有如下两种:
 使用非默认构造函数创建初始笔:
CPen( int nPenStyle, int nWidth, COLORREF crColor );
其中:
 nPenStyle为笔的风格,可取值见表8-3。
表8-3 笔风格nPenStyle值
符号常量 数值 风格名称 线例
PS_SOLID 0 实心

PS_DASH 1 虚线

PS_DOT 2 点线

PS_DASHDOT 3 虚点线

PS_DASHDOTDOT 4 虚点点线

PS_NULL 5 空笔
PS_INSIDEFRAME 6 框内
注意:1~4号笔风格只是在笔宽=0或1时有效,笔宽>1时总为实心的。
线例
符号常量 数值 风格名称 线例
PS_SOLID 0 实心

PS_DASH 1 虚线

PS_DOT 2 点线

PS_DASHDOT 3 虚点线

PS_DASHDOTDOT 4 虚点点线

PS_NULL 5 空笔
PS_INSIDEFRAME 6 框内

nWidth为笔宽,与映射模式有关,使用默认映射时为像素数,若nWidth = 0,则不论什么映射模式,笔宽都为一个像素。
crColor为笔的颜色值。
默认的笔为单像素宽的黑色实心笔。
例如:
CPen pGrayPen = new CPen(PS_SOLID, 0, RGB(128, 128, 128));
CPen grayPen(PS_SOLID, 0, RGB(128, 128, 128));
使用成员函数CreatePen(多次)创建笔:
BOOL CreatePen( int nPenStyle, int nWidth, COLORREF crColor );
可先用CPen类的默认构造函数创建一个空笔对象,然后再调用CreatePen创建具有新内容的笔,例如:
CPen grayPen;
grayPen.CreatePen(PS_SOLID, 0, RGB(128, 128, 128));
不过在用CreatePen创建新笔之前,必须先调用DeleteObject函数删除笔对象的内容。该函数是CPen的基类CGDIObject的成员函数,下面是它的原型:
BOOL DeleteObject( );
2.选笔入DC
为了能使用我们自己所创建的笔对象,必须先将它选入DC,这可以调用设备上下文类CDC的成员函数SelectObject来完成:
CPen
SelectObject( CPen pPen );
该函数的返回值为指向原来笔对象的指针(一般将其保存下来,供下次选出新笔时使用)。例如:
pOldPen = pDC->SelectObject(&pen);
另外,Windows中有一些预定义的笔对象(参见表8-4),可用CDC的另一成员函数SelectStockObject将其选入DC,其函数原型为:
virtual CGdiObject
SelectStockObject( int nIndex );
表8-4 预定义笔
符号常量 数值 笔
WHITE_PEN 6 白色笔
BLACK_PEN 7 黑色笔
NULL_PEN 8 空笔
DC_PEN 19 DC笔
其中,DC笔为单色实心笔,默认为黑色。是从Windows NT/2000开始新引进的,可用于直接设置笔色,参见本小节后面的第6部分。
例如:
pDC->SelectStockObject(BLACK_PEN);
3.绘制线状图
在将笔对象选入DC后,就可以使用CDC类中的绘图函数,利用DC中的笔对象,来画线状图及面状图的边线。如果不创建和选入笔对象,则使用的是DC中的默认笔,为单像素宽的实心黑色笔。
Windows中的线状图有直线、折线、矩形、(椭)圆(弧)等,详见8.5.3。
4.删除笔
不能直接删除已使用过的笔对象,必须先将它从DC中释放出来后才能删除。释放的方法是装入其他的笔对象(一般是重新装入原来的笔对象)。例如:
pDC->SelectObject(pOldPen);
删除笔对象的方法有如下几种:
删除笔内容:调用笔类CDC的成员函数DeleteObject删除笔的当前内容(但是未删除笔对象,以后可再用成员函数CreatePen在笔对象中继续创建新的笔内容)。例如:pen.DeleteObject();
删除笔对象:使用删除运算符delete将笔对象彻底删除,例如delete &pen;。
自动删除:若笔对象为局部变量,则在离开其作用域时,笔对象会被系统自动删除。
5.例子
下面为一段较完整地创建与使用笔的例子代码:
CDC pDC = GetDC(); // 获取DC
CPen pen,
pOldPen; // 定义笔对象和指针
// 创建10单位宽的绿色实心笔
pen.CreatePen(PS_SOLID, 10, RGB(0, 255, 0));
pOldPen = pDC->SelectObject(&pen); // 选入绿色笔
pDC->Rectangle(10, 10, 100, 100); // 画矩形
pDC->SelectObject(pOldPen); // 选出绿色笔
pen.DeleteObject(); // 删除绿色笔
// 创建单像素宽的红色虚线笔
pen.CreatePen(PS_DASH, 0, RGB(255, 0, 0));
pOldPen = pDC->SelectObject(&pen); // 选入红色笔
pDC->MoveTo(10, 10); pDC->LineTo(100, 100); // 画直线
ReleaseDC(pDC); // 释放DC
6.设置DC笔颜色
对Windows NT/2000及以上的版本,如果只需修改实心笔的颜色,而不需改变笔的粗细,则可以不用反复创建和选入新笔,而使用CDC类的成员函数SetDCPenColor,来直接设置DC笔的颜色:
COLORREF SetDCPenColor( COLORREF crColor ); // 返回原颜色值
对应的获取DC三笔颜色的成员函数为:
COLORREF GetDCPenColor( ) const;
不过这需要先调用CDC的成员函数SelectStockObject将DC笔选入DC后,才能使用SetDCPenColor函数来设置DC中笔的颜色。例如:
pDC->SelectStockObject(DC_PEN); // 选入DC笔
pDC->SetDCPenColor(RGB(255, 0, 0)); // 设置成红色
实例代码1:
Cpen
CDCpDC=GetDC();
CPen pen;
pen.CreatePen(PS_SOLID,5,RGB(255,0,0));
CPen
pOldPen=pDC->SelectObject(&pen);
pDC->LineTo(400,600);
pDC->SelectObject(pOldPen);
ReleaseDC(pDC); // 释放DC
实例代码2:
CDCpDC=GetDC();
CPen pen;
pen.CreatePen(PS_SOLID,5,RGB(255,0,0));
CPen
pOldPen=pDC->SelectObject(&pen);
CRect rect;
GetClientRect(rect); //获得用户区域大小
CPoint pts[4];
pts[0].x = rect.left + rect.Width()/2;
pts[0].y = rect.top;
pts[1].x = rect.right;
pts[1].y = rect.top + rect.Height()/2;
pts[2].x = pts[0].x;
pts[2].y = rect.bottom;
pts[3].x = rect.left;
pts[3].y = pts[1].y;
pDC->Polygon(pts,4);
pDC->SelectObject(pOldPen);
ReleaseDC(pDC); // 释放DC
8.4.2画刷
在Windows中,面状图必须用刷(brush)来填充,所以面色是由刷色来确定的。MFC中的刷类为CBrush(它也是CGDIObject的派生类),刷的创建与使用的步骤与笔的相似。
1.构造函数
CBrush类有4个构造函数,分别用于创建空刷、实心刷、条纹刷和图案刷:
CBrush( ); // 创建一个刷的空对象
CBrush( COLORREF crColor ); // 创建颜色为crColor的单色实心刷
CBrush( int nIndex, COLORREF crColor ); // 创建风格由nIndex指定且颜色为crColor的条纹(hatch,孵化/影线)刷,其中nIndex可取条纹风格(Hatch Styles)值见表8-5和图8-4。
表8-5 条纹风格nIndex值
符号常量 数值 风格
HS_HORIZONTAL 0 水平线
HS_VERTICAL 1 垂直线
HS_FDIAGONAL 2 正斜线
HS_BDIAGONAL 3 反斜线
HS_CROSS 4 十字线(正网格)
HS_DIAGCROSS 5 斜十字线(斜网格)

水平线 垂直线 正斜线 反斜线 十字线 斜十字线
图8-4 条纹刷的种类
 CBrush(CBitmap pBitmap); // 创建位图为pBitmap的图案(pattern)刷
默认的刷为白色实心刷。
2.创建函数
与构造函数相对应,CBrush类有多个创建不同类型刷的成员函数,如:
BOOL CreateSolidBrush( COLORREF crColor ); // 创建(单色)实心刷
BOOL CreateHatchBrush( int nIndex, COLORREF crColor ); // 创建条纹刷
BOOL CreatePatternBrush( CBitmap
pBitmap ); // 创建图案刷
3.预定义刷
预定义的刷对象见表8-6有:
表8-6 预定义刷
符号常量 数值 刷
WHITE_BRUSH 0 白刷(默认)
LTGRAY_BRUSH 1 亮灰刷
GRAY_BRUSH 2 灰刷
DKGRAY_BRUSH 3 暗灰刷
BLACK_BRUSH 4 黑刷
HOLLOW_BRUSH 5 空刷
NULL_BRUSH 5 空刷
DC_BRUSH 18 DC刷
其中,DC刷为单色实心刷,默认为白色。是从Windows NT/2000开始新引进的,可用于直接设置刷色,参见本小节后面的第5部分。
3.选入刷
与笔一样,可以用函数SelectObject或SelectStockObject将自定义刷或预定义刷选入DC中,供绘面状图时使用。如果未选入任何刷,则系统会使用DC中的默认刷来绘图,默认刷为白色实心刷。
4.例子
下面是一个创建、选入并使用自定义实心刷的简单例子代码段:
CRect rect(10, 10, 210, 160);
CBrush brush(RGB(0, 255, 0));
CDC pDC = GetDC();
pDC->SelectObject(&brush);
pDC->Rectangle(&rect);
ReleaseDC(pDC);
5.设置DC刷色
对Windows NT/2000及以上的版本,如果只需修改实心刷的颜色,而不改变刷的种类和风格,则可以不用反复创建和选入新刷,而使用CDC类的成员函数SetDCBrushColor,来直接设置DC刷的颜色:
COLORREF SetDCBrushColor( COLORREF crColor ); // 返回原颜色值
对应的获取DC中刷颜色的成员函数为:
COLORREF GetDCBrushColor( ) const;
与设置DC笔类似,也需要先调用CDC的成员函数SelectStockObject将DC刷选入DC后,才能使用SetDCBrushColor函数来设置DC中刷的颜色。例如:
pDC->SelectStockObject(DC_BRUSH); // 选入DC刷
pDC->SetDCBrushColor(RGB(255, 0, 0)); // 设置成红色
6.例子
CDC
pDC=GetDC();
CBrush brush(HS_CROSS,RGB(0,255,0));
CBrush pOldBrush=pDC->SelectObject(&brush);
pDC->Ellipse(50,50,300,400);
pDC->SelectObject(pOldBrush);
ReleaseDC(pDC); // 释放DC
7.画笔画刷例子(在控件上绘图)
本实例建立一个项目,项目功能是点击菜单“波形线”,弹出一个对话框,点击按钮“画波形”则在对话框的一个图片控件的黑色背景上画出绿色波形线。
(1)建立一个项目,名字为boxing,建立一个菜单“画波形线”。
(2)插入一个对话框,在对话框上添加一个picture control控件,修改该控件的id为IDC_PIC(如果不修改id,可能无法为其添加静态变量),并为其添加一个静态变量(static)m_pic,添加一个按钮,caption属性改为波形线,为其添加函数(可通过直接双击该按钮添加函数)。
(3)在按钮函数里添加代码画波形线:
CRect rectPicture;
CPen newPen;//用于创建新画笔
CPen
pOldPen;//用于存放旧画笔
CBrush newBrush;//用于创建新画刷
CBrushpOldBrush;//用于存放旧画刷
float fDeltaX,fDeltaY;
float fDeltaX,fDeltaY;
int nX,nY;
CWnd
pWin = GetDlgItem(IDC_PIC);
CDC pDC = pWin->GetDC();
//获取绘图控件的客户区坐标
//(客户区坐标以窗口的左上角为原点,这区别于以屏幕左上角为原点的屏幕坐标)
m_pic.GetClientRect(&rectPicture);
//创建黑色新画刷
newBrush.CreateSolidBrush(RGB(0,0,0));
//选择新画刷,并将旧画刷的指针保存到pOldBrush
pOldBrush=pDC->SelectObject(&newBrush);
//以黑色画刷为绘图控件填充黑色,形成黑色背景
pDC->Rectangle(rectPicture);
//恢复旧画刷
pDC->SelectObject(pOldBrush);
//删除新画刷
newBrush.DeleteObject();
//创建实心画笔,粗度为1,颜色为绿色
newPen.CreatePen(PS_SOLID,1,RGB(0,255,0));
//选择新画笔,并将旧画笔的指针保存到pOldPen
pOldPen=pDC->SelectObject(&newPen);
//将当前点移动到绘图控件窗口的左下角,以此为波形的起始点
pDC->MoveTo(rectPicture.left,rectPicture.bottom);
//计算fDeltaX和fDeltaY
fDeltaX=(float)rectPicture.Width()/(100);
fDeltaY=(float)rectPicture.Height()/100;
//计算m_nzValues数组中每个点对应的坐标位置,并依次连接,最终形成曲线
for(int i=0;i<100;i++)
{
nX=rectPicture.left+(int)(i
fDeltaX);
//nY=rectPicture.bottom-(int)(m_nzValues[i]*fDeltaY);
//(rand()%rectPicture.Height()为产生图片框高度值内的随机数
nY=rectPicture.bottom-(int)(rand()%rectPicture.Height());
pDC->LineTo(nX,nY);
}
//恢复旧画笔
pDC->SelectObject(pOldPen);
//删除新画笔、画刷
newPen.DeleteObject();
newBrush.DeleteObject();
ReleaseDC(pDC); // 释放DC
(4)调试运行观察结果
运行正确结果应该为:

8.5 绘图
8.5.1 绘图步骤
图8-11描述了一般MFC绘图的基本步骤(及相关的类型和函数)。

图8-11 绘图的基本步骤
(其中:为PEN或BRUSH,为Pen或Brush, 为Pen或SolidBrush、HatchBrush、PatternBrush)
8.5.2 画像素点
在Windows中,像素(pixel)的颜色是直接由CDC类的成员函数SetPixel来设置的,该函数的原型为:
COLORREF SetPixel( int x, int y, COLORREF crColor );
COLORREF SetPixel( POINT point, COLORREF crColor );
其中,x与y分别为像素点的横坐标与纵坐标,crColor为像素的颜色值。例如:
pDC->SetPixel(10, 10, RGB(0, 255, 0));
另外,还可以用CDC的成员函数
COLORREF GetPixel( int x, int y ) const;
COLORREF GetPixel( POINT point ) const;
来获得指定点(x, y)或point的颜色。例如:
COLORREF col;
col = pDC->GetPixel(10, 10);
画像素点就是设置像素点的颜色,可由CDC的成员函数SetPixel来做。
8.5.3 画线状图
在众多的绘图状态中,有一个是当前位置(current position),有些画线状图的函数(如MoveTo、LineTo和AngleArc)与它有关。
在Windows中,线状图必须用笔来画(笔的创建与使用见前面的8.3.3),下面是CDC类中可以绘制线状图的常用成员函数:
 当前位置——设置当前位置为(x, y)或point:(返回值为原当前位置的坐标):
CPoint MoveTo( int x, int y ); 或 CPoint MoveTo( POINT point );
 画线——使用DC中的笔,从当前位置画线到点(x, y)或point(成功返回非0值):
BOOL LineTo( int x, int y ); 或BOOL LineTo( POINT point );
你可以自己定义一个画线函数,如:
BOOL Line(CDC pDC, int x1, int y1, int x2, int y2 ) {
pDC-> MoveTo(x1, y1);
return pDC-> LineTo(x2, y2);
}

BOOL Line(CDC
pDC, POINT p1, POINT p2 ) {
pDC-> MoveTo(p1);
return pDC-> LineTo(p2);
}
函数成功时,设置当前位值为(x, y)或point。
 画折线——使用DC中的笔,依次将点数组lpPoints中的nCount(≥2)个点连接起来,形成一条折线:
BOOL Polyline( LPPOINT lpPoints, int nCount );
该函数不使用和设置当前位置。
画多边形——似画折线,但还会将最后的点与第一个点相连形成多边形,并用DC中的刷填充其内部区域:
BOOL Polygon( LPPOINT lpPoints, int nCount );
该函数也不使用和设置当前位置。
画矩形——使用DC中的笔,画左上角为(x1, y1)、右下角为(x2, y2)或范围为lpRect的矩形的边线,并用DC中的刷填充其内部区域:
BOOL Rectangle( int x1, int y1, int x2, int y2 ); 或
BOOL Rectangle( LPCRECT lpRect );
该函数也不使用和设置当前位置。有时需要根据用户给定的两个任意点(如p0和point)来重新构造左上角和右下角的点,例如:
rect = CRect(min(p0.x, point.x), min(p0.y, point.y),
max(p0.x, point.x), max(p0.y, point.y));
画圆角矩形——使用DC中的笔,画左上角为(x1, y1)、右下角为(x2, y2)或范围为
lpRect的矩形的边线,并用宽为x3或point.x、高为y3或point.y矩形的内接椭圆倒角(参见图8-9 a)),再用DC中的刷填充其内部区域:
BOOL RoundRect( int x1, int y1, int x2, int y2, int x3, int y3 );
BOOL RoundRect( LPCRECT lpRect, POINT point );
该函数也不使用和设置当前位置。例如:
int d = min(rect.Width(), rect.Height()) / 4;
pDC-> RoundRect(rect, CPoint(d, d));

a) 圆角矩形 b) 椭圆
图8-9 圆角矩形与椭圆
画(椭)圆——使用DC中的笔,在左上角为(x1, y1)、右下角为(x2, y2)或范围为lpRect的矩形中画内接(椭)圆的边线(当边线宽度超过1时会向外延伸,参见图8-9 b)),并用DC中的刷填充其内部区域:
BOOL Ellipse( int x1, int y1, int x2, int y2 );
BOOL Ellipse( LPCRECT lpRect );
该函数也不使用和设置当前位置。注意,CDC中没有画圆的专用函数,圆是作为椭圆的(宽高相等)特例来画的。从此函数很容易定义自己的画圆函数,例如:
BOOL Circle( CDC
pDC, int x, int y, int r ) {
return pDC->Ellipse( x - r, y - r, x + r, y + r );
}

BOOL Circle( CDC *pDC, POINT p, int r ) {
return pDC->Ellipse( p.x - r, p.y - r, p.x + r, p.y + r );
}
画弧——使用DC中的笔,在由(含义同画椭圆的)(x1, y1)与(x2, y2)或lpRect确定的椭圆上,以(x3, y3)或ptStart为起点、(x4, y4)或ptEnd为终点,按逆时针方向(可以用CDC的成员函数SetArcDirection修改成顺时针方向)旋转画弧(参见图8-10 a)):
BOOL Arc( int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4 );
BOOL Arc( LPCRECT lpRect, POINT ptStart, POINT ptEnd );
BOOL ArcTo(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4);
BOOL ArcTo(LPCRECT lpRect, POINT ptStart, POINT ptEnd);
该函数也不使用和设置当前位置。

a) 弧与弓弦 b) 直线段和圆弧
图8-10 弧与弓弦、直线段和圆弧
画弓弦——参数的含义同上,只是用一根弦连接弧的起点和终点,形成一个弓形(参见图8-10 a)),并用DC中的刷填充其内部区域:
BOOL Chord( int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4 );
BOOL Chord( LPCRECT lpRect, POINT ptStart, POINT ptEnd );
该函数也不使用和设置当前位置。
画直线段和圆弧——从当前位置画一直线段到弧的起点,并画一指定圆弧(其中(x, y)为圆心、nRadius为半径、fStartAngle为起始角度、fSweepAngle为弧段跨角,参见图8-10 b)):
BOOL AngleArc(int x, int y, int nRadius, float fStartAngle, float fSweepAngle);
函数成功时,设置当前位置为弧的终点。
8.5.4 拖放画动态直线
下面是一个较完整的拖放动态画直线的例子,可用于本章的交互绘图的必做作业中。其中粗体字为手工添加的部分:
// 类变量
class CDrawView : public CView {
//……
// 属性
public:
//……
BOOL m_bLButtonDown, // 判断是否按下左鼠标键
m_bErase; // 是否需要擦除图形的类变量
CPoint p0, pm; // 记录直线起点和动态终点的类变量
CPen pGrayPen, pLinePen; // 定义灰色和直线笔
//……
}
// 构造函数
CDrawView::CDrawView() {
m_bLButtonDown = FALSE; // 设左鼠标键按下为假
m_bErase = FALSE; // 设需要擦除为假
pGrayPen = new CPen(PS_DOT, 0, RGB(128, 128, 128)); // 创建灰色点线笔
pLinePen = new CPen(PS_SOLID, 10, RGB(255, 0, 0)); // 创建红色的直线笔
}
// 鼠标消息响应函数
void CDrawView::OnLButtonDown(UINT nFlags, CPoint point) {
// TODO: 在此添加消息处理程序代码和/或调用默认值
SetCapture(); // 设置鼠标捕获
m_bLButtonDown = TRUE; // 设左鼠标键按下为真
p0 = point; // 保存直线的起点
pm = p0; // 让直线的终点等于起点
CView::OnLButtonDown(nFlags, point);
}
void CDrawView::OnMouseMove(UINT nFlags, CPoint point) {
SetCursor(LoadCursor(NULL, IDC_CROSS)); // 设置鼠标为十字
if (m_bLButtonDown) { // 左鼠标键按下为真
CDC pDC = GetDC(); // 获取设备上下文
pDC->SelectObject(pGrayPen); // 选取灰色点线笔
pDC->SetROP2(R2_XORPEN); // 设置为异或绘图方式
pDC->SetBkMode(TRANSPARENT); // 设置透明背景模式
//pDC->SelectStockObject(NULL_BRUSH); // 选入空刷
// 用于动态画封闭图形(如矩形、椭圆等)
if (m_bErase) { // 需要擦除为真
pDC->MoveTo(p0); pDC->LineTo(pm); // 擦除原直线
}
else // 需要擦除为假
m_bErase = TRUE; // 设需要擦除为真
pDC->MoveTo(p0); pDC->LineTo(point); // 绘制新直线
pm = point; // 更新老终点
ReleaseDC(pDC); // 释放设备上下文
}
CView::OnMouseMove(nFlags, point);
}
void CDrawView::OnLButtonUp(UINT nFlags, CPoint point) {
ReleaseCapture(); // 释放鼠标捕获
if (m_bLButtonDown) { // 左鼠标键按下为真
CDC
pDC = GetDC(); // 获取设备上下文
pDC->SelectObject(pGrayPen); // 选入灰色点线笔
pDC->SetROP2(R2_XORPEN); // 设置为异或绘图方式
pDC->SetBkMode(TRANSPARENT); // 设置透明背景模式
// pDC->SelectStockObject(NULL_BRUSH); // 选入空刷
// 用于动态画封闭图形(如矩形、椭圆等)
pDC->MoveTo(p0); pDC->LineTo(pm); // 擦除原直线
pDC->SelectObject(pLinePen); // 选择直线笔
pDC->SetROP2(R2_COPYPEN); // 设置为覆盖绘图方式
pDC->MoveTo(p0); pDC->LineTo(point); // 绘制最终的直线
m_bLButtonDown = FALSE; // 重设左鼠标键按下为假
m_bErase = FALSE; // 重需要擦除为假
ReleaseDC(pDC); // 释放设备上下文
}
CView::OnLButtonUp(nFlags, point);
}
图8-14是鼠标交互拖放动态画图的主要步骤图:

图8-14 拖放动态画图的主要步骤
其中移动图形(OnMouseMove)和绘制用户图形(成图,OnLButtonUp)的主要步骤分别见图8-15和16。

图8-15 移动图形的主要步骤

图8-16 成图的主要步骤
8.5.5 画填充图
在Windows中,面状图必须用刷来填充(刷的创建与使用见前面8.3.4)。上面8.4.3中的Polygon、Rectangle、Ellipse和Chord等画闭合线状图的函数,只要DC中的刷不是空刷,都可以用来画对应的面状图(边线用当前笔画,内部用当前刷填充)。下面介绍的是CDC类中只能绘制面状图的其他常用成员函数:
 画填充矩形——用指定的刷pBrush画一个以lpRect为区域的填充矩形,无边线,填充区域包括矩形的左边界和上边界,但不包括矩形的右边界和下边界:
void FillRect( LPCRECT lpRect, CBrush pBrush );
画单色填充矩形——似FillRect,但只能填充单色,不能填充条纹和图案:
void FillSolidRect( LPCRECT lpRect, COLORREF clr );
void FillSolidRect( int x, int y, int cx, int cy, COLORREF clr );
画饼图(扇形)——参数含义同Arc,但将起点和终点都与外接矩形的中心相连接,形成一个扇形区域,用DC中的刷填充整个扇形区域,无另外的边线:
BOOL Pie( int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4 );
BOOL Pie( LPCRECT lpRect, POINT ptStart, POINT ptEnd );
画拖动的矩形——先擦除线宽为sizeLast、填充刷为pBrushLast的原矩形lpRectLast,然后再以线宽为size、填充刷为pBrush画新矩形lpRectLast。矩形的边框用灰色的点虚线画,默认的填充刷为空刷:
void DrawDragRect( LPCRECT lpRect, SIZE size, LPCRECT lpRectLast,
SIZE sizeLast, CBrush
pBrush = NULL, CBrush pBrushLast = NULL );
如:pDC->DrawDragRect(rect, size, rect0, size);
填充区域——有如下两种填充区域的函数:
使用当前刷从点(x, y)开始向四周填充到颜色为crColor的边界:
BOOL FloodFill(int x, int y, COLORREF crColor); // 成功返回非0
使用当前刷从点(x, y)开始向四周填充:
BOOL ExtFloodFill(int x, int y, COLORREF crColor,
UINT nFillType); // 成功返回非0
nFillType = FLOODFILLBORDER:填充到颜色为crColor的边界(同FloodFill);(用于填充内部颜色不同但边界颜色相同的区域)
nFillType = FLOODFILLSURFACE:填充所有颜色为crColor的点,直到碰到非crColor颜色的点为止。(点(x, y)的颜色也必须为crColor),(用于填充内部颜色相同但边界颜色可以不同的区域)。例如:
pDC->ExtFloodFill(point.x, point.y,
pDC->GetPixel(point), FLOODFILLSURFACE);
8.5.6 清屏
Windows API和MFC中都没有提供专门的GDI清屏函数,可以依次调用CWnd的下面两个函数调用来完成该功能:
void Invalidate(BOOL bErase = TRUE); // 使窗口内容无效
void UpdateWindow( ); // 更新窗口
或等价地调用一个CWnd的函数
BOOL RedrawWindow(
LPCRECT lpRectUpdate = NULL,
CRgn
prgnUpdate = NULL,
UINT flags = RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASE
);
来完成。这两组函数都是先用当前背景色(默认为白色)填充窗口,再请求操作系统重绘窗口(调用OnDraw或OnPaint函数)。例如(菜单项ID_CLEAR的事件处理函数):
CDrawView::OnClear() { // 调用OnDraw来清屏
//Invalidate(); UpdateWindow( );
RedrawWindow( );
}
也可以用画填充背景色矩形的方法来手工直接清屏,例如:
RECT rect;
GetClientRect(&rect);
pDC->FillSolidRect(&rect, RGB(255, 255, 255));
8.5.7 在控件上绘图
在对话框类的OnPaint函数中对图片框架控件绘图相对容易,而在其他控件上绘图,则必须依次调用窗口的无效和更新函数后,再调用各种CDC类的成员函数进行绘图。本小节内容,可用于本章的调色板选做作业。
1.在图片框架控件上绘图
图片框架控件指Frame类型的图片控件(Picture Control,一种静态文本框),可通过向对话框资源中添加图片控件,并设置其Type属性为Frame来得到。
可在对话框的绘图消息响应函数OnPaint或其他函数中,用CWnd类的成员函数GetDlgItem来获得图片控件的窗口对象,再用CWnd类的成员函数GetDC(原型为CDC GetDC( );)由窗口对象得到绘图环境DC,然后就可以用该DC在控件中画图。例如(在ID为IDC_HUESAT的图片控件上画调色板)
void CPaletteDlg::OnPaint()
{
……
CWnd
pWin = GetDlgItem(IDC_HUESAT);
CDC pDC = pWin->GetDC();
……
pDC->SetPixel(i, j, RGB(r, g, b));
……
}
2.在其他控件上绘图
在非Frame类型静态控件上绘图,必须先按顺序依次调用CWnd类的Invalidate和UpdateWindow函数后,再开始用DC画图。例如,在一个按钮(IDC_COLOR)上绘图:
CWnd
pWnd = GetDlgItem(IDC_COLOR); // 获取控件的窗口对象
CDC pDC = pWnd->GetDC(); // 获取控件的DC对象
CRect rect; // 定义矩形对象
pWnd->GetClientRect(&rect); // 获取控件窗口的客户区矩形
CBrush brush(m_crCol); // 定义并创建刷对象(m_crCol为颜色类变量)
pWnd->Invalidate(); // 使控件窗口无效
pWnd->UpdateWindow(); // 更新控件窗口
pDC->FillRect(&rect, &brush); // 绘制填充矩形
3.若干说明
下面是几点与控件上绘图有关的说明:
1) 为了使在运行时能够不断及时更新控件的显示(主要是自己加的显式代码),可以将自己绘制控件的所有代码都全部加入对话框类的消息响应函数OnPaint中。在需要时(例如在绘图参数修改后),自己调用CWnd的Invalidate和UpdateWindow函数,请求系统刷新对话框和控件的显示。因为控件也是窗口,传统的控件类都是CWnd的派生类。所以在对话框和控件中,可以像在视图类中一样,调用各种CWnd的成员函数。
2) 一般的对话框类,默认情况下都没有自动生成OnPaint函数。可以自己在对话框类中添加WM_PAINT消息的响应函数OnPaint来进行一些绘图工作。
3) 为了在鼠标指向按钮时,让按钮上自己绘制的图形不被消去,可以设置按钮控件的“Owner Draw”属性为“True”。
4) 如果希望非按钮控件(如图片控件和静态文本等),也可以响应鼠标消息(如单击、双击等),需要设置控件的“Notify”属性为“True”。
5) 使用OnPaint函数在对话框客户区的空白处(无控件的地方)绘制自己的图形,必须(利用注释符)屏蔽掉其中默认自动生成的对对话框基类的OnPaint函数的调用:
//CDialog::OnPaint();
6) 对话框的背景色,可以以COLOR_BTNFACE为数如参数调用CWnd类的成员函数GetSysColor得到,其函数原型为:
DWORD GetSysColor( int nIndex);
例如:
COLORREF col = GetSysColor(COLOR_BTNFACE);
可以在对话框资源中放置图片控件,并对其类型属性选Frame。可在对话框的绘图消息响应函数OnPaint或其他函数中,用CWnd类的函数GetDlgItem:
CWnd
GetDlgItem( int nID ) const;
来获得图片控件的窗口对象,再用函数GetDC:
CDC* GetDC( );
由窗口对象得到DC,然后就可以用该DC在控件中画图。如(在ID为IDC_HUESAT的图片控件上画调色板)。
4.综合实例
本实例要求建立一个MFC应用程序,点击菜单弹出一个对话框(非模态对话框),对话框上放置一个图片框,两个文本框(显示x,y坐标),两个滑动条(设置x,y坐标),要求移动滑动条,相应坐标显示在两个文本框内,同时在图片框内实时按照设置的坐标绘制直线。
步骤如下:
(1)建立MFC项目:项目名字为pictu。
(2)添加菜单,绘图-滑块图片框画直线
(3)添加对话框并在对话框上添加相应控件
(4)添加代码
(5)编译运行
8.6 文本绘制

CFont

CPalette

8.7 位图
CBitmap

8.8 图标和光标
CRgn

8.9 图像处理

9 文件处理

10 数据库编程

请我吃辣条吧!