MFC——第五次上机心得

  • HWND:h 是类型描述,表示句柄(handle), Wnd 是变量对象描述,表示窗口,所以hWnd 表示窗口句柄

    通俗地说,如果把一个到处跑的人当作指针的话,那么HWND就是该人的身份证;

hWnd 属性:

返回窗体或控件的句柄(注意 OLE 容器控件不支持该属性。句柄:是由操作环境定义的一个唯一的整数值,它被程序用来标识或者切换到对象,如窗体或控件等,就是一个指针。

  • 现在的世界流行图形界面,而不是文字,因此在软件开发里,肯定需要画图的,比如简单地画线,画一些比较特别的图形。比如让你画一个走动的时钟,就需要不断地画秒针、分针等等。MoveToEx是用来移动当前画笔的位置,LineTo是用来画直线的函数,其实在计算机图形里的直线显示是使用光栅图形学里的原理。

  • 函数MoveToEx和LineTo声明如下:

  • WINGDIAPI BOOL WINAPI MoveToEx( __in HDC hdc, __in int x, __in int y, __out_opt LPPOINT lppt);

  • hdc是当前设备的句柄。

  • x是X轴的位置,水平方向,一般原点是在屏幕左上角的位置。

  • y是Y轴的位置,垂直方向。

  • lppt是移动前的坐标位置。

  • WINGDIAPI BOOL WINAPI LineTo( __in HDC hdc, __in int x, __in int y);

  • hdc是当前设备的句柄。

  • x是X轴的位置,水平方向,一般原点是在屏幕左上角的位置。

  • y是Y轴的位置,垂直方向。

  • 对于函数前面要加::,是因为他们是全局函数。表示全局的, 全局的是不属于任何类和名字空间,所以 :: 前面只好空着.。

  • 以H开头的大多上都表示句柄类型,以C开头的都是MFC类。

  1. 窗口框架的客户区就是指图中的灰色部分:即蓝色方框内区域,框架的非客户区就是指蓝色方框之外的窗口区域,包括程序图标,标题栏,菜单,最大最小/关闭按纽,左右两个框和底下的状态条.

  2. 视图的客户区就是指图中绿色部分:即红色方框内的区域,视图的非客户区就是指视图部分红色方框之外的区域,包括:文件图标,标题栏,三个按纽和两个滚动条.

  3. 从图中可以看出,工具栏(工具条)不属于框架的非客户区,它跟视图是框架客户区内的"兄弟".它也是一个窗口,读者不妨再拖拽工具条,它还可以拖出框架窗口区域.

  4. 因此,以后编程中,对于CClientDC,CWindowDC,GetClientDC(),GetWindowDC()应该可以很容易就区分开了.

  • CDC是MFC封装的Windows 设备相关的一个类;

  • CClientDC是CDC的衍生类,产生对应于Windows客户区的对象,在构造的时候调用GetDC,在析构的时候调用ReleaseDC;

  • CWindowDC也是CDC的衍生类,在构造的时候调用GetWindowDC,在析构时调用ReleaseDC,可访问CWnd的整个屏幕,包括客户去和非客户区。

  • CClientDC:

(客户区设备上下文)用于客户区的输出,与特定窗口关联,可以让开发者访问目标窗口中客户区,其构造函数中包含了GetDC,析构函数中包含了ReleaseDC。

用法是:CClientDC dc(this);//this一般指向本窗口或当前活动视图

           dc.TextOut(10,10,str,str.GetLength());//利用dc输出文本,如果是在CScrollView中使用,还要注意调用[OnPrepareDC](http://baike.baidu.com/subview/4782475/4782475.htm)(&dc)调整设备上下文的坐                标。[CPaintDC](http://baike.baidu.com/subview/2962262/2962262.htm)用于响应窗口重绘消息(WM_PAINT)时的绘图输出。

CPaintDC在构造函数中调用BeginPaint()取得设备上下文,在析构函数中调用EndPaint()释放设备上下文。EndPaint()除了释放设备上下文外,还负责从消息队列中清除WM_PAINT消息。

因此,在处理窗口重画时,必须使用CPaintDC,否则WM_PAINT消息无法从消息队列中清除,将引起不断的窗口重画。CPaintDC也只能用在WM_PAINT消息处理之中。

  • CWindowDC:关联一特定窗口,允许开发者在目标窗口的任何一部分进行绘图,包含边界与标题,这种DC同WM_NCPAINT消息一起发送

CWindowDC与CClientDC,CPaintDC的区别:

CWindowDC可在非客户区绘制图形,而CClientDC,CPaintDC只能在客户区绘制图形。CWindowDC下坐标原点是在屏幕的左上角,CClientDC,CPaintDC下坐标原点是在客户区的左上角。CClientDC与CPaintDC的区别:

CPaintDC的对象一般用在OnPaint内以响应Windows消息WM_PAINT,自动完成绘制,在整个窗口内进行重画,维持原有窗口完整性。CClientDC应用在非响应Windows消息WM_PAINT的情况下,进行实时绘制,绘制的区域内被重画。

image.png

* 画笔
  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. 逻辑画笔结构体:

    1. typedef struct tagLOGPEN {  
    2. 
    int lopnStyle;  
    3. 
    POINT lopnWidth;  
    4. 
    COLORREF lopnColor;  
    5. 
} LOGPEN, *PLOGPEN;  

!其中lopnWidth只有域x才表示画笔宽度,y域没有用,要注意了!

         b. CreatePenIndirect:BOOL CreatePenIndirect(LPLOGPEN lpLogPen); 
         c. 通过逻辑画笔创建实际的画笔:

    1. CPen pen;  
    2. 
LOGPEN lopn;  
    3. 
  
    4. 
lopn.lopnStyle = PS_DOT;  
    5. 
lopn.lopnWidth.x = 1;  
    6. 
lopn.lopnColor = RGB(255, 0, 0);  
    7. 
pen.CreatePenIndirect(&lopn);  

  1. 使用构造函数和Create系列函数创建画笔的主要区别:构造函数在创建失败的情况下会抛出CResourceException(即内存严重缺乏异常),而Create系列则是在创建失败时返回FALSE,因此后者更加健壮,因此推荐先用无参构造函数创建一个没多少内存的默认画笔,然后再用Create系列函数创建实际画笔。

    1. 选中画笔:
      i. 要使画笔生效就必须先将创建好的CPen对象选入dc设备环境中;
      ii. 使用SelectObject将绘图对象选入环境设备dc中:CPen* SelectObject(CPen* pPen);,其返回更新之前的老画笔的句柄;
      iii. MFC将SelectObject重载过好几个版本的,有CBrush等其它类型的绘图对象,这里只是画笔一种而已;
      iv. CPen* pOldPen = dc.SelectObject(pNewPen);
  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. 逻辑画刷结构体定义:

    1. typedef struct tagLOGBRUSH {  
    2. 
    UINT lbStyle;  
    3. 
    COLORREF lbColor;  
    4. 
    LONG lbHatch;  
    5. 
} LOGPEN, *LPLOGPEN;  

!其中lbStyle是指画刷风格,即单色还是阴影线,以BS_打头,即Brush Style的缩写,主要有这几种:

BS_SOLID:单色
BS_HATCHED:阴影线型的
BS_HOLLOW/BS_NULL:两者等价,都是透明的,相当于SetBkMode(TRANSPARENT);,但不过设定为BS_HOLLOW比直接SetBkMode更好,因为前者的作用范围只是画刷,而后者是全局的,也会影响到其它GDI对象
!lbColor表示单色的颜色,如果是阴影线型则指定了阴影线的颜色;
!lbHatch即HS_打头的阴影线风格,如果lbStyle指定为单色或者透明,则将忽略这个参数,这个参数只有在阴影线风格下才有效;

  1. 创建并选中画刷的完整过程演示:
    !选中画刷和选中画笔一样,只不过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);  
  1. 画刷原点:
    1. 问题背景:采用阴影线填充矩形内部时会遇到一个问题,就是有时阴影线刚好从左上角射出,有时没有,这好像没什么,但是有时候在做一些动画效果时会有大问题,那就是矩形在移动,但是内部阴影线的位置却保持不动,这就带来的不好的效果,因此需要让内部的阴影线跟着矩形一块儿移动,并且最好是让一条阴影线刚好从矩形左上角射出;
    2. 实际上上画刷填充图案在屏幕上是以一个8×8的像素块为单位的,可以看成原本屏幕上布满了这样的小块,而屏幕上出现了一个矩形需要这样的阴影填充,从而只保留矩形内部的这些图案,矩形外部的都不显示罢了,而该8×8小正方形的左上角,就是默认的刚好射出一条阴影线的画刷原点了,如果画刷原点刚好能和矩形左上角重合的话那么刚好就有一条阴影线从矩形左上角射出了,这也正是我们想要的,但是如果不人为设置,画刷原点永远都在8×8像素块的左上角;
    3. 设置画刷原点的位置:
      i. 首先需要调用:BOOL CGdiObject::UnrealizeObject();
      !返回值表示本次调用成功与否;
      !该函数的作用是重设画刷和调色板,其中画刷在这里是我们关心的,调用这个函数之后就表示画刷的原点可以移动;
      !CBrush等GDI对象类型都直接或间接继承自CGdiObject,因此可以放心使用该函数;
      ii. 接着使用:CPoint CDC::SetBrushOrg(int x, int y);
      !同样也能接受CPoint和POINT类型的参数,返回值是更新前的画刷原点位置;
      iii. 将修改过原点的画刷选入设备描述表就能正常使用了;
    4. 示例:

    1. // 假设一开始工作在逻辑坐标下  
    2. 
CPoint point(x1, y1); // 矩形左上角的逻辑坐标  
    3. 
dc.LPtoDP(&point); // 得到设备坐标,!注意:画刷原点都是按照设备坐标设定的  
    4. 
point.x %= 8; // 得到矩形左上角在8×8像素块中的坐标  
    5. 
point.y %= 8;  
    6. 
brush.UrealizeObject();  
    7. 
dc.SetBrushOrg(point); // 设置画刷原点和矩形左上角重合  
    8. 
dc.SelectObjet(&brush); // 选入设备描述表后即可正常使用  
    9. 
dc.Rectangle(x1, y1, x2, y2);  

* 

CPenpOldPen=dc.SelectObject(&pen);,DC将当前的画笔保存在poldPen中,将Pen作为当前的画笔。而dc.SelectObject(pOldPen); 将poldPen所保存的画笔作为当前的画笔。如果没有CPenpOldPen=dc.SelectObject(&pen);,,则poldPen的值是不确定的,当然不是我们想要保存的画笔。而如果没有dc.SelectObject(pOldPen);,则画笔将永远是Pen,而不能恢复。

* 

解决了全局变量的问题,最好要设定全局变量的话不要放在.h头文件中,因为会因为多次调用造成重定义的现象,解决的方法就是在.cpp文件定义一个全局,然后在其他的地方通过extern方式引入。

画刷类CBrush
利用画笔可以画图形的边框,而用画刷就可以在图形内着色。大多数的GDI绘图函数既使用画笔又使用画刷,它们用画笔绘制各种图形的周边,而用画刷填充图形,因而可以用一种颜色和风格去设置画笔,而用另一种颜色和风格去设定画刷,通过一次函数调用就可以绘制出形状复杂的图形。
画刷是由CBrush类管理的,创建画刷有两种方法:一种是调用构造函数,另一种是调用相关的成员函数。
1.CBrush的构造函数
CBrush类的结构函数定义如下:

① CBrush( );
② CBrush( COLORREF crColor );
throw( CResourceException );
③ CBrush( int nIndex, COLORREF crColor );
throw( CResourceException );
④ CBrush( CBitmap* pBitmap );
throw( CResourceException );

参数说明:

crColor:设定画笔颜色。
NIndex:如果画刷是带花纹的,则该参数设定画刷的花纹类型,其取值如下:
l HS_BDIAGONAL:为左高右低的45度斜花纹。
l HS_CROSS:为由水平线和垂直线构成的方格花纹。
l HS_DIAGCROSS:为斜45度方格花纹。
l HS_FIDAGONAL:为右高左低的45度斜花纹。
l HS_HORIZONTAL:为水平线花纹。
l HS_VERTICAL:为垂直线花纹。
PBitmap:为CBitmap类的指针,用于指定填充时所使用的位图。

2.CBrush提供的创建画笔的成员函数
除了构造函数,CBrush还提供了6个创建画刷的成员函数:
BOOL CreateSolidBrush( COLORREF crColor )
该函数创建一个实填充的画刷。crColor为填充色。
BOOL CreateHatchBrush( int nIndex, COLORREF crColor )
该函数创建一个带花纹的画刷,nIndex为花纹类型,crColor为画刷颜色。
BOOL CreateBrushIndirect( const LOGBRUSH* lpLogBrush )
调用该函数可以通过LOGBRUSH结构生成一个画刷,LOGBRUSH结构的定义如下:

typedef struct tag LOGBRUSH {
UINT lbStyle;
COLORREF lbColor;
LONG lbHatch;
} LOGBRUSH;

其中lbStyle指定的是画刷的类型,lbColor指定画刷的颜色,lbHatch指定的是画刷的花纹类型。
BOOL CreatePatternBrush( CBitmap* pBitmap )
创建一个使用位图填充的画刷,画刷的位图由pBitmap指定,位图大小必须8×8。
BOOL CreateDIBPatternBrush( HGLOBAL hPackedDIB, UINT nUsage )
BOOL CreateDIBPatternBrush( const void* lpPackedDIB, UINT nUsage )
创建使用DIB(设备无关位图)的画刷,其中hPackedDIB指向存储位图数据的内存块的地址,lpPackedDIB指向DIB位图数据的指针,nUsage用于设定调色板的属性。
BOOL CreateSysColorBrush( int nIndex )
该函数设定带有系统色的画刷,nIndex指定为画刷的花纹类型。
3.CBrush演示实例
(1)创建一个基于对话框的应用程序BrushDemo。
(2)向CBrushDemoDlg类中添加一个成员函数RunBrush(),它主要完成在指定区域绘制指定矩形的功能。添加RunBrush()成员函数的代码如下:

void CBrushDemoDlg::RunBrush(CPaintDC* dc,CRect rect, COLORREF col,CString text, LONG lbHatch)
{
CBrush cbrush;
CBrush* pBrush; //旧笔刷
cbrush.CreateHatchBrush(lbHatch,col); //建立带花纹笔刷
dc->DrawText(text,&rect,DT_CENTER|DT_WORDBREAK); //写提示文本
rect.top=rect.top+40; //调整矩形区域
pBrush=dc->SelectObject(&cbrush); //载入笔刷
dc->Rectangle(&rect); //绘制矩形
dc->SelectObject(pBrush); //恢复笔刷
cbrush.DeleteObject();
pBrush->DeleteObject();
}
(3)找到CBrushDemoDlg::OnDrow()函数,修改这个函数的内容:
void CBrushDemoDlg::OnPaint()
{
CPaintDC dc(this);
//背景填充为白色,不建议这么做
CRect rect,fillrect;
CBrush brush;
brush.CreateSolidBrush(RGB(255,255,255));
this->GetClientRect(&rect);
dc.FillRect(&rect,&brush); //用FillRect成员函数利用笔刷填充指定区域
brush.DeleteObject();
//画矩形区域
int left=10;
const int MAX=6;
fillrect.top=rect.top+10;
fillrect.bottom=rect.bottom-10;
long style[MAX]={HS_BDIAGONAL,HS_CROSS,HS_DIAGCROSS,\
HS_FDIAGONAL,HS_HORIZONTAL,HS_VERTICAL}; //笔刷的花纹类型

CString text[MAX]={"剖 面 线\n从左到右","网 格 线",\
"斜网格线","剖 面 线\n从右到左","水 平 线","垂 直 线"}; //提示文本
for(int i=0;i

你可能感兴趣的:(MFC——第五次上机心得)