yidabu 2007-5-10 14:13
D语言GUI入门2 WNDCLASSEX设计一个窗口类
D语言GUI入门2 WNDCLASSEX设计一个窗口类知识若不分享 实在没有意义 http://www.d-programming-language-china.org 20070508点击下面网址查看原文:http://www.d-programming-language-china.orgtag:D语言教程,D语言GUI,wndclassex的instance创建一个窗口的过程 D语言论坛按:下面内容多引自孙鑫的VC++深入详情解。D语言相关说明加上了"D语言论坛"按语,未加按语的地方适用于VC或VC和D通用。 原文内容多是针对使用ASCII字符集windows api讲解,明显使用ascii字符集的windows 98及以下已经过时了。 D语言论坛 http://www.d-programming-language-china.org 20070509查看了一下网站统计,网站访客中使用windows 98不到1%。因此文中yidabu补充了UNICODE的相关内容,对原文有一定程序修改。 创建一个完整的窗口,需要经过下面几个操作步骤: 设计一个窗口类; 注册窗口类; 创建窗口; 显示及更新窗口。WNDCLASSEX结构分析 一个完整的窗口具有许多特征,包括光标(鼠标进入该窗口时的形状)、图标、背景色等。窗口的创建过程类似于汽车的制造过程。我们在生产一个型号的汽车之前,首先要对该型号的汽车进行设计,在图纸上画出汽车的结构图,设计各个零部件,同时还要给该型号的汽车取一个响亮的名字,例如“奥迪A6”。在完成设计后,就可以按照“奥迪A6”这个型号生产汽车了。( 本文出处: http://www.d-programming-language-china.org ) 类似地,在创建一个窗口前,也必须对该类型的窗口进行设计,指定窗口的特征。当然,在我们设计一个窗口时,不像汽车的设计这么复杂,因为Windows已经为我们定义好了一个窗口所应具有的基本属性,我们只需要像考试时做填空题一样,将需要我们填充的部分填写完整,一种窗口就设计好了。在Windows中,要达到作填空题的效果,只能通过结构体来完成,窗口的特征就是由WNDCLASSEX结构体来定义的。WNDCLASSEX结构体的定义如下(请读者自行参看MSDN): http://msdn2.microsoft.com/en-us/library/ms633577.aspx QUOTE: 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; D语言论坛 http://www.d-programming-language-china.org 按: 查D语言的core32中的winuser.d模块,WNDCLASSEXW结构定义如下: [Copy to clipboard] [ - ]CODE: struct WNDCLASSEXW { UINT cbSize; /* Win 3.x */ UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HINSTANCE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCWSTR lpszMenuName; LPCWSTR lpszClassName; /* Win 4.0 */ HICON hIconSm; } alias WNDCLASSEXW * PWNDCLASSEXW; //, NEAR *NPWNDCLASSEXW, FAR *LPWNDCLASSEXW; alias WNDCLASSEXW * NPWNDCLASSEXW; alias WNDCLASSEXW * LPWNDCLASSEXW; 下面对该结构体的成员变量做一个说明。 1 cbSize D语言论坛 http://www.d-programming-language-china.org 按: 可以这么记忆:c是class,b是bytes。 在C++里有sizeof(WNDCLASSEX)取得,在D语言里用WNDCLASSEX.sizeof取得。( 本文出处: http://www.d-programming-language-china.org ) Specifies the size, in bytes, of this structure. Set this member to sizeof(WNDCLASSEX). Be sure to set this member before calling the GetClassInfoEx function. 2 style style指定这一类型窗口的样式,常用的样式如下: CS_HREDRAW 当窗口水平方向上的宽度发生变化时,将重新绘制整个窗口。当窗口发生重绘时,窗口中的文字和图形将被擦除。如果没有指定这一样式,那么在水平方向上调整窗口宽度时,将不会重绘窗口。 CS_VREDRAW 当窗口垂直方向上的高度发生变化时,将重新绘制整个窗口。如果没有指定这一样式,那么在垂直方向上调整窗口高度时,将不会重绘窗口。 CS_NOCLOSE 禁用系统菜单的Close命令,这将导致窗口没有关闭按钮。( 本文出处: http://www.d-programming-language-china.org ) CS_DBLCLKS 当用户在窗口中双击鼠标时,向窗口过程发送鼠标双击消息。 style成员的其他取值请参阅MSDN: http://msdn2.microsoft.com/en-us/library/ms633574.aspx 知识点 在Windows.h中,以CS_开头的类样式(Class Style)标识符被定义为16位的常量,这些常量都只有某1位为1。在VC++开发环境中,利用goto definition功能,可以看到CS_VREDRAW=0x0001,CS_HREDRAW=0x0002,CS_DBLCLKS =0x0008,CS_NOCLOSE=0x0200,读者可以将这些16进制数转换为2进制数,就可以发现它们都只有1位为1,并且为1的位各不相同。用这种方式定义的标识符称为“位标志”,我们可以使用位运算操作符来组合使用这些样式。例如,要让窗口在水平和垂直尺寸发生变化时发生重绘,我们可以使用位或(|)操作符将CS_HREDRAW和CS_VREDRAW组合起来,如style=CS_HREDRAW | CS_VREDRAW。假如有一个变量具有多个样式,而我们并不清楚该变量都有哪些样式,现在我们想要去掉该变量具有的某个样式,那么可以先对该样式标识符进行取反(~)操作,然后再和这个变量进行与(&)操作即可实现。例如,要去掉先前的style变量所具有的CS_VREDRAW样式,可以编写代码:style=style & ~ CS_VREDRAW。 在Windows程序中,经常会用到这种位标志标识符,后面我们在创建窗口时用到的窗口样式,也是属于位标志标识符。 3 lpfnWndProc D语言论坛 http://www.d-programming-language-china.org 按: 可以这么记忆:lp是指针,fn是function函数,也就是WindProc函数的指针。 成员变量lpfnWndProc是一个函数指针,指向窗口过程函数,窗口过程函数是一个回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外一方调用的,用于对该事件或条件进行响应。回调函数实现的机制是:( 本文出处: http://www.d-programming-language-china.org ) (1)定义一个回调函数。 (2)提供函数实现的一方在初始化的时候,将回调函数的函数指针注册给调用者。 (3)当特定的事件或条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理。 针对Windows的消息处理机制,窗口过程函数被调用的过程如下: (1)在设计窗口类的时候,将窗口过程函数的地址赋值给lpfnWndProc成员变量。( 本文出处: http://www.d-programming-language-china.org ) (2)调用RegsiterClassEx(&wndclass)注册窗口类,那么系统就有了我们所编写的窗口过程函数的地址。 (3)当应用程序接收到某一窗口的消息时,调用DispatchMessage(&msg)将消息回传给系统。系统则利用先前注册窗口类时得到的函数指针,调用窗口过程函数对消息进行处理。 一个Windows程序可以包含多个窗口过程函数,一个窗口过程总是与某一个特定的窗口类相关联(通过WNDCLASSEX结构体中的lpfnWndProc成员变量指定),基于该窗口过程。 lpfnWndProc成员变量的类型是WNDPROC,我们在VC++开发环境中使用goto definition功能,可以看到WNDPROC的定义: QUOTE: typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM); D语言论坛 http://www.d-programming-language-china.org 按: 查windows.d里,可以看到如下定义:( 本文出处: http://www.d-programming-language-china.org ) [Copy to clipboard] [ - ]CODE: alias LRESULT (* WNDPROC)(HWND, UINT, WPARAM, LPARAM); 在这里又出现了两个新的数据类型LRESULT和CALLBACK,再次使用goto definition,可以看到它们实际上是long和__stdcall。 从WNDPROC的定义可以知道,WNDPROC实际上是函数指针类型。 注意:WNDPROC被定义为指向窗口过程函数的指针类型,窗口过程函数的格式必须与WNDPROC相同。 D语言论坛 http://www.d-programming-language-china.org 按: LRESULT的L就是Long,对应D语言的数据类型就是int。 知识点 在函数调用过程中,会使用栈。__stdcall与__cdecl是两种不同的函数调用约定,定义了函数参数入栈的顺序,由调用函数还是被调用函数将参数弹出栈,以及产生函数修饰名的方法。关于这两个调用约定的详细信息,读者可参看MSDN。对于参数个数可变的函数,例如printf,使用的是__cdecl调用约定,Win32的API函数都遵循__stdcall调用约定。在VC++开发环境中,默认的编译选项是__cdecl,对于那些需要__stdcall调用约定的函数,在声明时必须显式地加上__stdcall。在Windows程序中,回调函数必须遵循__stdcall调用约定,所以我们在声明回调函数时要使用CALLBACK。使用CALLBACK而不是__stdcall的原因是为了告诉我们这是一个回调函数。注意,在Windows 98和Windows 2000下,声明窗口过程函数时,即使不使用CALLBACK也不会出错,但在Windows NT4.0下,则会出错。( 本文出处: http://www.d-programming-language-china.org ) 4 cbClsExtra D语言论坛按: b是bytes,Cls是Class。 Specifies the number of extra bytes to allocate following the window-class structure. The system initializes the bytes to zero.( 本文出处: http://www.d-programming-language-china.org ) 成员变量cbClsExtra:Windows为系统中的每一个窗口类管理一个WNDCLASS结构。在应用程序注册一个窗口类时,它可以让Windows系统为WNDCLASS结构分配和追加一定字节数的附加内存空间,这部分内存空间称为类附加内存,由属于这种窗口类的所有窗口所共享,类附加内存空间用于存储类的附加信息。Windows系统把这部分内存初始化为0。一般我们将这个参数设置为0。] 5 cbWndExtra D语言论坛 http://www.d-programming-language-china.org 按: 可以这么记忆:b是byte,Wnd是window。 成员变量cbWndExtra:Windows系统为每一个窗口管理一个内部数据结构,在注册一个窗口类时,应用程序能够指定一定字节数的附加内存空间,称为窗口附加内存。在创建这类窗口时,Windows系统就为窗口的结构分配和追加指定数目的窗口附加内存空间,应用程序可用这部分内存存储窗口特有的数据。Windows系统把这部分内存初始化为0。如果应用程序用WNDCLASS结构注册对话框(用资源文件中的CLASS伪指令创建),必须给DLGWINDOWEXTRA设置这个成员。一般我们将这个参数设置为0。 6 hInstance 成员变量hInstance指定包含窗口过程的程序的实例句柄。 7 hIcon d-programming-language-china.org按:这个图标是按下Alt+Tab切换任务栏程序时显示的图标。 成员变量hIcon指定窗口类的图标句柄。这个成员变量必须是一个图标资源的句柄,如果这个成员为NULL,那么系统将提供一个默认的图标。 在为hIcon变量赋值时,可以调用LoadIcon函数来加载一个图标资源,返回系统分配给该图标的句柄。该函数的原型声明如下所示:( 本文出处: http://www.d-programming-language-china.org ) QUOTE: HICON LoadIcon( HINSTANCE hInstance, LPCTSTR lpIconName) LoadIcon函数不仅可以加载Windows系统提供的标准图标到内存中,还可以加载由用户自己制作的图标资源到内存中,并返回系统分配给该图标的句柄,请参看MSDN关于LoadIcon的解释。但要注意的是,如果加载的是系统的标准图标,那么第一个参数必须为NULL。 LoadIcon的第二个参数是LPCTSTR类型。LPCTSTR是什么类型的数据,bbs.d-programming-language-china.org为最好的老师是msdn: http://msdn2.microsoft.com/en-us/library/aa383751.aspx QUOTE: LPCTSTR An LPCWSTR if UNICODE is defined, an LPCSTR otherwise. This type is declared in WinNT.h as follows: #ifdef UNICODE typedef LPCWSTR LPCTSTR; #else typedef LPCSTR LPCTSTR; #endif 再看下面这页: http://msdn2.microsoft.com/en-us/library/ms776419.aspx QUOTE: The letter "T" in a type definition, for example, TCHAR or LPTSTR, designates a generic type that can be compiled for either Windows code pages or Unicode. The letter "W" in a type definition, for example, WCHAR or LPWSTR, designates a wide character (Unicode) type. 也就是类型名中的T表示类型定义,W表示wide character宽字符类型。LoadIcon的第二个参数LPCTSTR,如果是unicode(windows98以上),指的是LPCWSTR,如果是ascii(windows98及以下)指的是LPCSTR。 D语言里使用unicode 字符集LoadIcon的例子: [Copy to clipboard] [ - ]CODE: LoadIcon(cast(HINSTANCE) null, cast(wchar*) IDI_APPLICATION) 在D语言里加载自定义图标的方法:( 本文出处: http://www.d-programming-language-china.org ) 1.1 下载资源编译器rcc.exe http://ftp.digitalmars.com/bup.zip 1.2 资源文件源文件icon.rc 内容只有一行 [Copy to clipboard] [ - ]CODE: IDI_ICON ICON DISCARDABLE "icon.ico" 1.3 用rcc.exe编译资源文件 [Copy to clipboard] [ - ]CODE: rcc -32 icon.rc 编译后生成资源文件icon.res 1.4 LoadIcon函数加载图标 [Copy to clipboard] [ - ]CODE: //bbs.d-programming-language-china.org 注:这里以unicode字符集windows api为例 wc.hIcon = LoadIcon( hInstance, std.utf.toUTF16z("IDI_ICON") ); wc.hIconSm = LoadIcon( hInstance, std.utf.toUTF16z("IDI_ICON") ); D语言论坛 http://www.d-programming-language-china.org 测试了一下wc.hIcon和wc.hIconSm的区别: 只用wc.hIcon时,标题栏左边的图标灰灰的。其他都正常。 只用wc.hIconSm时,Alt+tab切换任务栏程序时显示的程序图标是系统默认图标,其他正常。 知识点 在VC++中,对于自定义的菜单、图标、光标、对话框等资源,都保存在资源脚本(通常扩展名为.rc)文件中。在VC++开发环境中,要访问资源文件,可以单击左边项目视图窗口底部的ResourceView选项卡,你将看到以树状列表形式显示的资源项目。在任何一种资源上双击鼠标左键,将打开资源编辑器。在资源编辑器中,你可以以“所见即所得”的方式对资源进行编辑。资源文件本身是文本文件格式,如果你了解资源文件的编写格式,也可以直接使用文本编辑器对资源进行编辑。( 本文出处: http://www.d-programming-language-china.org ) 在VC++中,资源是通过标识符(ID)来标识的,同一个ID可以标识多个不同的资源。资源的ID实质上是一个整数,在“resource.h”中定义为一个宏。我们在为资源指定ID的时候,应该养成一个良好的习惯,即在“ID”后附加特定资源英文名称的首字母,例如,菜单资源为IDM_XXX(M表示Menu),图标资源为IDI_ XXX(I表示Icon),按钮资源为IDB_ XXX(B表示Button)。采用这种命名方式,我们在程序中使用资源ID时,可以一目了然。 8 hCursor 成员变量hCursor指定窗口类的光标句柄。这个成员变量必须是一个光标资源的句柄,如果这个成员为NULL,那么无论何时鼠标进入到应用程序窗口中,应用程序都必须明确地设置光标的形状。 在为hCursor变量赋值时,可以调用LoadCursor函数来加载一个光标资源,返回系统分配给该光标的句柄。该函数的原型声明如下所示: QUOTE: HCURSOR LoadCursor(HINSTANCE hInstance, LPCTSTR lpCursorName); LoadCursor函数除了加载的是光标外,其使用方法与LoadIcon函数一样。 9 hbrBackground D语言论坛 http://www.d-programming-language-china.org 按: 可以这样记忆:h是句柄,br是Brush。 成员变量hbrBackground指定窗口类的背景画刷句柄。当窗口发生重绘时,系统使用这里指定的画刷来擦除窗口的背景。我们既可以为hbrBackground成员指定一个画刷的句柄,也可以为其指定一个标准的系统颜色值。关于hbrBackground成员的详细说明,请参看MSDN。( 本文出处: http://www.d-programming-language-china.org ) 我们可以调用GetStockObject函数来得到系统的标准画刷。GetStockObject函数的原型声明如下所示: QUOTE: HGDIOBJ GetStockObject( int fnObject); 参数fnObject指定要获取的对象的类型,关于该参数的取值,请参看MSDN。GetStockObject函数不仅可以用于获取画刷的句柄,还可以用于获取画笔、字体和调色板的句柄。由于GetStockObject函数可以返回多种资源对象的句柄,在实际调用该函数前无法确定它返回哪一种资源对象的句柄,因此它的返回值的类型定义为HGDIOBJ,在实际使用时,需要进行类型转换。例如,我们要为hbrBackground成员指定一个黑色画刷的句柄,可以调用如下: QUOTE: wndclass.hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH); D语言论坛 http://www.d-programming-language-china.org 按: 在D语言里这样调用: [Copy to clipboard] [ - ]CODE: wndclass.hbrBackground=cast(HBRUSH) GetStockObject(BLACK_BRUSH); 当窗口发生重绘时,系统会使用这里指定的黑色画刷擦除窗口的背景。 10 lpszMenuName D语言论坛 http://www.d-programming-language-china.org 论坛按: yidabu是这样理解的:lp是指针,s是string字符串z是zero空结尾。 成员变量lpszMenuName是一个以空终止的字符串,指定菜单资源的名字。如果你使用菜单资源的ID号,那么需要用MAKEINTRESOURCE宏来进行转换。如果将lpszMenuName成员设置为NULL,那么基于这个窗口类创建的窗口将没有默认的菜单。要注意,菜单并不是一个窗口,很多初学者都误以为菜单是一个窗口。( 本文出处: http://www.d-programming-language-china.org ) 11 lpszClassName 成员变量lpszClassName是一个以空终止的字符串,指定窗口类的名字。这和汽车的设计类似,设计一款新型号的汽车,需要给该型号的汽车取一个名字。同样的,设计了一种新类型的窗口,也要为该类型的窗口取个名字,这里我们将这种类型窗口的命名为“sunxin2006”,后面将看到如何使用这个名称。 下面是D语言指定ClassName的一个例子: [Copy to clipboard] [ - ]CODE: char[] ClassName = "http://www.d-programming-language-china.org"; wndclass.lpszClassName = std.utf.toUTF16z(ClassName); 查D语言标准库Phobos手册中std.utf: QUOTE: wchar* toUTF16z(char[] s); Encodes string s into UTF-16 and returns the encoded string. toUTF16z() is suitable for calling the 'W' functions in the Win32 API that take an LPWSTR or LPCWSTR argument. 可见toUTF16z是专门用来转换char[]到wchar*,wchar*也就是windows api中的LPWSTR,或者LPCWSTR。 12 hIconSm D语言论坛按: 显示在程序标题栏左边的小图标。Sm就是Small。( 本文出处: http://www.d-programming-language-china.org ) Handle to a small icon that is associated with the window class. If this member is NULL, the system searches the icon resource specified by the hIcon member for an icon of the appropriate size to use as the small icon. D语言的例子: [Copy to clipboard] [ - ]CODE: //bbs.d-programming-language-china.org 注:得到程序实例的句柄 HINSTANCE hInst = cast( HINSTANCE ) GetModuleHandle(null); wndclass.hIconSm = LoadIcon( hInst, std.utf.toUTF16z("IDI_MYICON") ); 具体实现方法请见下面文章: http://bbs.d-programming-language-china.org/thread-568-1.htmlRegisterClassEx注册窗口类 注册窗口类的函数原型是: http://msdn2.microsoft.com/en-us/library/ms633587.aspx QUOTE: ATOM RegisterClassEx( CONST WNDCLASSEX *lpwcx ); D语言的windows.d中没有定义RegisterClassEx,所以要import win32.winuser,在dsource.org的core32项目中有winuser.d。 或者源代码开头先定义:( 本文出处: http://www.d-programming-language-china.org ) [Copy to clipboard] [ - ]CODE: ATOM RegisterClassExW(WNDCLASSEXW *); 在D语言中注册窗口类: [Copy to clipboard] [ - ]CODE: RegisterClassEx(&wndclass); 现在全部D语言代码是这个样子: [Copy to clipboard] [ - ]CODE: import std.c.windows.windows; import std.utf; import win32.api; extern(C) void gc_init(); extern(C) void gc_term(); extern(C) void _minit(); extern(C) void _moduleCtor(); extern(C) void _moduleDtor(); extern(C) void _moduleUnitTests(); char[] ClassName = "http://www.d-programming-language-china.org"; extern(Windows) int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,int nCmdShow) { int result; gc_init(); _minit(); try { _moduleCtor(); _moduleUnitTests(); result = myWinMain(hInstance,hPrevInstance,lpCmdLine,nCmdShow); _moduleDtor(); } catch(Exception e) { printf("catch %.*s\n",e.msg); result = 0; } finally { } gc_term(); return result; } int myWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,int nCmdShow) { HINSTANCE hInst = cast(HINSTANCE) GetModuleHandle(null); WNDCLASSEX wc; wc.cbSize = WNDCLASSEX.sizeof; wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; wc.lpfnWndProc = &WindowProc; wc.cbClsExtra = wc.cbWndExtra = 0; wc.hInstance = hInst; //bbs.d-programming-language-china.org注: D语言标准库的windows.d主要服务于ascii字符集的windows api,IDI_APPLICATION被定义为CHAR*,所以这里要转换成wchar* //如果IDI_APPLICATION来定义是CHAR,就可以用toUTF16z来转换 wc.hIcon = LoadIcon(hInst,std.utf.toUTF16z("IDI_MYICON")); wc.hCursor = LoadCursor(cast(HINSTANCE)null,cast(wchar*)IDC_CROSS); wc.hbrBackground = cast(HBRUSH)(COLOR_WINDOW+1); wc.lpszMenuName = null; //转换类名到以0结尾的宽字符集 wc.lpszClassName = toUTF16z(ClassName); wc.hIconSm = LoadIcon(hInst,std.utf.toUTF16z("IDI_MYICON")); auto a = RegisterClassEx(&wc); assert(a); return 0; } extern(Windows) LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_SIZE: { return(0); } case WM_CREATE: { return(0); } case WM_DESTROY: { PostQuitMessage(0); return(TRUE); } default: return(DefWindowProcW(hwnd, uMsg, wParam, lParam)); } return 0; } ( lastupdate:20070510 最新文章请访问http://www.d-programming-language-china.org )关于一大步成功社区:yidabu提倡在交流中学习,在分享中提高收集感兴趣的知识,写下心得,通过网络与别人一起分享理解一点就实践一步,收获什么就分享什么,成功就是这样一点点一步步累积起来的网络只是一个工具,只有自己身心提高才是实实在在的。d-programming-language-china.org为大家提供一个学习交流各种知识的平台