为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

Windows_Gdi_应用-入门篇_(VC_SDK)

2010-12-20 34页 doc 552KB 24阅读

用户头像

is_403407

暂无简介

举报
Windows_Gdi_应用-入门篇_(VC_SDK)Windows Gdi 应用-入门篇 (VC SDK) Windows Gdi 应用-入门篇 (VC SDK) 一、 基础 GDI的绘图函数基本上都是有状态的,所有的函数都要求一个HDC类型的句柄。这个HDC的获得有几个途径BeginPaint,GetWindowDC, GetDC.他们的参数都只需要一个HWND就差不多了。记得调用了BeginPaint后要调用EndPaint进行清理,调用GetWindowDC和GetDC后要调ReleaseDC进行清理。在MFC代码中常常遇到的CDC CPaintDC CWindowDC ...
Windows_Gdi_应用-入门篇_(VC_SDK)
Windows Gdi 应用-入门篇 (VC SDK) Windows Gdi 应用-入门篇 (VC SDK) 一、 基础 GDI的绘图函数基本上都是有状态的,所有的函数都要求一个HDC类型的句柄。这个HDC的获得有几个途径BeginPaint,GetWindowDC, GetDC.他们的参数都只需要一个HWND就差不多了。记得调用了BeginPaint后要调用EndPaint进行清理,调用GetWindowDC和GetDC后要调ReleaseDC进行清理。在MFC代码中常常遇到的CDC CPaintDC CWindowDC CClientDC。在这里稍作解释。 CDC :例如用GDI画矩形要Rectangle(hDC,...),而使用CDC则是dc.Rectangle(...),由此可见CDC主要是把原本需要HDC作为参数的GDI函数封装了一下,HDC成了它的一个成员变量。 CPaintDC CWindowDC CClientDC:他们都是从CDC继承,分别是对上面所说的BeginPaint,GetWindowDC, GetDC调用对进行封装(CPaintDC构造时调用BeginPaint,析构时调用EndPaint,其余同理)。 BeginPaint:一般用在对WM_PAINT的响应函数中使用 GetWindowDC:可获得整个Window的HDC,而GetDC仅能获得客户区的HDC,区别就在于-- 前者有效地绘制区域是整个窗口(边框、标题栏、客户区的总和)。 后者有效地绘制区域仅限于客户区。 两者的坐标系都是相对坐标而非屏幕坐标,原点是(0,0)。即以自己可绘制区域的左上角作为原点。 这里可以顺带的讲讲RECT了,RECT是一个结构,依次有4个成员left,top,right,bottom用来代表一个矩形区域。CRect从RECT继承,提供了一些常用的操作(例如说位移,缩小等等),其实就是改变4个成员的值。完全不用CRect也可以。许多GDI函数都要求一个RECT作为参数,或者类似的用(x,y,cx,cy)作参数,其实也就是一个RECT变种,用了宽度和高度罢了。 二、 实例教程 基础知识介绍完毕,开始实例教程: 我们以如何绘制一个具有平面风格的状态栏为例: 首先从CStatusBar继承一个类:CStatusBarNew。(如果无法通过类向导做这件事,而你又对MFC的MESSAGEMAP等等东西不熟悉,可以从CStatusBarCtrl继承一个,待生成代码后,把所有的CStatusBarCtrl改为CStatusBar) 在此,只需要重写WM_PAINT和WM_ERASEBKGND这两个消息的响应函数。 BOOL CStatusBarNew::OnEraseBkgnd(CDC* pDC) { // TODO: Add your message handler code here and/or call default CRect rect; GetWindowRect(&rect); ScreenToClient(&rect); CBrush brush(0xf2f2f2); pDC->FillRect(&rect, &brush); return TRUE; } 上面函数把状态栏背景用0xf2f2f2这种颜色填充。 void CStatusBarNew::OnPaint() { CPaintDC cDC(this); // device context for painting // TODO: Add your message handler code here CRect rcItem; cDC.SetBkMode(TRANSPARENT); cDC.SelectObject (::GetStockObject (NULL_BRUSH));//选入画刷 // 获取字体 CFont* pfont = GetFont(); CFont* def_font; if (pfont) def_font = cDC.SelectObject(pfont);//选入字体 CPen pen; pen.CreatePen(PS_SOLID, 1, RGB(0xBD, 0xBA, 0xBD)); CPen* pOldPen = cDC.SelectObject(&pen);//选入画笔 CBrush br(0x00f2f2f2); for ( int i = 0; i < m_nCount; i++ ) { GetItemRect (i, rcItem); //填充面板背景 cDC.FillRect(rcItem, &br); rcItem.bottom--; if(i == 0) rcItem.left += 2; //对每个面板画圆角矩形 cDC.RoundRect(rcItem, CPoint(5, 5)); //画面板上的文字 UINT nNewStyle = GetPaneStyle(i); //如果style为SBPS_DISABLED,则跳过不画 if ((nNewStyle & SBPS_DISABLED) != 0) continue; CString text = GetPaneText(i); UINT uFormat = DT_SINGLELINE | DT_NOPREFIX | DT_TOP | DT_LEFT; rcItem.left += 3; rcItem.top += 3; cDC.DrawText(text, rcItem, uFormat); } if (pfont) cDC.SelectObject(def_font);//恢复字体 //画右下角小标志(这里画了六个小圆圈) if (GetStyle() & SBARS_SIZEGRIP) { CRect rc; GetClientRect(&rc); rc.left = rcItem.right; rc.right--; rc.bottom--; rc.left = rc.right - rc.Width() / 4; rc.top = rc.bottom - rc.Width(); int w = rc.Width(); rc.top++; rc.left++; cDC.SelectObject(GetStockObject(GRAY_BRUSH)); cDC.Ellipse(&rc); rc.OffsetRect(-w, -w); cDC.Ellipse(&rc); rc.OffsetRect(w, 0); cDC.Ellipse(&rc); rc.OffsetRect(-w, w); cDC.Ellipse(&rc); rc.OffsetRect(-w, 0); cDC.Ellipse(&rc); rc.OffsetRect(2 * w, -2 * w); cDC.Ellipse(&rc); } cDC.SelectObject(pOldPen);//恢复画笔 } 上面的函数我们可以多次看到SelectObject的调用,这就是前面所说的绘图函数基本上都是有状态的。这个状态保存在HDC中,而SelectObject则设置HDC的状态。通常称为选入。至于注释中的恢复是怎么回事呢?这要从CPen CBrush CFont等等说起了,它们是对GDI对象的封装。GDI对象通过CreatePen CreateBrush CreateFont等等函数创建,返回一个HGDIOBJ。这些对象不使用的时候需要销毁,用DeleteObject函数,但是如果一个HGDIOBJ被选入到一个HDC中的时候,它就不能被销毁,这样就造成了GDI资源的泄漏。解决这一问题通常有两种做法: 第一种,就是上面代码中看到的: 先保存原来的HGDIOBJ,def_font = cDC.SelectObject(pfont); 用完了之后再恢复原来的 cDC.SelectObject(def_font); 这样做,就保证了pfont能被正确销毁,至于原来的def_font能不能被销毁,就不关我们的事了。 第二种,利用了系统的库存对象。库存GDI对象是windows系统预先创建的,不需要应用程序销毁。所以,不需要保存原来的HGDIOBJ,直接像这样 SelectObject (hdc, ::GetStockObject (NULL_BRUSH)); 或者cDC.SelectStockObject(NULL_BRUSH); 就可以保证HDC中没有被选入任何我们自己创建的画刷了。 这两种各有好处,视情况选用。 另外上面说大部分GDI函数都是有状态的,有一个例外就是FillRect函数,它靠一个传给他的画刷进行填充。 三、 技巧 实例讲述完毕,接下来有一些补充技巧: 1. GDI绘图技巧的学习:通过阅读、运行、调试别人源代码获得经验这条路径是最快的。 2.GDI程序的调试 调试GDI一般来说比其他程序困难,但是掌握了一些技巧也就没什么障碍了。调试GDI的时候,将IDE和代调试的程序窗口在桌面上尽量分开排列,不要重叠在一起。这样你能通过单步执行,看到每一步的绘图效果。 为配合上述策略,在应用程序初始化的时候加上下面一句: #ifdef _DEBUG GdiSetBatchLimit(1); #endif 这能保证调试时每一条GDI函数调用能马上产生效果。因为Windows为了性能优化,可能会分批处理GDI调用。 3.内存绘图 首先理解内存绘图,即把要绘制的东西先在内存中画好,然后一次性的画到屏幕上来。内存绘图经常用来防止闪烁。因为闪烁的原因是因为反差太大。例如你的绘图过程是先用白色擦除整个窗口,然后再将黑色的文字画到屏幕上来,这样在窗口重绘的时候,原本黑色文字区域就会白光一闪,然后再出现文字,也就是我们说的闪烁了。而内存绘图的过程呢,是先创建一个内存DC,然后在这个DC上把要绘制的图形画好,之后一次性的填到屏幕上去。 示例代码如下: HDC hDestDC; RECT rc; //..此处得到目标的HDC和目标的RECT HDC hdc = ::CreateCompatibleDC (hDestDC); HBITMAP hBitmap = ::CreateCompatibleBitmap (hDestDC, rc.right, rc.bottom); HBITMAP hOldBitmap = ::SelectObject (hDC, hBitmap); //... 此处用hdc进行绘图 //... ::BitBlt (m_hDestDC, rc.left, rc.top, rc.Width(), rc.Height(), hDC, rc.left, rc.top, SRCCOPY); ::SelectObject (hDC, hOldBitmap); 当然,这样用起来不太方便,可以将这些操作封装到一个叫CMemDC的对象中,利用构造和析构自动进行这些操作。直接使用CMemDC还有一个好处,调试GDI时,如果图形都在内存中绘制,那么还是看不到绘图过程。 代码如果这样写: CRect rc; GetWindowRect(&rc); #ifdef _DEBUG CPaintDC dc; #else CPaintDC cdc; CMemDC dc(cdc.m_hDC, &rc); #endif 那么就既能享受内存绘图的好处又能方便调试了。 VC中用GDI函数实规高速平滑动画 摘要:许多游戏软件的开发中,实现高速平滑的动画需要许多比较深的技术,如:OpenGL、DirectX,并且可能还要开发人员有深厚的数学功底。但是,我们在开发一些小游戏,或为应用程序的界面实现一些动画效果,就可能不用以上这些技术了,我们更多的是用Windows API提供或MFC封装后的GDI绘图函数来实现。为此我们可不可以用GDI来实现高速平滑的动画呢?答案是肯定的。本文教您如何用GDI函数来开发平滑无闪的动画,并以一个应用实例来介绍这些用法。 关键词:GDI,MFC,Bitmap,内存设备环境 一、动画原理。 大家都知道播放电影的原理:在规定时间(一般为1秒)播放24幅连续的画面,由于人的视觉暂留,所以人们在观看电影时,看到的就不是一幅一幅的画面,而是丰富精彩的场景。于是,我们也模仿电影播放原理来用在编程中实现平滑无闪的动画。其实,这个原理已经在当今动画技术中实现,但我们讨论的是在VC++中用GDI函数实现同样效果的技术。 我已采用此技术开发了一款纸牌游戏:“扑克麻将”。读者可到Http://www.csdn.net程序员大本营上下载,软件代号:9175。“扑克麻将”中使用的动画技术全部系文本介绍,从游戏中读者可看到:不论是发牌,出牌,选牌,吃牌等各种操作,游戏画面看不到一丝闪动,速动也极快。 二、实现方法。 其实本技术也很简单,其关键就是在内存中创建一个与显示动画的窗口区域一样大的位图,先用GDI函数绘制位图,然后在适当的时候从内存中显示出来。因为位图已经绘制好,不象平时编程一样边绘制边显示,所以,显示一帧图形时,减速少了闪烁,从而实现平滑动画;并且,图形是从内存中直接显示到当前窗体,所以速度也很快,从而实现高速动画。下面我们将介绍实现这些技术的步骤: 1、启动 VC++,创建一个MFC支持的单文档应用程序。 2、选择菜单项InertNew Class创建一个从CBitmap类继承的类,取名为:CMemBitmap。我们创建了一个位图类来模仿电影中的一帧画面,作为将要显示在窗口区域(电影屏幕)的图像。今后,所有的绘图操作都针对这个位图类进行,而这些绘图操作,我们可以用成员函数来实现,比如:显示一个位图、一段文字及GDI函数中所有的绘图函数。 3、创建好位图类后,为了同窗体联系起来,用窗体的CDC内存设备环境指针创建该位图与窗体的客户区一样大。为此在位图类头文件MemBitmap.h可声明一个CWnd指针成员变量:m_PWnd,用以指向窗体,如下代码所示: private: CWnd* pWnd; 再声明一个成员函数来创建位图,其声明代码如下所示: public: void        Init(CWnd* pwnd); 在MemBitmap.cpp中实现代码如下: //初始化位图类 void CMemBitmap::init(CWnd *pwnd) { RECT      rt; //保存窗体客户区域的大小的矩形类型变量 pWnd = pwnd;                            //获取窗体指针 pwnd->GetClientRect(&rt);         //得到窗体客户区域的大小 //利用窗体类的CDC指针在内存中创建位图 CreateCompatibleBitmap(pwnd->GetDC(), rt.right;, rt.bottom); } CreateCompatibleBitmap函数作用是初始化位图类,其原型如下: BOOL CreateCompatibleBitmap( CDC* pDC, int nWidth, int nHeight ); pDC是设备环境指针,本例用窗体的设备环境指针。nWidth和nHeight是指定该位图尺寸的高度与宽度,单位为象素。 4、添加成员函数完成绘图功能。为了能在动画中显示文本信息,我们添加一个成员变更来显示文本信息。其原型的代码如下: //MemBitmap.h文件中 public: void TextOut(int x, int y, int iSize, LPCSTR strText,COLORREF color); //MemBitmap.cpp文件中 void CMemBitmap::TextOut(int x, int y, int iSize, LPCSTR strText, COLORREF color) { CDC*  pDC = pWnd->GetDC();//获取窗体的指针 CFont  NewFont;   //文本的字体对象 CFont  *OldFont; //保存以前的字体指针 CDC  dcMem ;         //内存中的DC指针,以便调用GDI函数在位图中绘图 dcMem.CreateCompatibleDC(pDC);    //创建与窗体设备环境一样大小DC dcMem.SelectObject(this);       //将内存中的DC选择该类的位图对象 NewFont.CreatePointFont(iSize,"宋体");//创建显示文本的字体 OldFont = dcMem.SelectObject(&NewFont);     //选择新字体 dcMem.SetTextColor(color); dcMem.TextOut(x,y,message);//在指定位置显示文本 dcMem.SelectObject(OldFont); //释放 NewFont.DeleteObject(); dcMem.DeleteDC(); pWnd->ReleaseDC(pDC); } TextOut函数用于在指定位置用指定的大小,颜色显示文本。参数x,y分别是显示文本的位置,iSize指定文本字体的大小,color指定文本的颜色,strText指定要显示的内容。从以上代码中,用一个内存设备环境dcMem来显示文本:首先从窗体设备环境创建,再选择该位图类,之后,即可用CDC类的绘图函数进行绘图了。同样,读者可、以用该内存设备环境变量dcMem来绘制一个位图(从文件或资源来的)、画直线等所有GDI函数的操作,而我们添加函数功能在于将这些GDI函数进行封装,以便调用方便,这也是面向对象编程的思想。 5、我们再实现一个清位图函数,以便在适当时候用指定的颜色将位图填充,达到清图的效果,其代码如下: //清除位图的一个区 void CMemBitmap::Clear(int x1, int y1, int x2, int y2, COLORREF color) { CDC*  pDC = m_pWnd->GetDC(); CDC  dcMem ;                                                   //内存中的DC指针 dcMem.CreateCompatibleDC(pDC); dcMem.SelectObject(this); CBrush  *OldBrush , blbrush(color); dcMem.SetBkMode( TRANSPARENT ); dcMem.SetBkColor(color); OldBrush = dcMem.SelectObject( &blbrush ); dcMem.Rectangle( x1 , y1 , x2 , y2 ); dcMem.SelectObject(OldBrush); dcMem.DeleteDC(); m_pWnd->ReleaseDC(pDC); } 参数x1,y1,x2,y2指定了矩形区的尺寸,color指定了填充色。其实现方法与4中所述一样,在此不必多介绍。 6、添加了绘图函数,下面再介绍如何使用CMemBitmap类,来实现动画效果: 首先,我们在视图类(也可以是其它窗口类)中声明一个CMemBitmap成员变量m_MemBitmap,代码如下: private: CMemBitmap m_MemBitmap; 然后,我们重载Cview类函数OnInitialUpdate(),以便视图初始化结束后初始化位图对象,并且视图指针传递过去,其实现代码如下: void CTestBitmapView::OnInitialUpdate() { CView::OnInitialUpdate(); // TODO: Add your specialized code here and/or call the base class m_MemBitmap.init(this); SetTimer(1,100,NULL); } 在函数最后,启动了一个定时器, 我们将用定时来实现动画功能。 接着,我们重载定时器消息函数OnTimer实现动画功能。其实现代码如下: void CTestBitmapView::OnTimer(UINT nIDEvent) { int        x , y;//文本显示的位置 CRect    rect;//客户区域 CDC*    pDC = GetDC();//获取视图的DC CDC    dcComp; //得到客户区尺寸 GetClientRect(&rect); //随机获得要显示文本的位置 srand( (unsigned)time( NULL ) ); //控制文本显示的位置位于客户区以内 x = rand()%rect.Width()/2; y = rand()%rect.Height(); //在内存中显示文本 m_MemBitmap.Clear(rect.left,rect.top,rect.right,rect.bottom,RGB(0,0,0)); m_MemBitmap.TextOut(10,10,500,"固定的文本",RGB(255,255,255)); m_MemBitmap.TextOut(x,y,400,"GDI函数实现高速动画演示",RGB(255,255,0)); //内存设备环境将位图对象选入 dcComp.CreateCompatibleDC(pDC); dcComp.SelectObject(&m_MemBitmap); //用位传输函数显示出来 pDC->BitBlt(0,0,rect.Width(),rect.Height(), &dcComp, 0,0,SRCCOPY); dcComp.DeleteDC(); ReleaseDC(pDC); CView::OnTimer(nIDEvent); } 读者可根据注释理解代码的含义,需要说明的是:在内存中绘制位图时,本例采用了一个静态显示文本和一个随机动态显示的文本来比较,从运行情况可以看出动态显示的文本0.1秒就随机移动一次位置,虽然每次绘制位图都使用Clear函数清屏,但静态文本的显示没有一点闪动,动画非常平滑,速度也很快。 怎么样,很简单吧?如果您再创建一个线程后台绘制图形,将会实现很多特殊效果的动画来,我们可以将这项技术用在用户界面上或其他地方,将会收到意想不到的动画效果。 三、结束语 通过以例子,用GDI函数实现高速平滑的动画也很简单。我们可以添加绘制位图,画线、画矩形等成员函数,便能实现各种GDI绘图函数的操作,如果读者还有兴趣,可以在显示位图,作优化显示,如:不是将位图全部显示出来,而是显示其中动画的一部分,因为BitBlt函数作位传输很慢。我开发的纸牌游戏“扑克麻将”就经过优化,其动画速很快,如果读者有兴趣,请到程序员大本营(http://www.csdn.net )共享软件栏目下载。欢迎来信与我切磋VC++编程技巧, INCLUDEPICTURE "http://images.chinabyte.com/button/impressions.gif" \* MERGEFORMATINET 基于VC++的GDI常用坐标系统及应用 在Windows应用程序中,只要进行绘图,就要使用GDI坐标系统。Windows提供了几种映射方式,每一种映射都对应着一种坐标系。例如,绘制图形时,必须给出图形各个点在客户区的位置,其位置用x 和y两个坐标表示,x 表示横坐标,y表示纵坐标。在所有的GDI绘制函数中,这些坐标使用的是一种“逻辑单位”。当GDI函数将结果输出送到某个物理设备上时,Windows将逻辑坐标转换成设备坐标(如屏幕或打印机的像素点)。本文讨论了图形环境中的各个映射模式,包括它们是什么,怎么工作的,以及它们真正的含义。   一、基础知识 (一)逻辑坐标。逻辑坐标与设备无关,缺省地,一个逻辑单位等于设备中的一个象素。它是实现“所见即所得”的基础。例如,当程序员调用LineTo函数绘制25.4mm(1 英 寸) 长的直线时,他只要使用合适的映射模式,那么就并不需要考虑输出的是何种设备。若设备是VGA显示器,Windows自动将其转化为96个像素点;若设备是一个300dpi的激光打印机,Windows自动将其转化为300 个像素点。 (二)设备坐标。图形输出时,Windows将GDI函数中指定的逻辑坐标映射为设备坐标,在所有的设备坐标系统中,单位以像素点为准,水平值从左到右增大(正方向向右),垂直值从上到下增大(正方向向下)。Windows中包括以下3 种设备坐标,以满足各种不同需要: 1、客户区域坐标,包括应用程序的客户区域,客户区域的左上角为(0, 0)。 2、屏幕坐标,包括整个屏幕,屏幕的左上角为(0, 0)。屏幕坐标用在WM_MOVE消息中(对于非子窗口)以及下面的Windows 函数中:CreateWindow 和MoveWindow(都对于非子窗口)、GetMessage、GetCursorPos、GetWindowRect、WindowFromPoint 和SetBrushOrg 中。 用函数ClientToScreen 和ScreenToClient可以将客户区域坐标转换成屏幕区域坐标,或反之。   3、全窗口坐标,包括一个程序的整个窗口,包括标题条、菜单、滚动条和窗口框,窗口的左上角为(0,0)。使用GetWindowDC得到的窗口设备环境,可以将逻辑单位转换成窗口”坐标。   (三)映射。映射方式定义了Windows如何将GDI函数中指定的逻辑坐标映射为设备坐标。在下文中我们将介绍常用的映射方式。 此外,习惯上,我们将逻辑坐标所在的坐标系称为“窗口”;将设备坐标所在的坐标系称为“视口”。“窗口”依赖于逻辑坐标,可以是像素点、毫米或其他尺度。这一点请牢记,这对于下面的有关内容的理解至关重要。 二、默认的坐标系统 当在微软的窗口中进行绘图时,绘图的坐标原点在屏幕的左上角,任何物体在屏幕上定位都要参考这个坐标原点。在笛卡尔坐标系统中这个点被定义为坐标原点(0,0),水平坐标轴的正方向是从该点出发向右延伸,垂直坐标轴的正方向是从该点出发向下延伸。 图一、笛卡尔坐标系   这个坐标原点只是操作系统默认的坐标原点,所以如果你调用Ellipse(-100, -100, 100, 100)函数来绘制图形的话,你将得到一个圆,它的圆心位于屏幕的左上角,仅仅只有圆的四分之一部分(270度到360度的部分)显示在屏幕上。代码及效果图如下 void CExoDraw1View::OnPaint() { CPaintDC dc(this); // 绘图的设备上下文 CPen PenBlue; // 兰色画笔 PenBlue.CreatePen(PS_SOLID, 1, RGB(0, 12, 255)); dc.SelectObject(&pPen); dc.Ellipse(-100, -100, 100, 100); }   按照同样的原理,你可以使用CpaintDC的方法或按照你的要求创建函数来绘制任何几何或非几何图形。例如,下面的代码绘制了两条相互垂直的直线,垂点位与窗口的中心: void CExoDraw1View::OnPaint() { CPaintDC dc(this); // 绘图的设备上下文 CRect Recto; CPen PenBlue; PenBlue.CreatePen(PS_SOLID, 1, RGB(0, 12, 255)); dc.SelectObject(&PenBlue); dc.Ellipse(-100, -100, 100, 100); CPen PenBlack; PenBlack.CreatePen(PS_SOLID, 1, BLACK_PEN); dc.SelectObject(&PenBlack); // 得到客户区域的尺寸; GetClientRect(&Recto); dc.MoveTo(Recto.Width() / 2, 0); dc.LineTo(Recto.Width() / 2, Recto.Height()); dc.MoveTo(0, Recto.Height() / 2); dc.LineTo(Recto.Width(), Recto.Height() / 2); } 图三、代码效果图 三、更改坐标系统     正如上面所看到的,默认的坐标系统坐标原点位于窗口的左上角,水平轴的正方向向右,垂直轴的正方向向下。为了进一步说明这一点,让我们来绘制一个半径为50个单位,圆心位于(0,0)点,同时绘制一个连接(0,0)(100,100)两点的直线。 void CExoDraw1View::OnPaint() { CPaintDC dc(this); // device context for painting // A circle whose center is at the origin (0, 0) dc.Ellipse(-50, -50, 50, 50); // A line that starts at (0, 0) and ends at (100, 100) dc.MoveTo(0, 0); dc.LineTo(100, 100); } 图四、代码效果图 这种默认的坐标原点在大多数图形操作情况下是适用的,但并不是总适用,有时你需要控制坐标系统的原点,例如,很多CAD(图形辅助设计)应用程序就需要用户来定义坐标系统的原点。 MFC提供了各种函数来处理坐标定位及扩展绘制区域的问题,包括在屏幕上任意位置设置坐标原点的函数。因为你是在一个设备上下文上进行绘图操作,因此,你所需要做的就是调用CDC::SetViewportOrg()函数。这个函数重载了两个版本,这允许你使用X、Y坐标或是一个定义的Point点。这个函数的语法如下: SetViewportOrg(int X, int Y); SetViewportOrg(CPoint Pt); 调用这个函数时只需要简单地说明哪儿是你想定义的坐标原点,如果使用函数的第二个版本,参数可以是一个POINT结构或是一个MFC提供的Tpoint类。为了演示这个函数的效果,让我们将上例的坐标原点沿X轴正方向移动200个单位,Y轴正方向移动150个单位,这时绘制函数如下: void CExoDraw1View::OnPaint() { CPaintDC dc(this); //绘图的设备上下文; dc.SetViewportOrg(200, 150); // 圆心位于坐标原点(0, 0) dc.Ellipse(-50, -50, 50, 50); // 连接(0, 0) 和 (100, 100)点的直线; dc.MoveTo(0, 0); dc.LineTo(100, 100); }  图五、代码效果图   需要注意的是,你也可以相对于客户区域来指定坐标原点 void CExoDraw1View::OnPaint() { CPaintDC dc(this); //绘图的设备上下文; CRect Recto; //获取客户区尺寸; GetClientRect(&Recto); dc.SetViewportOrg(Recto.Width() / 2, Recto.Height() / 2); // A circle whose center is at the origin (0, 0) dc.Ellipse(-50, -50, 50, 50); // A line that starts at (0, 0) and ends at (100, 100) dc.MoveTo(0, 0); dc.LineTo(100, 100); }   图六、代码效果图   现在你已了解了如何设置坐标原点,让我们来将(380,220)点作为坐标原点,并绘制出笛卡尔的坐标轴: void CExoDraw1View::OnPaint() { CPaintDC dc(this); // device context for painting CRect Recto; dc.SetViewportOrg(380, 220); // Use a red pen CPen PenRed(PS_SOLID, 1, RGB(255, 0, 0)); dc.SelectObject(PenRed); // A circle whose center is at the origin (0, 0) dc.Ellipse(-100, -100, 100, 100); // Use a blue pen CPen PenBlue(PS_SOLID, 1, RGB(0, 0, 255)); dc.SelectObject(PenBlue); // Horizontal axis dc.MoveTo(-380, 0); dc.LineTo(380, 0); // Vertical axis dc.MoveTo(0, -220); dc.LineTo(0, 220); }  图七、代码效果图   正如已经看到的,SetViewportOrg()函数可以更改设备上下文的坐标原点,同时,它也用来规定坐标轴的正方向,即水平轴向右,垂直轴向下: 图八、坐标轴示意图   为了说明这一点,下面来绘制一条黄色的45度角的直线: void CExoDraw1View::OnPaint() { CPaintDC dc(this); // device context for painting dc.SetViewportOrg(380, 220); // Use a red pen CPen PenRed(PS_SOLID, 1, RGB(255, 0, 0)); dc.SelectObject(PenRed); // A circle whose center is at the origin (0, 0) dc.Ellipse(-100, -100, 100, 100); // Use a blue pen CPen PenBlue(PS_SOLID, 1, RGB(0, 0, 255)); dc.SelectObject(PenBlue); // Horizontal axis dc.MoveTo(-380, 0); dc.LineTo(380, 0); // Vertical axis dc.MoveTo(0, -220); dc.LineTo(0, 220); // An orange pen CPen PenOrange(PS_SOLID, 1, RGB(255, 128, 0)); dc.SelectObject(PenOrange); // A diagonal line at 45 degrees dc.MoveTo(0, 0); dc.LineTo(120, 120); }  图九、代码效果图   正如你所看到的,我们的直线没有在45度位置,而是位于坐标系统的第四象限,造成这种情况的原因是默认的坐标系统。 三、固定映射模式 为了控制设备上下文中的坐标轴的方向,可以使用CDC类的SetMapMode()函数,它的语法如下: int SetMapMode(int nMapMode); 这个函数将根据参数的设置的不同做两件事,一是控制坐标轴的方向;二是坐标系统的单位长度。 这个函数的参数是用来定义映射模式的整型常量。它可能的值是:MM_TEXT, MM_LOENGLISH、MM_HIENGLISH、MM_ANISOTROPIC、MM_HIMETRIC, MM_ISOTROPIC、 MM_LOMETRIC, MM_TWIPS。 默认情况下使用MM_TEXT映射模式。换句话说,如果你没有具体的规定某一映射模式,你的应用程序就将使用MM_TEXT映射模式。在这种映射模式下,设备上下文中的度量尺寸将使用默认的像素单位,水平坐标轴正方向向右,垂直坐标轴正方向向下。例如,上面的OnPaint事件可以用下面的代码重写,它将产生同样的效果,仿佛没有使用映射模式。 void CExoDraw1View::OnPaint() { CPaintDC dc(this); // device context for painting dc.SetMapMode(MM_TEXT); dc.SetViewportOrg(380, 220); // Use a red pen CPen PenRed(PS_SOLID, 1, RGB(255, 0, 0)); dc.SelectObject(PenRed); // A circle whose center is at the origin (0, 0) dc.Ellipse(-100, -100, 100, 100); // Use a blue pen CPen PenBlue(PS_SOLID, 1, RGB(0, 0, 255)); dc.SelectObject(PenBlue); // Horizontal axis dc.MoveTo(-380, 0); dc.LineTo(380, 0); // Vertical axis dc.MoveTo(0, -220); dc.LineTo(0, 220); // An orange pen CPen PenOrange(PS_SOLID, 1, RGB(255, 128, 0)); dc.SelectObject(PenOrange); // A diagonal line at 45 degrees dc.MoveTo(0, 0); dc.LineTo(120, 120); } 图十、代码效果图   MM_LOENGLISH模式,与其他一些映射模式(不包括MM_TEXT模式)一样,执行两个动作,它改变坐标轴的方向,垂直坐标轴的正方向向上; 图十一、MM_LOENGLISH映射模式下的坐标系 此外,度量单位改为0.01英寸,这意味着你提供的坐标将除以100,观察上述代码的MM_LOENGLISH映射效果 void CExoDraw1View::OnPaint() { CPaintDC dc(this); // device context for painting dc.SetMapMode(MM_LOENGLISH); dc.SetViewportOrg(380, 220); . . . } 图十二、代码效果图 正如你所看到的,直线现在位于坐标系的第一象限,同时,直线比以前缩短,圆也比以前的要小。 与MM_LOENGLISH映射模式相似,MM_HIENGLISH映射模式也是垂直坐标轴正向向上,只是它以0.001英寸为坐标单位,下面是它的效果: void CExoDraw1View::OnPaint() { CPaintDC dc(this); // device context for painting dc.SetMapMode(MM_HIENGLISH); dc.SetViewportOrg(380, 220); . . . Same as previous } 图十三、代码效果图 MM_LOMETRIC映射模式使用与上两种映射模式相同的坐标轴,不同的是MM_LOMETRIC使用0.1毫米为单位,下面是一个例子: void CExoDraw1View::OnPaint() { CPaintDC dc(this); // device context for painting dc.SetMapMode(MM_LOMETRIC); dc.SetViewportOrg(380, 220); . . . } 图十四、代码效果图   MM_HIMETRIC使用与上述三种映射模式相同的坐标系,但它的坐标单位是0.01毫米,下面例子代码如下: void CExoDraw1View::OnPaint() { CPaintDC dc(this); // device context for painting dc.SetMapMode(MM_HIMETRIC); dc.SetViewportOrg(380, 220); . . . Same as previous } 图十五、代码效果图 MM_TWIPS映射模式将每个逻辑单位(像素)除以20,实际上一twip等于1/1440 英寸,坐标系统仍然与上面几种映射方式相同。 void CExoDraw1View::OnPaint() { CPaintDC dc(this); // device context for painting CRect Recto; dc.SetMapMode(MM_TWIPS); dc.SetViewportOrg(380, 220); . . . } 图十六、代码效果图 四、自定义坐标系统 目前为止,我们使用的映射模式可以允许我们选择坐标轴的方向,但仅仅是Y轴的方向。而且,我们不能更改坐标系统的单位,这是因为各种映射模式(MM_TEXT, MM_HIENGLISH, MM_LOENGLISH, MM_HIMETRIC, MM_LOMETRIC, and MM_TWIPS)有固定的属性集,例如坐标轴的方向和坐标单位等。在CAD应用程序中,如果你需要灵活设置坐标轴方向及坐标单位的话,应该怎么做呢? 仔细研究下面的OnPaint()事件代码,它绘制了一个200X200像素大小的红边、浅绿色背景的正方形,这个正方形的顶点在(-100,-100)处,右底端位于(100,100)处。同时,从坐标原点处绘制一个45度的直线。 void CExoDraw1View::OnPaint() { CPaintDC dc(this); // device context for painting CPen PenRed(PS_SOLID, 1, RGB(255, 0, 0)); CBrush BrushAqua(RGB(0, 255, 255)); dc.SelectObject(PenRed); dc.SelectObject(BrushAqua); // Draw a square with a red border and an aqua background dc.Rectangle(-100, -100, 100, 100); CPen BluePen(PS_SOLID, 1, RGB(0, 0, 255)); dc.SelectObject(BluePen); // Diagonal line at 45 degrees starting at the origin (0, 0) dc.MoveTo(0, 0); dc.LineTo(200, 200); } 图十七、代码效果图 正如你所看到的,我们只得到了正方形的右下部分,同时直线指向时钟的三点到六点之间的方向。假定你想将坐标原点设置与窗口中央位置,或者是更精确一点,设置于点(340, 220)处,我们已经知道可以使用CDC::SetViewportOrg()(记住,这个函数只用来更改坐标原点,它并不影响坐标轴的方向及坐标单位。同时,需要注意的是,它使用的坐标单位是像素)函数,下面是一个例子(我们没有规定映射模式,所以程序使用的是默认的MM_TEXT映射模式)。 void CExoDraw1View::OnPaint() { CPaintDC dc(this); // device context for painting dc.SetViewportOrg(340, 220); CPen PenRed(PS_SOLID, 1, RGB(255, 0, 0)); CBrush BrushAqua(RGB(0, 255, 255)); dc.SelectObject(PenRed); dc.SelectObject(BrushAqua); // Draw a square with a red border and an aqua background dc.Rectangle(-100, -100, 100, 100); CPen BluePen(PS_SOLID, 1, RGB(0, 0, 255)); dc.SelectObject(BluePen); // Diagonal line at 45 degrees starting at the origin (0, 0) dc.MoveTo(0, 0); dc.LineTo(200, 200); } 图十八、代码效果图 为了控制你自己应用程序中的坐标系统单位,坐标轴的方向,可以使用MM_ISOTROPIC 或MM_ANISOTROPIC映射模式。第一件事是调用CDC::SetMapMode()函数,并在两个常量中选择一个(MM_ISOTROPIC或 MM_ANISOTROPIC)。下面是例子代码: void CExoDraw1View::OnPaint() { CPaintDC dc(this); // device context for painting dc.SetMapMode(MM_ISOTROPIC); dc.SetViewportOrg(340, 220); CPen PenRed(PS_SOLID, 1, RGB(255, 0, 0)); CBrush BrushAqua(RGB(0, 255, 255)); dc.SelectObject(PenRed); dc.SelectObject(BrushAqua); // Draw a square with a red border and an aqua background dc.Rectangle(-100, -100, 100, 100); CPen BluePen(PS_SOLID, 1, RGB(0, 0, 255)); dc.SelectObject(BluePen); // Diagonal line at 45 degrees starting at the origin (0, 0) dc.MoveTo(0, 0); dc.LineTo(200, 200); } 图十九、代码效果图 先抛开上面的图片。当调用CDC::SetMapMode(),并使用MM_ISOTROPIC或 MM_ANISOTROPIC作为参数后,并没有结束,这两种映射方式允许我们改变坐标轴的正方向及坐标单位。这两种映射方式的区别在于:MM_ISOTROPIC映射方式中水平、垂直坐标轴的单位相等,MM_ANISOTROPIC映射方式可以随意控制水平及垂直方向的坐标单位长度。 所以,在调用SetMapMode()函数并规定了MM_ISOTROPIC或MM_ANISOTROPIC映射模式后,你必须调用CDC:SetWindowExt()函数,这个函数用来计算老的或默认的坐标系中一个单位的长度。这个函数有两个版本: CSize SetWindowExt(int cx, int cy); CSize SetWindowExt(SIZE size); 如果使用第一版本,第一个参数CX说明了水平坐标轴上按照新的逻辑单位代表的长度,CY代表了垂直坐标轴上按照新的逻辑单位代表的长度。 如果你知道按照新的坐标单位计算需要的逻辑尺寸的话,可以使用第二个版本的函数,例子代码如下: void CExoDraw1View::OnPaint() { CPaintDC dc(this); // device context for painting dc.SetMapMode(MM_ISOTROPIC); dc.SetViewportOrg(340, 220); dc.SetWindowExt(480, 480); CPen PenRed(PS_SOLID, 1, RGB
/
本文档为【Windows_Gdi_应用-入门篇_(VC_SDK)】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
热门搜索

历史搜索

    清空历史搜索