正文

游戏编程起源(初学者)Ⅴ2006-01-26 12:37:00

【评论】 【打印】 【字体: 】 本文链接:http://blog.pfan.cn/mroske/9890.html

分享到:

第三章 跟踪你的窗口和使用GDI

简介
如果你看过了头两章,你或许已经在问我什么时候能给你讲一点有成就感的东东呢?OK,时候到了。这次我们将学习WINDOWS GDI(图形设备接口)和其它一些相关的东西,象响应用户输入和处理Windows产生的一些消息。至于显示图形,我们将接触三个课题:文本显示,绘制象素,显示位图。我们先来研究一下几个Windows消息的细节。
重复的话:你需要C语言的基础知识,最好看过上两章。由于本章将使你能做一个具体的图形DEMO,有一个源代码例程附在本章后面。是用Visual C++写的和编译的,所以吗,最好同我保持一致哦!好了,开动吧!

设备上下文
在第一章里,我们创建和注册了一个窗口类,其中有一行定义了窗口的风格(功能),是这个样子:

sampleClass.style = CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW; // standard settings

其中三个属性是很一般的,但这个——CS_OWNDC,需要解释一下。如果你记得,我曾经告诉过你这个属性允许窗口有自己独特的设备上下文,但直到现在,我们还没有具体的讲,OK,时间到了,开讲!
设备上下文是一个结构,是一个表现一组图形对象和属性的结构,还有一些输出设备的设置和属性。使用设备上下文允许你直接操纵图形,不用考虑低级细节。Windows GDI是一个图形翻译系统,是介于应用程序和图形硬件之间的一层。GDI可以输出到任意的兼容设备,不过最常使用的设备是视频监视器、图形硬拷贝设备(如打印机或绘图仪),或者是内存中的图元文本。GDI函数能够绘制直线、曲线、封闭的图形和文本。所有访问GDIWindows函数都需要一个设备上下文句柄作为参数。感谢上帝,这是非常容易做到的。你若想得到一个窗口的设备上下文句柄,你可以用这个函数:

HDC GetDC(
    HWND hWnd  // handle to a window
);


很简单,是不是?所有你做的是,把要操作的窗口的句柄传递给它,然后返回一个设备上下文句柄。如果你传递的是NULL,将返回整个屏幕的设备上下文(DC,以后都用DC表示)句柄。如果函数调用失败,将返回NULL
设备上下文不仅仅处理图形,但我们习惯于泛泛的认为它是处理图形的。处理显示图形的DC类型,称作显示DC,处理打印的,称作打印DC;处理位图数据的,称作内存DC,还有其它一些设备DC。感觉有点复杂吧,不要紧,这是Windows,它的主要功能就是迷惑群众。^_^一旦我们接触一些代码,就不会觉得难了。
当你结束使用DC时,一定要释放它,也就是释放它占用的内存空间。要把这种思想贯穿到以后的编程中去,占用了内存,不用时要释放,切记!释放DC是一个很简单的函数:

int ReleaseDC(
    HWND hWnd, // handle to window
    HDC hDC    // handle to device context
);


若成功释放,返回值是1,否则是0。参数有注释,我还是说一下:
HWND hWnd:你所要控制的那个窗口的句柄。如果你开始传递的是NULL,现在还要传递NULL
HDC hDCDC的句柄。
在用DCGDI进行图形显示前,我们先看看创建窗口实例时要遇到的几条重要的消息。我将要提到的四条消息是:WM_MOVEWM_SIZEWM_ACTIVATEWM_PAINT

追踪窗口状态
头两个是很简单的。当窗口被用户移动时将发送WM_MOVE消息,窗口新位置的坐标储存在lparam中。(还记得吗,消息在lparamwparam中被进一步描述,它们是消息控制函数的参数)lparam的低端字中存储窗口客户区左上角的坐标x,高端字中存储坐标y
当窗口的大小被改变时,将发送WM_SIZE消息。同WM_MOVE消息差不多,lparam的低端字中存储客户区的宽度,高端字存储高度。同WM_MOVE不同的是,wparam参数也控制了一些重要的东西。它可以是下列中任意一个值:
SIZE_MAXHIDE:其它的窗口被最大化了。
SIZE_MAXIMIZED:本窗口被最大化了。
SIZE_MAXSHOW:其它的窗口被还原了。
SIZE_MINIMIZED:本窗口被最小化了。
SIZE_RESTORED:窗口被改变了尺寸,但既没最大化,也没有最小化。
当我编写窗口实例时,我通常喜欢把窗口的当前位置和大小保留在几个全局变量里。假设我们命名这些全局变量为xPosyPosxSizeySize,你最好这样控制WM_SIZEWM_MOVE这两个消息:

if (msg == WM_SIZE)
{
    xSize = LOWORD(lparam);
    ySize = HIWORD(lparam);
}
if (msg == WM_MOVE)
{
    xPos = LOWORD(lparam);
    yPos = HIWORD(lparam);
}


现在轮到WM_ACTIVATE消息了。它告诉你一个新窗口被激活。这是很有用的,因为如果出现优先的申请,你就不可能处理程序里的所有逻辑。有时,例如写一个全屏的DIRECTX程序,忽略WM_ACTIVATE消息将导致你的程序出现致命的错误,可能它做了一些你不希望它做的事情。在任何情况下,守候WM_ACTIVATE消息从而采取行动,是一个好主意。
窗口被激活和被解除激活都会发出WM_ACTIVATE消息,我们可以通过检测wparam的低端字来得知是被激活还是被取消。这将有三种可能的值:
WA_CLICKACTIVE:窗口被鼠标激活。
WA_ACTIVE:窗口被其它东西激活。(键盘、函数调用、等等)
WA_INACTIVE:窗口被解除激活。
为了处理这个消息,我保留了另一个全局变量bFocus,当接收到WM_ACTIVATE消息,它的值将改变。示例如下:

if (msg == WM_ACTIVATE)
{
    if (LOWORD(wparam) == WA_INACTIVE)
        focus = FALSE;
    else
        focus = TRUE;

    // tell Windows we handled it
    return(0);
}


有两个相关联的消息WM_KILLFOCUSWM_SETFOCUS,在窗口接收到输入焦点的时候,Windows消息WM_SETFOCUS被发送给它,在失去焦点的时候则发送WM_KILLFOCUS消息。应用程序可以截取这些消息以得知输入焦点的任何改变情况。什么是输入焦点呢?存有输入焦点的应用程序(窗口)就是被激活的那个窗口。你就认为被激活的窗口就是输入焦点就行了。因为可能出现没有窗口具有输入焦点,所以我建议用WM_ACTIVATE消息跟踪你的窗口状态。(有些胡涂?不要紧,你就记住用WM_ACTIVATE就行了)往下进行。

WM_PAINT 消息
WN_PAINT
消息通知程序,全部或部分客户窗口需要重新绘制。当用户在最小化、重叠或调整客户窗口区域的时候,就会产生这条消息。重新绘制,你需要做两件事,首先是要用到WM_PAINT消息专用的一对函数,第一个是BeginPaint()函数,这是它的原形:

HDC BeginPaint(
    HWND hwnd,             // handle to window
    LPPAINTSTRUCT lpPaint  // pointer to structure for paint information
);


在我告诉你返回值是什么之前,让我们先看看参数:
HWND hwnd:需要重绘的窗口的句柄。你应该已经对于这种参数比较熟悉了。
LPPAINTSTRUCT lpPaint:这是很重要的一个。是指向PAINTSTRUCT结构的指针,该结构包含所有的要被重绘区域的信息。
继续之前,我应该给你看看PAINTSTRUCT结构:

typedef struct tagPAINTSTRUCT { // ps
    HDC hdc;
    BOOL fErase;
    RECT rcPaint;
    BOOL fRestore;
    BOOL fIncUpdate;
    BYTE rgbReserved[32];
} PAINTSTRUCT;


结构内的成员如下:
HDC hdc:哈哈,有理由复习一下设备上下文(DC,有的书中叫设备描述表)了。这是要被刷新区域的句柄。
BOOL fErase:指明应用程序是否应该抹去背景。如果是FALSE,说明系统已经删除了背景。还记得在Windows类中我们曾经用黑色画刷定义了一个背景吗?这就意味着系统将用这个画刷抹去无效的区域。
RECT rcPaint:这是最重要的一个成员。它将告诉你需要被重绘的无效区域的矩形。我将稍后告诉你RECT结构。
BOOL fRestoreBOOL fIncUpdateBYTE rgbReserved[32]:好消息,这些是保留成员,为老Windows服务的,所有你我都不必管它们。:)
现在我已经给你看了这么多,这就是BeginPaint()函数的全部。它做了三件事儿。首先,它使窗口再次有效,直到下一次被改变,WM_PAINT消息发出前,这个窗口都是有效的。第二,如果在窗口类(Windows class)里定义了背景画刷,就像我们做过的那样,就用这个画刷重绘无效的区域。(所谓无效,就是被改变的)第三,返回了被重绘区域的DC句柄。重绘的区域,是由RECT结构定义的:

typedef struct _RECT {
    LONG left;
    LONG top;
    LONG right;
    LONG bottom;
} RECT;


我们已经指出这个结构描绘了一个矩形,但是有一件事情需要说说。RECT包含左上角,但不包含右下角。什么意思呢?让我们先定义一个RECT对象:

RECT myRect = {0, 0, 5, 5};

这个RECT包含象素(00),但是没有达到(55),所以矩形的右下角实际是(44)。看起来没有什么意义,但是你得习惯它。
现在,还记得我所说的关于使用DC的事儿吗?一旦你用完了,你就必须释放它。OK,你记起来了,用EndPaint()函数释放。回应WM_PAINT消息,每次调用完BeginPaint()函数,必须匹配一个EndPaint()函数释放DC。这是函数的原形:

BOOL EndPaint(
    HWND hWnd,                 // handle to window
    CONST PAINTSTRUCT *lpPaint // pointer to structure for paint data
);


函数通过返回TRUEFALSE分别表明成功还是失败。有两个简单的参数:
HWND hWnd:就是窗口的句柄。
CONST PAINSTRUCT *lpPaint:指向PAINTSTRUCT类型的结构变量地址。同BeginPaint()的第二个参数是一回事。不要被CONST迷惑了,它只是保证和确认函数没有改变结构的内容。
你还可以通过调用ValidateRect()函数代替BeginPaint()函数使得窗口再次有效。但你得手工操作一切。可能我们真的什么时候就要用到它。所以给你它的原形:

BOOL ValidateRect(
    HWND hWnd,         // handle of window
    CONST RECT *lpRect // address of validation rectangle coordinates
);

通过返回TRUEFALSE来确定函数调用成功还是失败。参数很简单:
HWND hWnd:烦不烦,我不说了:)
CONST RECT *lpRect:是指向RECT结构是否有效的指针。如果你传递NULL,则整个客户区域都是有效的。
现在把以上讲到的做个样子给你看吧,假设我们已经定义了一个全局的变量hMainWindow作为我们的窗口句柄。

if (msg == WM_PAINT)
{
    PAINTSTRUCT ps; // declare a PAINTSTRUCT for use with this message
    HDC hdc; // display device context for graphics calls

    hdc = BeginPaint(hMainWindow, &ps); // validate the window

    // your painting goes here!

    EndPaint(hMainWindow, &ps); // release the DC

    // tell Windows we took care of it
    return(0);
}


这段代码很简单,也没做什么令人兴奋的事儿!OK,如果你不想用窗口类里的默认画刷重绘窗口,你必须自己做一些事情,包括使用我们还没有讲的图形部分。永远不要害怕,我们过一会儿就讲。现在我们在讨论消息,还有一些事情我必须解释。

关闭你的应用程序(关闭窗口)
待续。。。。。。

阅读(3412) | 评论(0)


版权声明:编程爱好者网站为此博客服务提供商,如本文牵涉到版权问题,编程爱好者网站不承担相关责任,如有版权问题请直接与本文作者联系解决。谢谢!

评论

暂无评论
您需要登录后才能评论,请 登录 或者 注册