Win视窗编程和DOS下编程不同,但是类似。Windows应用程序也有它的入口函数,DOS程序中的入口函数是main函数,Windows程序的入口函数是WinMain函数。新建Win32 Application, 选择Empty proj, Finished 完成HelloMsg项目创建。添加HelloMsg.c文件,具体编码如下:
#include <Windows.h>
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine,int iCmdShow)
{
MessageBox(NULL,"Hello World", "Title", 0);
return 0;
}
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
WinMain()函数是入口函数,调用MessageBox()函数之后就返回0结束应用程序。MessageBox()函数的功能就是弹出一个确认对话框,并在这个对话框上显示Hello World. 非常简单,但是它已经脱离了命令行的方式,开始了基于窗口的编程。
Windows消息循环
Windows应用程序是基于窗口的应用程序,而整个Windows操作系统是基于消息驱动的。这就意味着窗口乃至整个系统所发生的事件都被封装为各种各样的消息。OS和APP通过接收消息,分析消息附带的参数信息来进行相关的处理。不管什么语言开发的程序,在WindowsOS上运行都要有一个能够接受并处理消息的循环,这就是Windows程序的核心内容---消息循环机制。
WindowsOS中包括以下几种消息:
1.系统定义的消息
2.应用程序定义的消息
那么消息是如何运作的呢?这里,Windows有一个概念叫做消息队列,OS自己拥有一个消息队列,每个WINDOWS程序也有一个自己的消息队列。系统在有事件发生时,例如,单击鼠标或按下键盘中的某个键,OS就将这个时间转换成一个消息结构,放到自己的消息队列中。根据消息中的句柄将消息分发到应用程序的消息队列中,应用程序使用PeekMessage函数判断是否有消息,使用GetMessage函数取出消息,使用TranslateMessage函数转换消息最后使用DispatchMessage函数将消息分发出去。典型的处理案例如下:
for(;;)
{
if(PeekMessage(&msg,NULL,0,0,PM_NOREMOVE)) //判断是否消息
{
if(GetMessage(&msg,NULL,0,0))//取出消息
{
TranslateMessage(&msg);//消息转换
DispatchMessage(&msg);//分发消息
}
else
{
break;
}
}
}
分发出去之后,OS使用回调机制调用应用程序自己的消息处理函数来处理消息,所谓回调就是与应用程序调用操作系统的API函数相反,由操作系统来调用应用程序的函数个过程叫做回调。一个回调函数示例如下:
LRESULT CALLBACK WndProc(HWND hwnd , UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_PAINT:
//do something
return 0;
case WM_KEYDOWN:
//do something
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc (hwnd, message, wParam,lParam);
}
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
在这个函数中,采用switch-case 结构针对不同的消息作不同的处理就是windows程序的核心部分。
GDI绘图
绘图是游戏的第一个任务,其次就是操作即需要响应键盘等输入设备的消息。在windows下绘图最基本的需要就是GDI即 Graphical Device Interface 图像设备接口。GDI函数大致可分析如下4类:
1.取得(或者建立)和释放(或者清除)设备内容的函数, 绘图时需要设备内容句柄。GetDC和RealseDC函数可以在非WM_PAINT的消息处理期间来做到这一点,而BeginPaint和EndPaint函数在进行绘图的WM_PAINT消息处理期间使用。
2.绘图函数,如TextOut函数在窗口的显示区域显示一些文字。
3.设定和取得设备内容参数的函数, 设备内容的"属性"决定有关绘图函数如何工作的细节。例如,用SetTextColor来指定TextOut所绘制的文字色彩。设备内容的所有属性都有默认值,取得设备内容时这些默认值就设定好了。对于所有的Set函数,都有对应的Get函数,以便取得目前设备内容属性。
4.使用GDI对象的函数, GDI对象包括画笔,建立填入封闭区域的画刷、字体、位图等。
注:位图是位的矩形数组,这些位对应于显示设备上的图素,它们是位映射图形的基础工具。位图通常用于在视讯显示器或打印机上显示复杂图像。位图还可以用于显示必须快速绘制的小图像。GDI支持两种形态的位图--旧式的"设备相关"位图(是GDI对象)和新的"设备无关"位图(可以存储在磁盘文件中)。
设备内容的句柄
在绘图之前,首先必须获得一个设备内容的句柄。最常用的取得并释放设备内容句柄的方法是,在处理WM_PAINT消息时,使用BeginPaint和EndPaint调用:
hdc = BeginPaint(hwnd, &ps);
//do something
EndPaint(hwnd, &ps);
变量ps是型态为PAINTSTRUCT的结构,该结构的hdc字段是BeginPaint传回的设备内容句柄。PAINTSTRUCT结构又包含一个名为rcPaint的RECT结构,rcPaint定义一个包围窗口显示区域无效范围的矩形。使用从BeginPaint获得设备内容句柄,只能在这个区域内绘图。BeginPaint调用使该区域有效。
Windows程序还可以在处理非WM_PAINT消息时取得设备内容:
hdc = GetDC(hwnd);
//do something
ReleaseDC(hwnd,hdc);
这个设备内容适用于窗口句柄为hwnd的显示区域。这些调用与BeginPaint和EndPaint的组合之间的基本区别是,利用从GetDC传回的句柄可以在整个显示区域上绘图。当然,GetDC和ReleaseDC不使显示区域中任何可能的无效区域变成有效。
Windows程序还可取得适用于整个窗口(而不仅限于窗口的显示区域)的设备内容句柄:
hdc = GetWindowDC(hwd);
//do something
ReleaseDC(hwnd, hdc);
这个设备内容除了显示区域之外,还包括窗口的标题列、菜单、滚动条和框架。GetWindowDC函数很少使用,如果想尝试使用它,则必须拦截处理WM_NCPAINT消息,Windows使用该消息在窗口的非显示区域上绘图。
BeginPaint,GetDC和GetWindowDC获得的设备内容都与视讯显示器上的某个特定窗口相关。取得设备内容句柄的另一个更通用的函数是CreateDC:
hdc = CreateDC(pszDriver, pszDevice,pszOutput, pData);
// do something
DeleteDC(hdc);
也可通过在调用GetDC时使用一个NULL参数,从而取得整个屏幕的设备内容句柄。使用位图时,取得一个"内存设备内容"有时有用:
hdcMem = CreateCompatibleDC(hdc);
//do something
DeleteDC(hdcMem);
可以将位图选进内存设备内容,然后使用GDI函数在位图上绘图。
可以通过使用CreatePen或CreatePenIndirect函数来自定义一个"逻辑画笔",因为这些函数在创建画笔时不需要设备内容句柄作为参数,即通过这两个函数创建的画笔是与设备无关的逻辑画笔,直到调用SelectObject之后,画笔才与设备内容发生联系。因此,可以对不同的设备使用相同的逻辑画笔。
CreatePen创建画笔:
hpen = CreatePen(ipenStyle, iwidth,crcolor);
ipenStyle是画笔的样式,可以是:PS_SOLID,PS_NULL等值,iWidth是画笔宽度如果为0, 则代表画笔宽度为一个像素,如果在选择画笔样式为点线或虚线的同时 iWidth宽度值设置为大于1,那么windows将采用实线画笔来代替这些虚线画笔来画。尤其在支持缩放图像中使用这些虚线时要特别注意,一旦缩放的线宽超过1它们就变成了实线。crColor是笔的颜色
也可以通过建立一个型态为LOGPEN(逻辑画笔)的结构,然后调用CreatePenIndirect()来创建画笔。
LOGPEN logPen;
LOGPEN 结构有lopnStyle,lopnWidth和lopnColor(COLORREF)三个变量。
然后将结构的地址传递给CreatePenIndirect来创建画笔:
hpen = CreatePenIndirect(&logpen);
定义画笔是一种"GDI对象",所以使用完之后要删除自定义的GDI对象,如DeleteObject(hpen);
GDI位图对象
GDI位图对象有时也称为设备相关位图(DDB).DDB是Windows图形设备接口的图形对象之一(其中还包括绘图笔,画刷,字体,metafile和调色盘)。这些图形对象存储在GDI模块内部,由应用程序软件以句柄数字的方式引用。可以将DDB句柄存储在一个HBITMAP(handle to a Bitmap, 位图句柄)型态的变量中。
HBITMAP hBitmap;
然后,通过调用DDB建立的一个函数来获得句柄,如CreateBitmap
CreateBitmap函数用法如下:
hBitmap = CreateBitmap(cx,cy, cPlanes,cBitsPixel,bits);
CreateBitmap函数配置并初始化GDI内存中的一些内存来存储关于位图的信息以及实际位图的信息,前两个参数是位图的宽度和高度(以像素为单位),第三个参数是颜色面的数目,第四个参数是每个像素的位数,第五个参数是指向一个以特定颜色格式存储的位数组的指针,数组内存放有用来初始化该DDB的图像。如果不想用一张现有的图像来初始化DDB,可以将最后一个参数设为NULL。
当程序使用完位图以后,就要清除这段内存:
DeleteObject(hBitmap);
可以用CreateCompatibleBitmap来简化问题:
hBitmap = CreateCompatibleBitmap(hdc, cx, cy);
此函数建立一个与设备兼容的位图,hdc即此设备的内容句柄。通常设备内容指的是特殊的图形输出设备(例如显示器或打印机)以及其设备驱动程序。内存设备内容只位于内存中,它不是真正的图形输出设备,可以说与指定的真正的设备"兼容"。要建立一个内存设备内容,首先要有实际设备的设备内容句柄,如果是hdc, 可以用如下方法来建立内存设备内容:
hdcMem = CreateCompatibleDC(hdc);
建立的内存设备内容最终必须通过DeleteDC来删除。