为了能选用不同的字体和大小来输出文本串,须使用作为GDI对象的CFont类。Windows和常用的字处理软件(如Word)、绘图软件(如CorelDraw)等应用软件会提供多种与设备无关的字体,主要是TrueType轮廓字体。
CFont类是CGDIObject的派生类:CObject → CGDIObject → CFont。只有一个缺省构造函数CFont( ); 必须用下列字体创建成员函数CreateFont[Indirect]或CreatePointFont [Indirect]来初始化。
其中的CreatePointFont提供了创建字体的一种简单方法:
BOOL CreatePointFont( int nPointSize, LPCTSTR lpszFaceName, CDC* pDC = NULL );
l nPointSize为字体的大小,以0.1点(像素/墨点/磅数)为单位,如汉字的字号与nPointSize值及磅数的对应关系见下表:
汉字字号 |
nPointSize值 |
磅数 |
汉字字号 |
nPointSize值 |
磅数 |
初号 |
420 |
42 |
四号 |
140 |
14 |
小初 |
360 |
36 |
小四 |
120 |
12 |
一号 |
260 |
26 |
五号 |
105 |
10.5 |
小一 |
240 |
24 |
小五 |
90 |
9 |
二号 |
220 |
22 |
六号 |
75 |
7.5 |
小二 |
180 |
18 |
小六 |
65 |
6.5 |
三号 |
160 |
16 |
七号 |
55 |
5.5 |
小三 |
150 |
15 |
八号 |
50 |
5 |
l lpszFaceName为字体名称字符串的指针
l 若pDC非空,则系统会将设备单位点自动转换为pDC中的映射模式所指定的逻辑单位
如
CFont font;
font.CreatePointFont(160, “宋体”);
注意:CFont类的这些逻辑字体创建函数,并不是从无到有创建一个新的GDI字体,而只是从GDI的物理字体库中选择与所设置参数最匹配的字体。
为了方便用户选择各种字体参数,可使用字体公用对话框:
汉字的字号与磅数
字体公用对话框
使用字体公用对话框需要用到CFontDialog类。CFontDialog类的构造函数为
CFontDialog( LPLOGFONT lplfInitial = NULL, DWORD dwFlags = CF_EFFECTS | CF_SCREENFONTS, CDC* pdcPrinter = NULL, CWnd* pParentWnd = NULL );
其中,lplfInitial为逻辑字体结构的指针(可用CFont的成员函数GetLogFont来获得,参见《Windows程序设计》),dwFlags为对话框的可选参数,CF_EFFECTS表示对话框中有删除线和下划线复选框与选择颜色的下拉式组合框,CF_SCREENFONTS则使得对话框中只列出系统支持的显示字体。CFontDialog类的常用成员函数有:
virtual int DoModal( ); // 显示对话框,返回IDOK或IDCANCEL
CString GetFaceName( ) const; // 返回字体名称串
int GetSize( ) const; // 返回所选择的字体大小,以0.1点为单位
COLORREF GetColor( ) const; // 返回所选择的字体颜色
常用的文本输出函数有TextOut、DrawText和ExtTextOut,它们都是CDC类的成员函数。下面只介绍最简单的TextOut:
BOOL TextOut( int x, int y, const CString& str );
其中,x与y为显示串的左上角坐标,str为要显示的文本串。如:
pDC->TextOut(10, 10, L"Test text");
还可以使用CDC类的成员函数SetTextColor和SetBkColor来分别设置输出文本的前景色和背景色:(缺省的前景色为黑色,背景色空)
virtual COLORREF SetTextColor( COLORREF crColor );
virtual COLORREF SetBkColor( COLORREF crColor );
如:
pDC->SetTextColor(RGB(0, 128, 0)); pDC->TextOut(10, 30, L"Test text");
pDC->SetBkColor(RGB(0, 0, 128)); pDC->TextOut(10, 50, L"Test text");
创建名为Font的MFC单文档应用程序,在视图类中添加若干字体参数类变量:
CFont font;
CString m_sFontName;
int m_iFontSize;
COLORREF m_colTextColor;
CString m_sStr;
并在构造函数中对它们进行初始化:
m_sFontName = L"宋体";
m_iFontSize = 120;
font.CreatePointFont(m_iFontSize, m_sFontName);
m_colTextColor = RGB(0,0,0);
m_sStr = L"Test text 测试文本";
添加字体设置菜单项ID_FONT_SET及其响应函数,在里面打开公用字体对话框,将用户选择的字体参数保存到视图类的类变量中,创建新的字体对象,并调用重绘窗口函数:
void CFontView::OnFontSet() {
// TODO: 在此添加命令处理程序代码
LOGFONT lf;
font.GetLogFont(&lf);
CFontDialog fontDlg(&lf);
fontDlg.m_cf.rgbColors = m_colTextColor;
if (fontDlg.DoModal() == IDOK)
{
m_sFontName = fontDlg.GetFaceName();
m_iFontSize = fontDlg.GetSize();
m_colTextColor = fontDlg.GetColor();
font.DeleteObject();
font.CreatePointFont(m_iFontSize, m_sFontName);
RedrawWindow();
}
}
创建文本串输入对话框的资源,添加对应的对话框类和编辑控件变量。添加字符串输入菜单项ID_STRING_INPUT及其响应函数,在里面打开输入对话框,将用户输入的字符串保存到视图类的对应类变量中:
void CFontView::OnStringInput() {
// TODO: 在此添加命令处理程序代码
CInputDlg dlg;
dlg.m_sStr = m_sStr;
if (dlg.DoModal() == IDOK) {
m_sStr = dlg.m_sStr;
RedrawWindow();
}
}
在视图类的OnDraw函数中,选入所设置的字体、设置文本颜色、输出用户的字符串:
void CFontView::OnDraw(CDC* pDC)
{
CFontDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc) return;
// TODO: 在此处为本机数据添加绘制代码
pDC->SelectObject(&font);
pDC->SetTextColor(m_colTextColor);
pDC->TextOut(20, 20, m_sStr);
}
下面是该例程序的编写步骤框图:
在建立项目 (Project)时,选择Siggle document (SDI单文档界面)或Multiple documents(MDI多文档界面,缺省)而不是Dialog based(基于对话框)的应用程序,并且选择缺省的Document/View architecture suport?(支持文档-视图体系)的复选框,建立支持文档/视图体系的项目。
应用程序框架会自动生成应用程序类C*App、文档类C*Doc、主框架窗口类CMainFrame、视图类C*View,对多文档界面还有子框架窗口类CChildFrame。
在应用程序框架自动生成的C*Doc类的Serialize(系列化)成员函数中使用其输入参数——文档类CArchive的对象ar——来读写文件,读写方法似文件流操作。如
void CWaveDoc::Serialize(CArchive& ar) {
if (ar.IsStoring()) {
// TODO: add storing code here 写文件
... ...
ar << id;
ar << fileLen;
... ...
}
else {
// TODO: add loading code here 读文件
... ...
ar >> id;
ar >> fileLen;
... ...
}
}
注意,用<<或>>输出或输入的为二进制数据;为了输出或输入字符串,可以用CArchive类的成员函数:void WriteString( LPCTSTR lpsz ); 或Bool ReadString(CString& rString );。如:
char str[80];
sprintf(str, "%d区(%XA1~%XFE):/r/n", a, c1, c1);
ar.WriteString(str);
可将重要的读入数据作为文档类的类变量或数组,供视图类的代码访问和图形输出,也可供文档类自己在写入文件时使用。
名称 |
类型 |
字节数 |
定义(windef.h) |
字节 |
BYTE |
1B |
typedef unsigned char BYTE; |
字 |
WORD |
2B |
typedef unsigned short WORD; |
双字 |
DWORD |
4B |
typedef unsigned long DWORD; |
无符号整数 |
UINT |
2B(Win16) / 4B(Win32) |
typedef unsigned int UINT; |
为了简化RIFF文件中的4字符标识的读写与比较,Windows SDK在多媒体头文件mmsystem.h中定义了类型FOURCC(Four-Character Code四字符代码):
typedef DWORD FOURCC;
及其构造宏(用于将4个字符转换成一个FOURCC数据)
FOURCC mmioFOURCC(CHAR ch0, CHAR ch1, CHAR ch2, CHAR ch3);
其定义为MAKEFOURCC宏:
#define mmioFOURCC(ch0, ch1, ch2, ch3) MAKEFOURCC(ch0, ch1, ch2, ch3);
而MAKEFOURCC宏定义为:
#define MAKEFOURCC(ch0, ch1, ch2, ch3) /
((DWORD)(BYTE)(ch0) | ((DWORD)(BYTE)(ch1) << 8) | /
((DWORD)(BYTE)(ch2) << 16) | ((DWORD)(BYTE)(ch3) << 24 ));
例如:
#include <mmsystem.h>
#define ID_RIFF mmioFOURCC('R', 'I', 'F', 'F')
#define ID_WAVE mmioFOURCC('W', 'A', 'V', 'E')
... ...
FOURCC id;
... ...
ar >> id;
if (id != ID_RIFF) {
... ...
}
... ...
在文件读写过程中,如果出现读写错误或文件的格式与数据不对,可动态创建一个普通的(generic)文件异常类(CFileException)对象,作抛出(throw)处理。例如
if (id != ID_RIFF) {
::MessageBox(NULL, L"Not RIFF format!", L"Error", MB_OK);
throw(new CFileException(CFileException:: genericException));
}
函数PlaySound可以播放系统声音、声音资源和声音文件,其函数原型为:
BOOL PlaySound(
LPCSTR pszSound,
HMODULE hmod,
DWORD fdwSound
);
其中参数
l pszSound的含义与fdwSound的设置有关。
n 若fdwSound标志设置为SND_ALIAS、SND_FILENAME或SND_RESOURCE,则pszSound为系统事件的别名、文件名或资源ID
n 若fdwSound标志没有设置这些值,则先在注册表或win.ini中寻找串为pszSound的声音,若没有,则视其为文件名
n 若pszSound=NULL,则停止播放正在播放的任何声音(同sndPlaySound)。若要停止非波形格式的声音,必须设置fdwSound的标志SND_PURGE
l hmod
n 若设置了fdwSound的标志SND_RESOURCE,则hmod为包含pszSound所指定资源的可执行文件的句柄
n 若没有设置fdwSound的标志SND_RESOURCE,则hmod必须为NULL
l fdwSound为标志参数,可以取下列值:
PlaySound函数中的fdwSound参数的值
fdwSound值 |
对应数值 |
含义 |
SND_ASYNC |
0x01 |
异步播放,调用后立即返回(最常用) |
SND_LOOP |
0x08 |
循环播放,必须与SND_ASYNC标志同用 |
SND_MEMORY |
0x04 |
lpszSound指向内存中波形声音映像(可以动态生产声音) |
SND_NODEFAULT |
0x02 |
找不到指定声音时,不播放缺省的声音 |
SND_NOSTOP |
0x10 |
如果有声音正在播放,则不播放指定的声音而直接返回 |
SND_SYNC |
0x00 |
同步播放,直到声音播完后调用才返回(缺省值) |
SND_NOWAIT |
0x002000 |
若设备忙,则不等待(不播放声音,立即返回) |
SND_ ALIAS |
0x010000 |
pszSound为注册项的别名 |
SND_ ALIAS_ID |
0x110000 |
别名是一个预定义的ID |
SND_ FILENAME |
0x020000 |
pszSound为文件名 |
SND_ RESOURCE |
0x040004 |
pszSound为资源名或原子(atom) |
SND_ PURGE |
0x40 |
清除任务的非静止事件 |
SND_ APPLICATION |
0x80 |
使用应用程序指定关联程序来播放声音 |
例如:
PlaySound(“c://sounds//sample.wav”, NULL, SND_ASYNC);
PlaySound(ar.GetFile()->GetFilePath(), NULL, SND_ASYNC);
为了使包含PlaySound的程序能够编译通过,必须包含多媒体头文件:
#include <mmsystem.h>
并在项目中添加多媒体链接库:选“项目/*属性”(其中*号表示项目名)菜单项或按Alt+F7组合键,弹出“*属性页”对话框。在该其左上角的“配置”栏的下拉式列表中,选择“所有配置”项。在其左边的“配置”目录栏中,选中“配置属性/链接器/输入”项,在右边顶行的“附加依赖项”栏中键入winmm.lib。先按“应用”钮,再按“确定”钮关闭对话框。
绘图一般在视图类的(屏幕/打印机)绘图消息响应函数OnDraw中进行:
void CWaveView::OnDraw(CDC* pDC)
{
CWaveDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
}
在绘图前,必须先访问文档数据、得到客户区大小、设置绘图颜色,然后再根据文档数据来绘制图形。
可通过在OnDraw函数中自动生成的代码所得到的文档指针pDoc来访问文档类对象中的各种变量和数组,并根据这些数据来绘图。如
CWaveDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
... ...
for(i = 0; i< pDoc->n; i++) {
x = (int)(i * dx + 0.5);
y = h0 - (int)(pDoc->d[i] * dy + 0.5);
if(i == 0) pDC->MoveTo(x, y); else pDC->LineTo(x, y);
}
绘图一般都是在视图窗口的客户区进行,而客户区的大小在运行时可由用户改变,为了使绘制的图形能随窗口大小自动改变,必须先得到当前客户区大小的数据(宽w和高h)。获取客户区大小的方法有两种:
使用类向导ClassWizard,在视图类中添加WM_SIZE消息的响应函数OnSize。该函数在窗口第一次显示或窗口大小被改变时会被Windows系统调用。其输入参数中的cx和cy就是客户区大小的宽和高,可将他们赋值给类变量(如w和h)供绘图时使用。如
void CClassView::OnSize(UINT nType, int cx, int cy)
{
w = cx; h = cy;
}
可在绘图前,定义一个矩形变量crect,然后再调用函数GetClientRect得到当前客户区矩形的数据,其中的右(right)与底(bottom)就是客户区的宽与高(其左left与顶top都为0,右right = 客户区的宽、底bottom = 客户区的高),如:
RECT crect;
GetClientRect(&crect);
int w = crect.right, h = crect.bottom;
其中,表示矩形的结构RECT的定义为(windef.h)
typedef struct _RECT {
LONG left;
LONG top;
LONG right;
LONG bottom;
} RECT;
其对应的MFC类为CRect。
Windows中的颜色一般用4个字节表示(4B = 32b = 0BGR[高位在前] = RGB0[低位在前]),定义了一个专门表示颜色索引值的变量类型COLORREF:(windef.h)
typedef DWORD COLORREF;
及由红绿蓝三原色构造颜色值的宏RGB:(wingdi.h)
#define RGB(r,g,b) ((COLORREF)(((BYTE)(r)|((WORD)((BYTE)(g))<<8))|(((DWORD)(BYTE)(b))<<16)))
其中,r、g、b为字节变量,取值范围为0~255。其函数说明为:
COLORREF RGB(
BYTE bRed, // red component of color
BYTE bGreen, // green component of color
BYTE bBlue // blue component of color
);
例如:
COLORREF red, gray;
red = RGB(255, 0, 0);
gray = RGB(128, 128,128);
在Windows中,像素(pixel)的颜色是直接由设备上下文类CDC的成员函数SetPixel来设置的,该函数的原型为:
COLORREF SetPixel( int x, int y, COLORREF crColor );
其中,x与y分别为像素点的横坐标与纵坐标,crColor为像素的颜色值。
在Windows中,线状图必须用笔(pen)来画,所以线的颜色就由笔色来确定。笔的创建与使用的步骤为:
l 创建笔对象:创建笔类CPen对象的方法有如下两种:
n 使用构造函数CPen
CPen( int nPenStyle, int nWidth, COLORREF crColor );
其中,nPenStyle为笔的风格,可取值:
风格值 |
风格名称 |
线例 |
PS_SOLID |
实心 |
|
PS_DASH |
虚线 |
|
PS_DOT |
点线 |
|
PS_DASHDOT |
虚点线 |
|
PS_DASHDOTDOT |
虚点点线 |
|
nWidth为笔宽,与映射模式有关,使用缺省映射时为像素数,若nWidth = 0,则不论什么映射模式,笔宽都为一个像素;crColor为笔的颜色值。例如
CPen* pGrayPen = new CPen(PS_SOLID, 0, RGB(128, 128, 128));
CPen grayPen(PS_SOLID, 0, RGB(128, 128, 128));
n 使用成员函数CreatePen
BOOL CreatePen( int nPenStyle, int nWidth, COLORREF crColor );
如:
CPen grayPen;
grayPen.CreatePen(PS_SOLID, 0, RGB(128, 128, 128));
n 缺省的笔为单像素宽的实心黑色笔
l 将笔对象选入设备上下文:为了能使用我们所创建的笔对象,必须先将它选入设备上下文,这可以调用设备上下文类CDC的成员函数SelectObject来完成:
CPen* SelectObject( CPen* pPen );
返回值为指向原来笔对象的指针(一般将其保存下来,供下次在装入时使用)。如
pOldPen = pDC->SelectObject(&pen);
另外,Windows中有一些预定义的笔对象,可用CDC的另一成员函数SelectStockObject将其选入DC,其函数原型为:
virtual CGdiObject* SelectStockObject( int nIndex );
预定义的笔对象有BLACK_PEN(黑色笔)、WHITE_PEN (白色笔)、NULL_PEN(空笔/无色笔)。例如:pDC->SelectStockObject(BLACK_PEN);
l 使用设备上下文画线状图:画线状图所使用的是当前设备上下文中的笔对象。线状图有直线、折线、矩形、(椭)圆(弧)等,详见4)(2)
l 将笔对象从设备上下文中放出:为了能删除使用过的笔对象,必须先将它从设备上下文中释放出来后,然后才能删除。释放的方法是重新装入原来的笔对象,如
pDC->SelectObject(pOldPen);
l 删除笔对象:为了能删除笔对象,必须先将其从设备上下文中释放。删除方法有
n 调用笔类CDC的成员函数DeleteObject,之后可再用成员函数CreatePen在笔对象中继续创建新的笔内容。如
pen.DeleteObject();
n 使用删除运算符delete将笔对象彻底删除,如delete pen;
n 自动删除:若笔对象为局部变量,则在离开其作用域时,会被系统自动删除
下面为一段较完整地创建与使用笔的代码:
CPen pen, *pOldPen;
for (int j = 0; j <= 255; j++) {
HSLtoRGB(m_hue, m_sat, 255 - j, r, g, b);
pen.CreatePen(PS_SOLID, 0, RGB(r, g, b));
pOldPen = pDC->SelectObject(&pen);
pDC->MoveTo(0, j); pDC->LineTo(40, j);
pDC->SelectObject(pOldPen);
pen.DeleteObject();
}
在Windows中,面状图必须用刷(brush)来填充,所以面的颜色就由刷色来确定。MFC中的刷类为CBrush,刷的创建与使用的步骤与笔的相似。
l 构造函数有3个:
n CBrush( COLORREF crColor ); // 创建颜色为crColor的实心刷
n CBrush( int nIndex, COLORREF crColor ); // 创建风格由nIndex指定且颜色为crColor的孵化(hatch)刷,其中nIndex可取孵化风格(Hatch Styles)值:
符号常量 |
数字常量 |
风格 |
图案 |
HS_HORIZONTAL |
0 |
水平线 |
----- |
HS_VERTICAL |
1 |
垂直线 |
||||| |
HS_FDIAGONAL |
2 |
正斜线 |
///// |
HS_BDIAGONAL |
3 |
反斜线 |
///// |
HS_CROSS |
4 |
十字线 |
+++++ |
HS_DIAGCROSS |
5 |
斜十字线 |
xxxxx |
n CBrush( CBitmap* pBitmap ); // 创建位图为pBitmap的图案刷
l 与构造函数相对应,也有3个创建不同类型刷的成员函数:
n BOOL CreateSolidBrush( COLORREF crColor );
n BOOL CreateHatchBrush( int nIndex, COLORREF crColor );
n BOOL CreatePatternBrush( CBitmap* pBitmap );
如:pDC->FillRect( &rect, new CBrush( RGB(r, g, b) ) );
l 预定义的刷对象有BLACK_BRUSH(黑刷)、DKGRAY_BRUSH(暗灰刷)、GRAY_BRUSH(灰刷)、HOLLOW_BRUSH(空刷)、LTGRAY_BRUSH(亮灰刷)、NULL_BRUSH(空刷)、WHITE_BRUSH(白刷)
l 缺省的刷为空刷
在Windows中,绘图一般在视图窗口的客户区进行,使用的是设备上下文类CDC中各种绘图函数。
缺省情况下,绘图的默认映射模式为MM_TEXT,其绘图单位为像素(只要不打印输出,使用该模式就够了)。若窗口客户区的宽和高分别为w和h,则其x坐标是从左到右,范围为0 ~ w-1;y坐标是从上到下,范围为0 ~ h-1。参见下图:
缺省的Windows窗口坐标系
画像素点就是设置像素点的颜色,从前面3)(2)已知道这可由CDC的成员函数SetPixel来做,该函数的原型为:
COLORREF SetPixel( int x, int y, COLORREF crColor ); 或
COLORREF SetPixel( POINT point, COLORREF crColor );
其中,x与y分别为像素点的横坐标与纵坐标,crColor为像素的颜色值。如
pDC->SetPixel(i, j, RGB(r, g, b));
另外,表示点的结构POINT的定义为(windef.h)
typedef struct tagPOINT {
LONG x;
LONG y;
} POINT;
对应的MFC类为CPoint。
在Windows中,线状图必须用笔来画(笔的创建与使用见前面的3)(3)),下面介绍的是CDC类中可以绘制线状图的常用成员函数:
l 当前位置:设置当前位置为(x, y)或point:(返回值为原当前位置的坐标)
CPoint MoveTo( int x, int y ); 或 CPoint MoveTo( POINT point );
l 画线:使用DC中的笔从当前位置画线到点(x, y)或point:(若成功返回非0值):
BOOL LineTo( int x, int y ); 或BOOL LineTo( POINT point );
l 画折线:使用DC中的笔,依次将点数组lpPoints中的nCount(≥2)个点连接起来,形成一条折线:
BOOL Polyline( LPPOINT lpPoints, int nCount );
l 画多边形:似画折线,但还会将最后的点与第一个点相连形成多边形,并用DC中的刷填充其内部区域:
BOOL Polygon( LPPOINT lpPoints, int nCount );
l 画矩形:使用DC中的笔画左上角为(x1, y1)、右下角为(x2, y2)或范围为*lpRect的矩形的边线,并用DC中的刷填充其内部区域:
BOOL Rectangle( int x1, int y1, int x2, int y2 ); 或
BOOL Rectangle( LPCRECT lpRect );
l 画(椭)圆:使用DC中的笔在左上角为(x1, y1)、右下角为(x2, y2)或范围为*lpRect的矩形中画内接(椭)圆的边线,并用DC中的刷填充其内部区域:
BOOL Ellipse( int x1, int y1, int x2, int y2 );
BOOL Ellipse( LPCRECT lpRect );
l 画弧:(x1, y1)与(x2, y2)或lpRect的含义同画(椭)圆,(x3, y3)或ptStart为弧的起点,(x4, y4)或ptEnd为弧的终点:(逆时针方向旋转)
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 );
l 画弓弦:参数的含义同上,只是用一根弦连接弧的起点和终点,形成一个弓形,并用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 );
在Windows中,面状图必须用刷来填充(刷的创建与使用见前面的3)(4))。上面(2)中的Polygon 、Rectangle、Ellipse和Chord等画闭合线状图的函数,只要DC中的刷不是空刷,都可以用来画对应的面状图。下面介绍的是CDC类中只能绘制面状图的其他常用成员函数:
l 画填充矩形:用指定的刷pBrush画一个以lpRect为区域的填充矩形,无边线,填充区域包括矩形的左边界和上边界,但不包括矩形的右边界和下边界:
void FillRect( LPCRECT lpRect, CBrush* pBrush );
l 画单色填充矩形:似FillRect,但只能填充单色,不能填充花纹和图案:
void FillSolidRect( LPCRECT lpRect, COLORREF clr );
void FillSolidRect( int x, int y, int cx, int cy, COLORREF clr );
l 画扇形:参数含义同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 );
为了能画移动的位置标识(如十字、一字)和随鼠标移动画动态虚线框,必须在不破坏原有背景图形的基础上移动这些图形。所用的方法为异或画图。
绘图模式(drawing mode)指前景色的混合方式,它决定新画图的笔和刷的颜色(pbCol)如何与原有图的颜色(scCol)相结合而得到结果像素色(pixel)。可使用CDC类的成员函数SetROP2 来设置绘图模式:(ROP = Raster OPeration光栅操作)
int SetROP2( int nDrawMode );
其中,nDrawMode可取值:
符号常量 |
作用 |
运算结果 |
R2_BLACK |
黑色 |
pixel = black |
R2_WHITE |
白色 |
pixel = white |
R2_NOP |
不变 |
pixel = pbCol |
R2_NOT |
反色 |
pixel = ~scCol |
R2_COPYPEN |
覆盖 |
pixel = pbCol |
R2_NOTCOPYPEN |
反色覆盖 |
pixel = ~pbCol |
R2_MERGEPENNOT |
反色或 |
pixel = ~scCol | pbCol |
R2_MERGENOTPEN |
或反色 |
pixel = scCol | ~pbCol |
R2_MASKNOTPEN |
与反色 |
pixel = scCol & ~pbCol |
R2_MERGEPEN |
或 |
pixel = scCol | pbCol |
R2_NOTMERGEPEN |
或非 |
pixel = ~(scCol | pbCol) |
R2_MASKPEN |
与 |
pixel = scCol & pbCol |
R2_NOTMASKPEN |
与非 |
pixel = ~(scCol & pbCol) |
R2_XORPEN |
异或 |
pixel = scCol ^ pbCol |
R2_NOTXORPEN |
异或非 |
pixel = ~(scCol ^ pbCol) |
其中,R2_COPYPEN(覆盖)为缺省绘图模式,R2_XORPEN(异或)常用。
移动图形采用的是异或画图方法,移动图形的过程为:异或画图、在原位置再异或化图(擦除)、在新位置异或画图、...。
如
pOldPen = pDC->SelectObject(pGrayPen);
pDC->SetROP2(R2_XORPEN);
if (erase) DrawCross(pDC, m_x0, m_y0);
DrawCross(pDC, x0, y0);
pDC->SetROP2(R2_COPYPEN);
pDC->SelectObject(pOldPen);
m_x0 = x0; m_y0 = y0;
为了编写颜色调色板的程序,需要建立基于对话框的项目,并要在控件中画图,动态改变编辑框中的数据,还要在对话框中响应编辑消息和鼠标消息。
编辑好对话框资源后,可以使用ClassWizard为对话框的控件(ID)添加(用于数据交换的)类数据成员,它们可以是不同的数据类型,如整数、浮点数、字符串等,还可以为它们设置初值和取值范围。
在程序运行时可以调用对话框类的基类CWnd的成员函数:
UINT GetDlgItemInt( int nID, BOOL* lpTrans = NULL, BOOL bSigned = TRUE ) const;
void SetDlgItemInt( int nID, UINT nValue, BOOL bSigned = TRUE );
int GetDlgItemText( int nID, LPTSTR lpStr, int nMaxCount ) const;
int GetDlgItemText( int nID, CString& rString ) const;
void SetDlgItemText( int nID, LPCTSTR lpszString );
来动态获得和设置指定控件所对应的整数或字符串数据。如:
m_hue = GetDlgItemInt(IDC_EDIT_HUE);
SetDlgItemInt(IDC_EDIT_RED, m_red, false);
为了能在用户修改某一编辑框的数据时,程序能同步修改与之相应的其他控件中的数据,必须使用ClassWizard为编辑控件添加通知消息响应。一般为“改变” 通知消息EN_CHANGE,添加消息响应函数OnChange*。如
void CColorDlg::OnChangeEditRed()
{
// TODO: Add your control notification handler code here
if (fcsID != IDC_EDIT_RED) return;
m_red = (BYTE)GetDlgItemInt(IDC_EDIT_RED);
... ...
SetDlgItemInt(IDC_EDIT_HUE, m_hue);
... ...
}
为了防止相关数据相互自动修改,进入无限死循环,必须区分是否为用户修改(控件为输入焦点)。可以再添加对“设置输入焦点”的通知消息EN_SETFOCUS的响应函数OnSetfocus*,并在函数中记录当前输入焦点的控件ID。如
void CColorDlg::OnSetfocusEditRed()
{
// TODO: Add your control notification handler code here
fcsID = IDC_EDIT_RED;
}
然后,在编辑修改的消息响应函数中,判断是否该编辑控件为输入焦点。只有是输入焦点时,才能对其他控件中的内容作修改。参见前一例子。
可以在对话框资源中放置图片控件(其图标在控件工具箱顶排的右边),并对其类型属性选Frame。可在对话框的绘图消息响应函数OnPaint或其他函数中,用CWnd类的函数GetDlgItem:
CWnd* GetDlgItem( int nID ) const;
来获得图片控件的窗口对象,再用函数GetDC:
CDC* GetDC( );
由窗口对象得到DC,然后就可以用该DC在控件中画图。如
void CColorDlg::OnPaint()
{
if (IsIconic()) {
... ...
}
else {
CDialog::OnPaint();
int i, j;
BYTE r, g, b;
// get control window and DC of Hue&Saturation
CWnd *pWin = GetDlgItem(IDC_HUESAT);
CDC *pDC = pWin->GetDC();
// draw hue-saturation palette
for (i = 0; i < 360; i++)
for (j = 0; j <= 255; j++) {
HSLtoRGB(i, 255 - j, 128, r, g, b);
pDC->SetPixel(i, j, RGB(r, g, b));
}
... ...
}
}
有时需要根据图像的尺寸来调整子窗口的大小,如使用MDI来实现BMP、GIF和JPG等图像的显示。
因为窗口可能包括边框、标题条、菜单条、工具条和状态条,而我们只是要改变客户区的大小,可是Windows的SDK和MFC中都没有直接改变客户区大小的函数可供调用。因此,必须先调用CWnd类的GetWindowRect函数来获得整个窗口的位置和大小:
void GetWindowRect( LPRECT lpRect ) const;
(其中,lpRect中的参数为相对于屏幕左上角的屏幕坐标)
再调用CWnd类的GetClientRect函数:
void GetClientRect( LPRECT lpRect ) const;
(其中,lpRect的left = top = 0、right = 客户区的宽、bottom = 客户区的高)
来获得窗口客户区的大小,然后根据窗口与客户区大小来计算所需整个窗口的大小,最后再调用CWnd的MoveWindow函数:
void MoveWindow( int x, int y, int nWidth, int nHeight, BOOL bRepaint = TRUE );
void MoveWindow( LPCRECT lpRect, BOOL bRepaint = TRUE );
(其中,x与y为窗口左上角的坐标(相对于父窗口或屏幕),nWidth与nHeight为窗口的宽和高)
来改变窗口的大小和位置。注意,MoveWindow只对普通窗口或框架窗口有效,而直接用对应于客户区的CView类(的this指针)调用MoveWindow是毫无用处的。
例如,为使客户区的大小为w*h,可这样来调整窗口大小:(保持窗口左上角的位置不变)
RECT rect, crect;
GetWindowRect(&rect );
GetClientRect(&crect);
MoveWindow(rect.left, rect.top, w + ( rect.right - rect.left - crect .right ),
h + ( rect.bottom - rect.top – crect.bottom ) );
对客户区对应于非CScrollView派生类的SDI窗口,为了在视图类的成员函数中按所需的客户区大小来改变整个主框架窗口的大小,可先调用CWnd类的GetTopLevelFrame函数:
CFrameWnd* GetTopLevelFrame( ) const;
来获得主框架窗口的指针,并使用该指针调用CWnd类的GetWindowRect函数来获得框架窗口的位置和大小,再直接调用CWnd类的GetClientRect函数来获得绘图客户区的位置和大小,为了保证该客户区不包含工具条与状态条(如在OnInitialUpdate函数中),可在调用GetClientRect函数之前,先调用CFrameWnd类的RecalcLayout函数:
virtual void RecalcLayout( BOOL bNotify = TRUE );
来泊放控制条。最后使用主框架窗口的指针调用CWnd的MoveWindow函数来改变窗口的大小和位置。如:
void CTestView::OnFileOpen() {
... ...
RECT rect, crect;
CFrameWnd *pFrm = GetTopLevelFrame();
pFrm ->GetWindowRect( &rect );
pFrm ->RecalcLayout();
GetClientRect(&crect);
pFrm ->MoveWindow( rect.left, rect.top, w + ( rect.right - rect.left - crect .right ),
h + ( rect.bottom - rect.top - crect.bottom ) );
}
所谓MDI的非滚动子框架窗口的客户区对应于非CScrollView派生类,一般为CView的派生类。这时,可先调用CWnd类的GetParentFrame函数:
CFrameWnd* GetParentFrame( ) const;
来获得子框架窗口的指针,其余部分似(2),只是因为子框架窗口没有控制条,不必调用RecalcLayout函数。如
void CTView::OnInitialUpdate() {
CView::OnInitialUpdate();
RECT rect, crect;
GetParentFrame()->GetWindowRect( &rect );
GetClientRect(&crect);
GetParentFrame()->MoveWindow( rect.left, rect.top,
w + ( rect.right - rect.left - crect .right ),
h + ( rect.bottom - rect.top - crect.bottom ) );
}
滚动窗口指客户区对应于从CScrollView派生的类的SDI主框架窗口或MDI的子框架窗口。因为其基类CScrollView有成员函数ResizeParentToFit:
void ResizeParentToFit( BOOL bShrinkOnly = TRUE );
可指示子框架窗口,按SetScrollSizes的参数sizeTotal来自动调整其窗口大小。所以不需要使用MoveWindow函数。
具体做法是,在调用了SetScrollSizes语句以后的任何适当地方,加上如下语句即可
ResizeParentToFit();
但对SDI情形,因为有控制条,需要先调用RecalcLayout函数。如:
void CImageView::OnInitialUpdate() {
CScrollView::OnInitialUpdate();
CImageDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if(pDoc->bFile) {
CSize sizeTotal = CSize(pDoc->width, pDoc->height);
SetScrollSizes(MM_TEXT,sizeTotal);
GetParentFrame()->RecalcLayout();
ResizeParentToFit( );
}
}
因为对话框中所用的单位,为与字体有关的相对单位(1/4字符大小),而系统字体又与各个Windows系统版本、字体集、及用户自己的系统设置有关。若想控件的大小为一确定的像素数,则可在控件已经被创建后,再调用CWnd的函数MoveWindow来改变窗口的大小和位置。如
CWnd *pWin = GetDlgItem(IDC_HUESAT);
pWin->MoveWindow(10, 10, 370, 266);
常见的鼠标操作有:左/右鼠标键按下和松开、双击鼠标左/右键、鼠标移动。其中左鼠标键按下/松开和鼠标移动最常用。每种鼠标操作都有相对应的消息事件,可以用ClassWizard为视图类或对话框类添加鼠标消息响应函数。如
afx_msg void OnLButtonDown( UINT nFlags, CPoint point );
afx_msg void OnMouseMove( UINT nFlags, CPoint point );
afx_msg void OnLButtonUp( UINT nFlags, CPoint point );
afx_msg void OnLButtonDblClk( UINT nFlags, CPoint point );
afx_msg void OnRButtonUp( UINT nFlags, CPoint point );
其中,point为鼠标的位置坐标(相对于客户区的左上角)、nFlags为标记参数,可以为如下符号常量值(二进制位,可以用与来判断,用或来组合):MK_CONTROL(Ctrl键被按下)、MK_LBUTTON(左鼠标键被按下)、MK_MBUTTON(中鼠标键被按下)、MK_RBUTTON(右鼠标键被按下)、MK_SHIFT(Shift键被按下)。
注意,在Windows的鼠标消息中,并没有单击和拖动消息。编程中,单击操作一般用松开左鼠标键消息WM_LBUTTONUP来代替,如
void CColorDlg::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
... ...
CDialog::OnLButtonUp(nFlags, point);
}
而拖动操作则在鼠标移动的消息响应中判断左鼠标键是否被按下来区分。如
void CColorDlg::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
if (nFlags&MK_LBUTTON) {
... ...
}
CDialog::OnMouseMove(nFlags, point);
}
有时需要判断鼠标位置是否在某一矩形区域(如对话框的一个控件)中,这可以调用CRect类的PtInRect函数来做:
BOOL PtInRect( POINT point ) const;
该函数当点point在其矩形区域内时,返回真。注意,该矩形区域不包括矩形的右边界和底边界。例如:
CRect hsRect( 10, 10, 371, 267 );
void CColorDlg::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
if ( hsRect.PtInRect( point ) ) {
... ...
}
... ...
CDialog::OnLButtonUp(nFlags, point);
}
可通过修改串资源中IDR_MAINFRAME或IDR_WAVETYPE所对应的串,为应用程序的文件I/O对话框增加文件过滤器。如为Wave程序增加*.wav的过滤器:
将原来的串“[Wave]/n/nWave/n/n/nWave.Document/nWave Document”修改成
“[Wave]/n/nWave/nWave Files (*.wav)/n.wav/nWave.Document/nWave Document”
其中,
l 第1行“Wave/n”为应用程序的窗口名
l 第2行“/n”为缺省文档名的根(若该行为空,则缺省文档名的根为“未命名”或Untitled)
l 第3行“Wave/n”为文档类型名
l 第4行“Wave Files (*.wav)/n”为文档类型和过滤器的描述
l 第5行“.wav/n”为过虑器本身(文件扩展名)
l 第6行“Wave.Document”为注册的文件类型的ID
l 第7行“Wave Document”为注册的文件类型的描述
注意:这种方法不支持多个文件过虑器,为了同时支持多种扩展名的文件(如图像程序中可同时处理*.BMP、*.GIF、*.JPG等文件),有两种方法:
在多文档程序的C*App类的InitInstance函数中创建多文档模板类CMultiDocTemplate的对象:
CMultiDocTemplate( UINT nIDResource, CRuntimeClass* pDocClass,
CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass );
并使用CWinAPP的成员函数AddDocTemplate:
void AddDocTemplate( CDocTemplate* pTemplate );
将新建的模板加入。如
pDocTemplate = new CMultiDocTemplate(
IDR_BMPTYPE,
RUNTIME_CLASS(CImageDoc),
RUNTIME_CLASS(CChildFrame), // custom MDI child frame
RUNTIME_CLASS(CImageView));
AddDocTemplate(pDocTemplate);
pDocTemplate = new CMultiDocTemplate(
IDR_GIFTYPE,
RUNTIME_CLASS(CImageDoc),
RUNTIME_CLASS(CChildFrame), // custom MDI child frame
RUNTIME_CLASS(CImageView));
AddDocTemplate(pDocTemplate);
其中,IDR_BMPTYPE与IDR_GIFTYPE为资源ID,对应的有与(1)类似的串资源,还有菜单资源。
如果只是文件过虑器不同,而菜单与文档类、子框架类及视图类都一样的话,编程必须同时维护三个完全相同的菜单,太低效。解决办法是覆盖对ID_FILE_OPEN消息的缺省响应CWinApp::OnFileOpen:
//ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
添加自己消息响应函数。在自己的OnFileOpen函数中,设置多个文件过虑器,调用文件公用对话框,然后用用户选择的文件路径调用CWinApp的成员函数OpenDocumentFile:
virtual CDocument* OpenDocumentFile( LPCTSTR lpszFileName );
如:
void CImageApp::OnFileOpen() {
char filters[] = "图像文件(*.bmp;*.gif)|*.bmp;*.gif|位图文件(*.bmp)|*.bmp|/
GIF文件(*.gif)|*.gif|所有文件(*.*)|*.*||";
CFileDialog fileDlg(TRUE, NULL, NULL, OFN_HIDEREADONLY, filters);
if (fileDlg.DoModal() == IDOK) OpenDocumentFile(fileDlg.GetPathName());
}
为了MDI程序在开始运行时不自动创建一个新文档及其对应子窗口,可在应用程序类C*App的InitInstance函数的语句
// Parse command line for standard shell commands, DDE, file open
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
与
// Dispatch commands specified on the command line
if (!ProcessShellCommand(cmdInfo)) return FALSE;
之间,加上语句:
// Disable to creation a child frame at start time
cmdInfo.m_nShellCommand = CCommandLineInfo::FileNothing;
当文档的内容太多(如大尺寸图像),窗口客户区显示不下时,应该使用滚动窗口。
MFC提供了一种自动化程度非常高的滚动视图类CScrollView,可在创建项目时,在最后一步的MFC AppWizard - Step 6 of 6对话框中,将C*View的基类从缺省的CView改成CScrollView。(对已经存在的项目,可在*View.h和*View.cpp文件中,将所有的CView替换成CScrollView。)
在OnInitialUpdate函数中或其他需要的地方调用CScrollView类的成员函数SetScrollSizes来设置滚动的范围和参数:
void SetScrollSizes( int nMapMode, SIZE sizeTotal, const SIZE& sizePage = sizeDefault, const SIZE& sizeLine = sizeDefault );
如:
void CImageView::OnInitialUpdate() {
CScrollView::OnInitialUpdate();
CImageDoc* pDoc = GetDocument();
if(pDoc->bFile) {
SetScrollSizes(MM_TEXT, CSize(pDoc->width, pDoc->height));
GetParentFrame()->RecalcLayout();
ResizeParentToFit( );
}
}
在OnDraw函数中,可以对整个滚动范围绘图;也可以调用CDC类的成员函数GetClipBox:
virtual int GetClipBox( LPRECT lpRect ) const;
来确定前客户区中需要更新的矩形区域及需要绘制的文档范围。如
void CImageView::OnDraw(CDC* pDC) {
CImageDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
int i, j, x1, x2, y1, y2;
COLORREF col;
CRect updateRect;
pDC->GetClipBox(updateRect);
x1 = updateRect.left; x2 = updateRect.right;
y1 = updateRect.top; y2 = updateRect.bottom;
if (x1 >= w || y1 >= h) return;
if (x2 > w) x2 = w; if (y2 > h) y2 = h;
for (i = y1; i < y2; i++) for (j = x1; j < x2; j++) {
col = ... ... ;
pDC->SetPixel(j, i, col);
}
}
可以调用API函数LoadImage来装载位图文件:
HANDLE LoadImage(
HINSTANCE hinst, // handle of the instance containing the image
LPCTSTR lpszName, // name or identifier of image
UINT uType, // type of image
int cxDesired, // desired width
int cyDesired, // desired height
UINT fuLoad // load flags
);
例如:
HBITMAP hBmp = (HBITMAP) LoadImage(NULL, ar.GetFile()->GetFilePath(),
IMAGE_BITMAP, 0, 0,
LR_LOADFROMFILE | LR_DEFAULTCOLOR | LR_DEFAULTSIZE);
仍然用BitBlt来显示位图,但需要改用GetObject来获取BITMAP结构对象:
BITMAP bs;
GetObject(hBmp, sizeof(bs), &bs);
CDC dc, *pDC = GetDC();
dc.CreateCompatibleDC(pDC);
::SelectObject(dc.GetSafeHdc(), hBmp);
pDC->BitBlt(0, 0, bs.bmWidth, bs.bmHeight, &dc, 0, 0, SRCCOPY);