一、CPen画笔类
1. 画笔的风格:
1) 所有的画线函数都是使用CPen类指定的画笔绘制的,默认都是1像素宽、黑色的实线;
2) 要使定义的画笔生效(即可以画线)必须先创建CPen的画笔对象,然后将其选入设备环境dc中方可生效,但如果什么都不设定也可以直接用dc画 线,是因为dc初始化时有一个默认的画笔,就是上述的1像素宽、黑色的实线;
3) 画笔的风格由三个特性决定,即样式、宽度和颜色:
i. 样式(int nPenStyle):主要有实线、划线、点线、点划线、透明这几种主要风格,该项特性由宏PS_指定,即Pen Style的缩写,主要有这几 个常用的
PS_SOLID:实线
PS_DOT:点线
PS_DASH:划线
PS_DASHDOT:点划线
PS_DASHDOTDOT:一划两点的点划线
PS_NULL:透明线
PS_INSIDEFRAME:边框内沿线
!注意:其中边框内沿线是指整个边框线的实体都紧贴边框内沿,而非PS_INSIDEFRAME的线在边框内侧和外侧的宽度都一样,比如画一个 圆,直径为5像素,画圆的线的风格是非内沿的2像素宽的线,则边沿线内外两侧各占1像素宽的边框线,因此总的外径为12像素,但如果是内 沿的风格,则变沿线外侧无边框线的像素,而内侧有2像素宽度的边框线,因此外径仍然是10像素;
ii. 宽度(int nWidth):宽度由逻辑单位给出,逻辑单位取决于当前的映射模式,其中PS_SOLID、PS_NULL、PS_INSIDEFRAME可以指定任意逻 辑宽度,但其余(PS_DASH、PS_DOT、PS_DASHDOT、PS_DASHDOTDOT)必须指定1单位宽,如果指定其它任何值都会在后台自动修正 为1逻辑宽度。
!注意:任意样式如果指定的宽度为0,都会被默认修正成1;
iii. 颜色(COLORREF crColor):由RGB宏定义,三个分量分别为红、绿、蓝;
2. 创建、修改、选中画笔:
1) 可以使用CPen的构造函数:
i. 无参构造函数:CPen(),和dc初始化时默认的画笔相同,也是实线、1像素宽、黑色;
ii. 有参构造函数:CPen(int nPenStyle, int nWidth, COLORREF crColor);
2) 修改画笔:如果想修改画笔的风格,则可以使用CPen的成员函数CreatePen或者CreatePenIndirect来重新生成一个新的画笔
i. CreatePen:BOOL CreatePen(int nPenStyle, int nWidth, COLORREF crColor);,可以看到参数和有参构造函数一模一样,只不过返回值是 BOOL,如果创建成功则返回TRUE;
ii. CreatePenIndirect:这种方法需先创建一个"逻辑画笔",再用该逻辑画笔来创建实际的画笔。所谓逻辑画笔和实际的画笔不同的地方在于,真 实的画笔一旦被创建将会在内存创建出一块画笔的资源,该资源直接和底层的GDI有关,容量较大,而逻辑画笔只是包含画笔风格字段的 结构体,里面只有画笔的参数(仅仅就是几个int而已),因此,如果程序中需要使用各种不同风格的画笔(数量非常多),则应该创建不 同风格的多种逻辑画笔,但只创建一个实际的画笔,可以分别用不同的逻辑画笔来重新定义实际画笔,这样节省内存资源提高程序效率。
//a. 逻辑画笔结构体:
typedef struct tagLOGPEN {
int lopnStyle;
POINT lopnWidth;//其中lopnWidth只有域x才表示画笔宽度,y域没有用,要注意了!
COLORREF lopnColor;
} LOGPEN, *PLOGPEN
// b. CreatePenIndirect:BOOL CreatePenIndirect(LPLOGPEN lpLogPen);
//c. 通过逻辑画笔创建实际的画笔:
CPen pen;
LOGPEN lopn;
lopn.lopnStyle = PS_DOT;
lopn.lopnWidth.x = 1;
lopn.lopnColor = RGB(255, 0, 0);
pen.CreatePenIndirect(&lopn);
3) 使用构造函数和Create系列函数创建画笔的主要区别:构造函数在创建失败的情况下会抛出CResourceException(即内存严重缺乏异常),而 Create系列则是在创建失败时返回FALSE,因此后者更加健壮,因此推荐先用无参构造函数创建一个没多少内存的默认画笔,然后再用 Create系列函数创建实际画笔。
4) 选中画笔:
i. 要使画笔生效就必须先将创建好的CPen对象选入dc设备环境中;
ii. 使用SelectObject将绘图对象选入环境设备dc中:CPen* SelectObject(CPen* pPen);,其返回更新之前的老画笔的句柄;
iii. MFC将SelectObject重载过好几个版本的,有CBrush等其它类型的绘图对象,这里只是画笔一种而已;
iv. CPen* pOldPen = dc.SelectObject(pNewPen);
二、CBrush画刷类
1. 画刷的用途以及创建不同风格的画刷:
1) 画刷用于填充封闭图形内部区域,像椭圆、矩形等的内部区域;
2) 画刷三种风格:
i. 单色:填充单色,创建时只需给出颜色参数即可;
ii. 阴影线:填充的是阴影线,此时需要指定阴影线的颜色以及线的背景色、背景模式;
iii. 位图填充:使用位图(可以自定义)来填充封闭区域;
3) 创建单色画刷:
i. 使用重载的单参数构造函数:CBrush(COLORREF crColor);
ii. 使用简单明了的“创建单色画刷”成员函数:BOOL CBrush::CreateSolidBrush(COLORREF crColor);,这种方式建议先创建一个空的画刷, 即使用无参构造函数创建,然后再使用该函数创建单色画刷;
4) 创建阴影线画刷:
i. 使用重载的双参数构造函数:CBrush(int nIndex, COLORREF crColor);,其中第一个参数指定阴影线的风格,第二个参数指定阴影线的颜色;
ii. 使用简单明了的“创建阴影线画刷”成员函数:BOOL CBrush::CreateHatchBrush(int nIndex, COLORREF crColor);,参数意义和前者相同;
iii. 但是前面说过了,还需要指定背景颜色和背景模式:
!注意:GDI中所谓的“背景”:
a. 背景颜色:决定了一下这些地方的填充颜色:文本字符背后的背景颜色、点线以及点划线空隙中的颜色,阴影画笔的阴影线之间区域的颜色;
b. 背景模式:只有两种模式,一种是透明,另一种是不透明,如果是透明的,则背景颜色将不起作用,其颜色将和整个客户区的背景颜色相同, 如果是不透明的,就需要自己指定的背景颜色;
c. 背景颜色和背景模式并不是从属于文本、画笔、画刷的,而是一个独立的GDI对象,设置背景颜色使用dc的SetBkColor,设置背景使用dc的 SetBkMode;
d. SetBkColor:COLORREF SetBkColor(COLORREF crColor);,返回更新前的背景色;
e. SetBkMode:int SetBkMode(int nBkMode);,nBkMode有两种模式,一种是OPAQUE,即非透明,第二种是TRANSPARENT,即透明的;
iv. nIndex指定阴影线的风格:
HS_BDIAGONAL:附对角线型
HS_FDIAGONAL:主对角线型
HS_HORIZONTAL:水平线型
HS_VERTICAL:垂直线型
HS_CROSS:水平垂直交叉型
HS_DIAGCROSS:斜交叉型
!其中HS_即Hatch Style的缩写;
2. 使用逻辑画刷创建实际画刷:
1) 和前面介绍的逻辑画笔一样,主要是用来创建多种不同风格的画刷(数量也很多)以节省内存资源;
2) 逻辑画刷结构体定义:
typedef struct tagLOGBRUSH {
UINT lbStyle;
COLORREF lbColor;
LONG lbHatch;
} LOGPEN, *LPLOGPEN;
a.其中lbStyle是指画刷风格,即单色还是阴影线,以BS_打头,即Brush Style的缩写,主要有这几种:
BS_SOLID:单色
BS_HATCHED:阴影线型的
BS_HOLLOW/BS_NULL:两者等价,都是透明的,相当于SetBkMode(TRANSPARENT);,但不过设定为BS_HOLLOW比直接SetBkMode更 好,因为前者的作用范围只是画刷,而后者是全局的,也会影响到其它GDI对象。
b. lbColor表示单色的颜色,如果是阴影线型则指定了阴影线的颜色;
c. lbHatch即HS_打头的阴影线风格,如果lbStyle指定为单色或者透明,则将忽略这个参数,这个参数只有在阴影线风格下才有效;
3. 创建并选中画刷的完整过程演示:
//选中画刷和选中画笔一样,只不过SelectObject是重载过的版本:CBrush* SelectObject(CBrush* pBrush);
CBrush brush;
brush.CreateHatchBrush(HS_DIAGCROSS, RGB(255, 0, 0));
CBrush* brOldBrush = dc.SelectObject(&brush);
COLORREF crOldColor = dc.SetBkColor(RGB(0, 0, 255));
int nOldBkMode = dc.SetBkMode(OPAQUE);
dc.Rectangle(0, 0, 100, 100);
4. 画刷原点:
1) 问题背景:采用阴影线填充矩形内部时会遇到一个问题,就是有时阴影线刚好从左上角射出,有时没有,这好像没什么,但是有时候在做一些动画效果时会有大问题,那就是矩形在移动,但是内部阴影线的位置却保持不动,这就带来的不好的效果,因此需要让内部的阴影线跟着矩形一块儿移动,并且最好是让一条阴影线刚好从矩形左上角射出;
2) 实际上上画刷填充图案在屏幕上是以一个8×8的像素块为单位的,可以看成原本屏幕上布满了这样的小块,而屏幕上出现了一个矩形需要这样的阴影填充,从而只保留矩形内部的这些图案,矩形外部的都不显示罢了,而该8×8小正方形的左上角,就是默认的刚好射出一条阴影线的画刷原点了,如果画刷原点刚好能和矩形左上角重合的话那么刚好就有一条阴影线从矩形左上角射出了,这也正是我们想要的,但是如果不人为设置,画刷原点永远都在8×8像素块的左上角;
3) 设置画刷原点的位置:
i. 首先需要调用:BOOL CGdiObject::UnrealizeObject();
a.返回值表示本次调用成功与否;
b.该函数的作用是重设画刷和调色板,其中画刷在这里是我们关心的,调用这个函数之后就表示画刷的原点可以移动;
c.CBrush等GDI对象类型都直接或间接继承自CGdiObject,因此可以放心使用该函数;
ii. 接着使用:CPoint CDC::SetBrushOrg(int x, int y);
a.同样也能接受CPoint和POINT类型的参数,返回值是更新前的画刷原点位置;
iii. 将修改过原点的画刷选入设备描述表就能正常使用了;
4) 示例:
// 假设一开始工作在逻辑坐标下
CPoint point(x1, y1); // 矩形左上角的逻辑坐标
dc.LPtoDP(&point); // 得到设备坐标,注意:画刷原点都是按照设备坐标设定的
point.x %= 8; // 得到矩形左上角在8×8像素块中的坐标
point.y %= 8;
brush.UrealizeObject();
dc.SetBrushOrg(point); // 设置画刷原点和矩形左上角重合
dc.SelectObjet(&brush); // 选入设备描述表后即可正常使用
dc.Rectangle(x1, y1, x2, y2);