窗口是屏幕上的矩形区域,消息窗口功能有限,因为我们不能添加四个以上的按钮以及菜单等,而且添加的按钮必须是windows提供的按钮,不能自定义。所以我们有必要自己创建一个多功能可自定义的窗口。

自己的窗口

创建窗口最重要的函数是CreateWindow,它可以创建重叠式窗口,弹出式窗口,子窗口等。而且可以自定义各种功能。

HWND CreateWindow(      

    LPCTSTR lpClassName//窗口类

    LPCTSTR lpWindowName

    DWORD dwStyle

    int x

    int y

    int nWidth

    int nHeight

    HWND hWndParent

    HMENU hMenu

    HINSTANCE hInstance

    LPVOID lpParam 

);

从函数原型可以看出创建窗口的各种属性,一种窗口含有显示程序名称的标题列、菜单甚至可能还有工具列和滚动条,另一种窗口是对话框,在上面可以放置各种控件。而这些控件也是窗口,称为子窗口。

窗口消息处理程序

窗口处理函数是理解消息机制的关键,windows处理将消息循环中取得的各种消息放入这个函数中进行处理,该函数会比对各个匹配项直到找到对应的处理项,如果没有找到就传给一个默认的窗口函数,是程序正常结束。该函数可以在程序中也可以在dll中。使用窗口类使多个窗口能够属于同一个窗口类,并使用同一个窗口消息处理函数。

HELLOWIN程序

//===========================

//  (c)狗尾草 2008.1.19

//===========================

#include<windows.h>

LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,PSTR szCmdLine,int iCmdShow)

{

 static TCHAR szAppName[]="HelloWin";

 HWND hWnd;

 MSG msg;

 WNDCLASS wndclass;

 wndclass.cbClsExtra=0;

 wndclass.cbWndExtra=0;

 wndclass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);

 wndclass.hCursor=LoadCursor(NULL,IDC_ARROW);

 wndclass.hIcon=LoadIcon(NULL,IDI_APPLICATION);

 wndclass.hInstance=hInstance;

 wndclass.lpfnWndProc=WndProc;

 wndclass.lpszClassName=szAppName;

 wndclass.lpszMenuName=NULL;

 wndclass.style=CS_HREDRAW|CS_VREDRAW;

 if(!RegisterClass(&wndclass))

 {

  MessageBox(NULL,"This program requires windows nt!",szAppName,MB_ICONERROR);

  return 0;

 }

 hWnd=CreateWindow(szAppName,

  "The Hello Program!",

  WS_OVERLAPPEDWINDOW,

  CW_USEDEFAULT,

  CW_USEDEFAULT,

  CW_USEDEFAULT,

  CW_USEDEFAULT,

  NULL,

  NULL,

  hInstance,

  NULL);

 ShowWindow(hWnd,iCmdShow);

 UpdateWindow(hWnd);

 while(GetMessage(&msg,NULL,0,0))

 {

  TranslateMessage(&msg);

  DispatchMessage(&msg);

 }

 return msg.wParam;

}

LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)

{

 HDC hdc;

 PAINTSTRUCT ps;

 RECT rect;

 switch(message)

 {

 case WM_CREATE:

  return 0;

 case WM_PAINT:

  hdc=BeginPaint(hWnd,&ps);

  GetClientRect(hWnd,&rect);

  DrawText(hdc,"Hello,WindowsXP!",

           -1,&rect,DT_SINGLELINE|DT_CENTER|DT_VCENTER);

  EndPaint(hWnd,&ps);

  return 0;

 case WM_DESTROY:

  PostQuitMessage(0);

  return 0;

 }

return DefWindowProc(hWnd,message,wParam,lParam);

}

调用的函数和功能如下,绝大多数在winuser.h:

LoadIcon

 加载图标供程序使用

LoadCursor

加载鼠标光标供程序使用

GetStockObject

 取得一个图形对象

RegisterClass

 为程序窗口注册窗口类别

MessageBox 

显示消息框

CreateWindow

 根据窗口类别建立一个窗口

ShowWindow

 在屏幕上显示窗口

UpdateWindow

 指示窗口自我更新

GetMessage

 从消息队列中取得消息

TranslateMessage 

转译某些键盘消息

DispatchMessage

 将消息发送给窗口消息处理程序

PlaySound 

播放一个声音文件

BeginPaint

 开始绘制窗口

GetClientRect 

取得窗口显示区域的大小

DrawText

 显示字符串

EndPaint

 结束绘制窗口

PostQuitMessage

 在消息队列中插入一个「退出程序」消息

DefWindowProc

 执行内定的消息处理

关于一些常量定义的约定如下:

前缀

类别

CS

窗口类别样式

CW

建立窗口

DT

绘制文字

IDI

图示ID

IDC

游标ID

MB

消息框

SND

声音

WM

窗口消息

WS

窗口样式

常数值是没有必要记忆的,记住常量的一些大写标识符就可以了。

各种句柄

标识符

含义

HINSTANCE

执行实体(程序自身)句柄

HWND

窗口句柄

HDC

设备内容句柄

句柄通常是调用函数取得的,起到一个标识身份的目的。其实实质是一个32位整数。

数据结构注解

窗口类结构

typedef struct { 

    UINT cbSize; 

    UINT style; 

    WNDPROC lpfnWndProc; 

    int cbClsExtra; 

    int cbWndExtra; 

    HINSTANCE hInstance; 

    HICON hIcon; 

    HCURSOR hCursor; 

    HBRUSH hbrBackground; 

    LPCTSTR lpszMenuName; 

    LPCTSTR lpszClassName; 

    HICON hIconSm; 

} WNDCLASSEX, *PWNDCLASSEX;

窗口是根据窗口类建立的,窗口类相当于一个模板。他定义了各种属性,定义后打造出来的窗口就是他定义的那个样子了。如果窗口类没有定义完全,注册的时候就会失败,我在写的过程中漏写了几个属性导致这样的错误

该消息是在注册的时候发生的。

Msg数据结构

ttypedef struct tagMSG { HWND hwnd ; UINT message ; WPARAM wParam ; LPARAM lParam ; DWORD time ; POINT pt ; } MSG, * PMSG ; 

从消息队列中取得的消息来填充各个属性。

Point数据结构

typedef struct tagPOINT { LONG x ; LONG y ; } POINT, * PPOINT

存放该消息时的鼠标坐标。

建立窗口

HWND CreateWindow(      

    LPCTSTR lpClassName

    LPCTSTR lpWindowName

    DWORD dwStyle

    int x

    int y

    int nWidth

    int nHeight

    HWND hWndParent

    HMENU hMenu

    HINSTANCE hInstance

    LPVOID lpParam 

);

其中的大部分参数都可以猜出意思,最后一个参数有必要解释一下,是用来传递额外的数据的,这里并没有用到就设为NULL了。该函数返回一个窗口句柄,用来唯一表示一个窗口的。这样其他函数才知道作用在哪个窗口上。

建立窗口后调用ShowWindow (hwnd, iCmdShow)来显示窗口,第二个参数是初始的显示方式。UpdateWindow (hwnd)是用来重画窗口的。

消息循环

while(GetMessage (&msg, NULL, 0, 0)) { 

   TranslateMessage (&msg) ; 

   DispatchMessage (&msg) ; }    

消息循环功能是将事件转换为消息并给处理函数处理。TranslateMessage用来解释消息,DispatchMessage用来投递给消息处理函数,非常神奇的一个函数。

WM_PAINT消息

显示区域无效时才产生WM_PAINT消息,遮盖后重新要求显示,此时为无效。当然改变窗口大小也为无效。

关于程序的结束

当WM_DESTROY消息收到后执行PostQuiteMessage产生WM_QUITE消息,这样该消息GetMessage()时返回的是0,导致消息循环结束。WinMain函数继续执行,这个例子中是返回并结束程序。从这里我可以想象到,windows程序主要是消息循环和消息处理函数的交互。除此之外的都是一些设置和初始化等事务性的例程。

汇编版本

;==========================

;  (c)狗尾草 2008.1.19

;==========================

.386

.Model Flat, StdCall

Option Casemap :None

Include windows.inc

Include user32.inc

Include kernel32.inc

Include gdi32.inc

includelib gdi32.lib

IncludeLib user32.lib

IncludeLib kernel32.lib

include macro.asm

 

 WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD

 WndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD

 

.DATA

 szClassName db "HELLOWIN",0

 

.DATA?

 hInstance dd ?

 

.CODE

START:

 invoke GetModuleHandle,NULL  ;取得当前进程实例

 mov hInstance,eax

 invoke WinMain,hInstance,NULL,NULL,SW_SHOWDEFAULT

 invoke ExitProcess,0

WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,CmdShow:DWORD

 LOCAL wc   :WNDCLASSEX

 LOCAL msg  :MSG

 local hWnd :HWND

 

 mov wc.cbSize,sizeof WNDCLASSEX

 mov wc.style,CS_HREDRAW or CS_VREDRAW or CS_BYTEALIGNWINDOW

 mov wc.lpfnWndProc,offset WndProc

 mov wc.cbClsExtra,NULL

 mov wc.cbWndExtra,NULL

 push hInst  ;存储器变量用push和pop完成赋值

 pop wc.hInstance

 mov wc.hbrBackground,COLOR_BTNFACE+1

 mov wc.lpszMenuName,NULL

 mov wc.lpszClassName,offset szClassName

 invoke LoadIcon,hInst,100

 mov wc.hIcon,eax

 invoke LoadCursor,NULL,IDC_ARROW

 mov wc.hCursor,eax

 mov wc.hIconSm,0

 invoke RegisterClassEx, ADDR wc

 invoke CreateWindowEx,NULL,ADDR szClassName,CTXT("henry's hellowin program"),WS_OVERLAPPEDWINDOW,200,200,400,200,NULL,NULL,hInst,NULL

 mov hWnd,eax

 invoke ShowWindow,hWnd,SW_SHOWNORMAL

 invoke UpdateWindow,hWnd

 

 StartLoop:

  invoke GetMessage,ADDR msg,NULL,0,0

   cmp eax, 0

   je ExitLoop

    invoke TranslateMessage, ADDR msg

    invoke DispatchMessage, ADDR msg

   jmp StartLoop

 ExitLoop:

 

mov eax,msg.wParam

ret

WinMain endp

WndProc proc hWin:DWORD,uMsg:DWORD,wParam :DWORD,lParam :DWORD

   LOCAL hdc:HDC

   LOCAL ps:PAINTSTRUCT

   LOCAL rect:RECT

 .if uMsg==WM_CREATE

  

 .elseif uMsg == WM_DESTROY

  invoke PostQuitMessage,NULL

 .elseif uMsg == WM_PAINT

  invoke BeginPaint,hWin,ADDR ps

  mov hdc,eax

  invoke GetClientRect,hWin,ADDR rect

  invoke DrawText,hdc,CTXT("HELLO,WINDOWS XP!"),-1,ADDR rect,DT_SINGLELINE or DT_CENTER or DT_VCENTER

  invoke EndPaint,hWin,ADDR ps

 .else

  invoke DefWindowProc,hWin,uMsg,wParam,lParam

 .endif

 ret

WndProc endp

END START

汇编版本基本结构同C版,主要更注重细节方面。程序实例句柄由一个特殊的函数取得GetModuleHandle。函数符合先声明后使用的特点。这点与严格的c语言是相同的。各种高级的伪指令的使用,可以说和C语言已经是哥哥弟弟了。程序运行的结果和C版没任何区别。