⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 3.窗口和消息.txt

📁 本书介绍了在Microsoft Windows 98、Microsoft Windows NT 4.0和Windows NT 5.0下程序写作的方法
💻 TXT
📖 第 1 页 / 共 5 页
字号:
程序可以根据需要来使用预留的空间。HELLOWIN没有使用它们,所以设定值为0。否则,和匈牙利表示法所指示的一样,这个字段将被当成「预留的字节数」。(在第七章的程序CHECKER3将使用cbWndExtra字段。)

下一个字段就是程序的执行实体句柄(它也是WinMain的参数之一):

wndclass.hInstance = hInstance ;
        
叙述

wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
        
为所有依据这个窗口类别建立的窗口设置一个图标。图标是一个小的位图图像,它对使用者代表程序,将出现在Windows工作列中和窗口的标题列的左端。在本书的后面,您将学习如何为您的Windows程序自订图标。现在,为了方便起见,我们将使用预先定义的图示。

要取得预先定义图示的句柄,可以将第一个参数设定为NULL来呼叫LoadIcon。在加载程序写作者自订的图标时(图标应该存放在磁盘上的.EXE程序文件中),这个参数应该被设定为程序的执行实体句柄hInstance。第二个参数代表图示。对于预先定义图示,此参数是以IDI开始的标识符(「ID代表图示」),标识符在WINUSER.H中定义。IDI_APPLICATION图标是一个简单的窗口小图形。LoadIcon函数传回该图示的句柄。我们并不关心这个句柄的实际值,它只用于设置hIcon字段元的值。该字段在WNDCLASS结构中定义为HICON型态,此型态名的含义为「handle to an icon(图示句柄)」。

叙述

wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
        
与前一条叙述非常相似。LoadCursor函数加载一个预先定义的鼠标光标(命名为IDC_ARROW),并传回该游标的句柄。该句柄被设定给WNDCLASS结构的hCursor字段。当鼠标光标在依据这个类别建立的窗口的显示区域上出现时,它变成一个小箭头。

下一个字段指定依据这个类别建立的窗口背景颜色。hbrBackground字段名称中的hbr前缀代表「handle to a brush(画刷句柄)」。画刷是个绘图词汇,指用来填充一个区域的着色样式。Windows有几个标准画刷,也称为「备用(stock)」画刷。这里所示的GetStockObject呼叫将传回一个白色画刷的句柄:

wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;
        
这意味着窗口显示区域的背景完全为白色,这是一种极其普遍的做法。

下一个字段指定窗口类别菜单。HElLOWIN没有应用程序菜单,所以该字段被设定为NULL:

wndclass.lpszMenuName = NULL ;
        
最后,必须给出一个类别名称。对于小程序,类别名称可以与程序名相同,即存放在szAppName变量中的「HelloWin」字符串。

wndclass.lpszClassName = szAppName ;
        
至于该字符串由ASCII字符组成或由Unicode字符组成,取决于是否定义了UNICODE标识符。

在初始化该结构的10个字段后,HELLOWIN呼叫RegisterClass来注册这个窗口类别。该函数只有一个参数,即指向WNDCLASS结构的指针。实际上,RegisterClassA函数将获得一个指向WNDCLASSA结构的指针,而RegisterClassW函数将获得一个指向WNDCLASSW结构的指针。程序要使用哪个函数来注册窗口类别,取决于发送给窗口的消息包含ASCII文字还是Unicode文字。

现在有一个问题:如果用定义的UNICODE标识符编译了程序,程序将呼叫RegisterClassW。该程序可以在Microsoft Windows NT中执行良好。但如果此程序在Windows 98上执行,RegisterClassW函数并未真地被执行到。函数有一个进入点,但函数呼叫后只传回0,表明错误。对于在Windows 98下执行的Unicode程序来说,这是一个通知使用者有问题并终止执行的好机会。这是本书中多数程序处理RegisterClass函数呼叫的方法:

if (!RegisterClass (&wndclass))
        
{
        
    MessageBox (  NULL, TEXT ("This program requires Windows NT!"),
        
                 szAppName, MB_ICONERROR) ;
        
    return 0 ;
        
}
        
由于MessageBoxW是可在Windows 98环境下执行的几个Unicode函数之一,所以其执行正常。

当然,这段程序假定RegisterClass不会因为其它原因而呼叫失败,诸如WNDCLASS结构中lpfnWndProc字段被设定成NULL之类的错误。GetLastError函数会帮助您确定在这样的情况下产生错误的原因。GetLastError是Windows中常用的函数,它可以在函数呼叫失败时获得更多错误信息。不同函数的文件将指出您是否能够用GetLastError来获得这些信息。在Windows 98中呼叫RegisterClassW时,GetLastError将传回120。在WINERROR.H中您可以看到,值120与标识符ERROR_CALL_NOT_IMPLEMENTED相等。您也可以在/Platform SDK/Windows Base Services/Debugging and Error Handling/Error Codes/System Errors - Numerical Order查看错误。

一些Windows程序写作者喜欢检查所有可能发生错误的函数呼叫的传回值。这么做确实有点道理,相信您也非常习惯在配置内存后检查错误。而许多Windows函数需要配置内存。例如,RegisterClass需要配置内存,以保存窗口类别的信息。如此一来,您就应该要检查这个函数的执行结果。另一方面说来,如果由于RegisterClass不能得到所需要的内存,它会声明呼叫失败,而Windows大概也快当掉了。

在本书的范例程序中,我做了最少的错误检查。这不是因为我认为错误检查不是一个好方法,而是因为这会让我们在程序举例中分心。

最后,一个老经验是:在一些Windows范例程序中,您可能在WinMain中看到以下程序代码:

if (!hPrevInstance)
        
{
        
    wndclass.cbStyle = CS_HREDRAW | CS_VREDRAW ;
        
           初始化其它 wndclass
        

    RegisterClass (&wndclass) ;
        
}
        
这是出于「旧习难改」的原因。在16位的Windows中,如果您启动正在执行的程序的一个新执行实体,WinMain的hPrevInstance参数将是前一个执行实体的执行实体句柄。为节省内存,两个或多个执行实体就可能会共享相同的窗口类别。这样,窗口类别就只在hPrevInstance是NULL的时候才注册,这表明程序没有其它执行实体。

在32位的Windows中,hPrevInstance总是NULL。此程序代码会正常执行,而实际上也没必要检查hPrevInstance。

建立窗口


窗口类别定义了窗口的一般特征,因此可以使用同一窗口类别建立许多不同的窗口。实际呼叫CreateWindow建立窗口时,可能指定有关窗口的更详细的信息。

Windows程序设计新手有时会混淆窗口类别和窗口之间的区别,以及为什么一个窗口的所有特征不能被一次设定好。实际上,以这种方式分开这些样式信息是非常方便的。例如,所有的按钮窗口都可以依据同样的窗口类别来建立,与这个窗口类别相关的窗口消息处理程序位于Windows内部。由窗口类别来负责处理按钮的键盘和鼠标输入,并定义按钮在屏幕上的外观形象。从这一点看来,所有的按钮都是以同样的方式工作的。但是并非所有的按钮都是一样的。它们可以有不同的大小,不同的屏幕位置,以及不同的字符串。后面的这样一些特征是窗口定义的一部分,而不是窗口类别定义的。

传递给RegisterClass函数的信息会在一个数据结构中设定好,而传递给CreateWindow函数的信息会在函数单独的参数中设定好。下面是HELLOWIN.C中的CreateWindows呼叫,每一个字段都做了完整的说明:

hwnd = CreateWindow (szAppName,    // window class name
        
    TEXT ( "The Hello Program"), // window caption
        
            WS_OVERLAPPEDWINDOW,     // window style
        
           CW_USEDEFAULT,           // initial x position
        
           CW_USEDEFAULT,           // initial y position
        
            CW_USEDEFAULT,            // initial x size
        
          CW_USEDEFAULT,           // initial y size
        
            NULL,                        // parent window handle
        
           NULL,                     // window menu handle
        
   hInstance,                  // program instance handle
        
   NULL) ;                      // creation parameters
        
在这里,我不想提实际上有CreateWindowA函数和CreateWindowW函数,两个函数分别将前两个参数当成ASCII或者Unicode字符串来处理。

标记为「window class name」的参数是szAppName,它含有字符串「HelloWin」-这是程序注册的窗口类别名称。这就是我们建立的窗口联结窗口类别的方式。

此程序建立的窗口是一个普通的重迭式窗口。它含有一个标题列,标题列左边有一个系统菜单按钮,标题列右边有缩小、放大和关闭图示,四周还有一个表示窗口大小的边框。这是标准样式的窗口,名为WS_OVERLAPPEDWINDOW,出现在CreateWindow的「窗口样式」参数中。如果看一下WINUSER.H,您将会发现此样式是几种位旗标的组合:

#define     WS_OVERLAPPEDWINDOW (WS_OVERLAPPED   | \
        
                  WS_CAPTION                               | \
        
                  WS_SYSMENU                               | \
        
                  WS_THICKFRAME                        | \
        
                  WS_MINIMIZEBOX                       | \
        
                  WS_MAXIMIZEBOX)
        
「窗口标题」是显示在标题列中的文字。

注释着「initial x position」和「initial y position」的参数指定了窗口左上角相对于屏幕左上角的初始位置。由于这些参数使用CW_USEDEFAULT标识符,指示Windows使用重迭窗口的内定位置。(CW_USEDEFAULT定义为0x80000000。)内定情况下,Windows依次对新建立的窗口定位,使各窗口左上角的垂直和水平距离在屏幕上按一定的大小递增。与此类似,注释着「initial x size」和「initial y size」的参数分别指定窗口的宽度和高度。同样使用了CW_USEDEFAULT标识符,表明希望Windows使用内定尺寸。

在建立一个「最上层」窗口,如应用程序窗口时,注释为「父窗口句柄」的参数设定为NULL。通常,如果窗口之间存在有父子关系,则子窗口总是出现在父窗口的上面。应用程序窗口出现在桌面窗口的上面,但不必为呼叫CreateWindow而找出桌面窗口的句柄。

因为窗口没有菜单,所以「窗口菜单句柄」也设定为NULL。「程序执行实体句柄」设定为执行实体句柄,它是作为WinMain的参数传递给这个程序的。最后,「建立参数」指标设定为NULL,可以用这个参数存取稍后程序中可能引用到的数据。

CreateWindow传回被建立的窗口的句柄,该句柄存放在变量hwnd中,后者被定义为HWND型态(「窗口句柄型态」)。Windows中的每个窗口都有一个句柄,程序用句柄来使用窗口。许多Windows函数需要使用hwnd作为参数,这样,Windows才能知道函数是针对哪个窗口的。如果一个程序建立了许多窗口,则每个窗口均有一个句柄。窗口句柄是Windows程序所处理最重要的句柄之一。

显示窗口


在CreateWindow呼叫传回之后,Windows内部已经建立了这个窗口。这就是说,Windows已经配置了一块内存,用来保存在CreateWindow呼叫中指定窗口的全部信息跟一些其它信息,而Windows稍后就是依据窗口句柄找到这些信息的。

然而,光是这样子,窗口并不会出现在视讯显示器上。您还需要两个函数呼叫,一个是:

ShowWindow (hwnd, iCmdShow) ;
        
第一个参数是刚刚用CreateWindow建立的窗口句柄。第二个参数是作为参数传给WinMain的iCmdShow。它确定最初如何在屏幕上显示窗口,是一般大小、最小化还是最大化。在开始菜单中安装程序时,使用者可能做出最佳选择。如果窗口按一般大小显示,那么WinMain接收到后传递给ShowWindow的就是SW_SHOWNORMAL﹔如果窗口是最大化显示的,则为SW_SHOWMAXIMIZED。而如果窗口只显示在工作列上,则是SW_SHOWMINNOACTIVE。

ShowWindow函数在显示器上显示窗口。如果ShowWindow的第二个参数是SW_SHOWNORMAL,则窗口的显示区域就会被窗口类别中定义的背景画刷所覆盖。函数呼叫

UpdateWindow (hwnd) ;
        
会重画显示区域。它经由发送给窗口消息处理程序(即HELLOWIN.C中的WndProc函数)一个WM_PAINT消息做到这一点。后面,我们将说明WndProc如何处理这个消息。

消息循环


呼叫UpdateWindow之后,窗口就出现在视讯显示器上。程序现在必须准备读入使用者用键盘和鼠标输入的数据。Windows为当前执行的每个Windows程序维护一个「消息队列」。在发生输入事件之后,Windows将事件转换为一个「消息」并将消息放入程序的消息队列中。

程序通过执行一块称之为「消息循环」的程序代码从消息队列中取出消息:

while       (GetMessage (&msg, NULL, 0, 0))
        
{
        
    TranslateMessage (&msg) ;
        
    DispatchMessage (&msg) ;
        
}
        
msg变量是型态为MSG的结构,型态MSG在WINUSER.H中定义如下:

typedef struct tagMSG
        
{
        
    HWND   hwnd ;
        
    UINT   message ;
        
    WPARAM wParam ;
        
    LPARAM lParam ;
        
    DWORD  time ;
        
    POINT  pt ;
        
}
        
MSG, * PMSG ;
        
POINT数据型态也是一个结构,它在WINDEF.H中定义如下:

typedef struct tagPOINT
        
{
        
    LONG  x ;
        
    LONG  y ;
        
}
        
POINT, * PPOINT;
        
消息循环以GetMessage呼叫开始,它从消息队列中取出一个消息:

GetMessage (&msg, NULL, 0, 0)
        
这一呼叫传给Windows一个指标,指向名为msg的MSG结构。第二、第三和第四个参数设定为NULL或者0,表示程序接收它自己建立的所有窗口的所有消息。Windows用从消息队列中取出的下一个消息来填充消息结构的各个字段,结构的各个字段包括:

hwnd 接收消息的窗口句柄。在HELLOWIN程序中,这一参数与CreateWindow传回的hwnd值相同,因为这是该程序拥有的唯一窗口。
  
message 消息标识符。这是一个数值,用以标识消息。对于每个消息,均有一个对应的标识符,这些标识符定义于Windows表头文件(其中大多数在WINUSER.H中),以前缀WM(「window message」,窗口消息)开头。例如,使用者将鼠标光标放在HELLOWIN显示区域之内,并按下鼠标左按钮,Windows就在消息队列中放入一个消息,该消息的message字段等于WM_LBUTTONDOWN。这是一个常数,其值为0x0201。
  

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -