第三章 WINDOWS 资源

  大多数Windows程序都包含一个定制的图标,Windows将该图标显示在应用程序窗口标题栏的左上角。当程序被列在“开始”菜单中,或者被显示在屏幕底部的任务栏中,或者被列在“Windows资源管理器”中,或者作为快捷方式显示在桌面上时,Windows也显示该程序的图标。有些程序——诸如 Windows Paint一类的重要的图形绘制工具——也使用用户化的鼠标光标来表示程序的不同操作。许多Windows程序还使用菜单和对话框。
  图标、光标、菜单和对话框都是相互关联的,它们都是Windows的资源类型。资源即数据,它们被存储在程序的.EXE文件中,但是它们并非驻留在程序的数据区域中。也就是说,资源不能通过程序源代码中定义的变量直接访问,Windows提供函数直接或间接地把它们加载到内存中以备使用。我们已经遇到了两个这样的函数,即 LoadIcon和 LoadCursor,它们出现在示例程序中定义窗口类结构的赋值语句中。它们从Windows中加载二进制图标和光标图像,并返回该图标或光标的句柄。在本章,我们从创建自己定制的图标开始,这些图标从该程序自己的.EXE文件中加载。
资源包括:
 图标;
 光标;
 字符串;
 定制资源;
 菜单;
 键盘加速键;
 对话框;
 位图。

将图标添加到程序

  使用资源的好处之一在于程序的许多组件能够连编进程序的.EXE文件中。如果没有资源这一概念,例如图标图像的二进制文件可能会驻留在单独的文件中,.EXE会把它读入内存中使用。或者图标不得不在程序中以字节数组的形式定义(这将不会看到实际的图标图像)。作为资源,图标存储在开发者计算机上的单独的可编辑文件中,但在编译过程中被连编进.EXE文件中。
  将资源添加到程序中需要 Visual C++ Developer Studio的一些附加功能。对于图标来说,可以使用“Image Editor”(也称为Graphics Editor”)来绘制图标的图像。该图像被保存在扩展名为.ico的图标文件中。 Developer Studio还生成一个资源描述文件(扩展名为.RC的文件,有时也称作资源定义文件),它列出了程序的所有资源和一个让程序引用资源的头文件(RESOURCE.H)。
  因此,您可以看到这些新文件是如何组织到一起的,让我们以创建名为ICONDEMO的新项目开始。像往常一样,在Deveoper Studio中从File菜单中选择New,依次选择Projects
  选项卡和 Win32 Application。在Project Name域中键人 ICONDEMO,然后单击 OK。 现在.让我们像通常那样创建 C源代码文件。从 File 菜单中选择 New.选择 Files 选项卡,并单击 C++ Source Files在 File Name域中键人 ICONDEMO.C.然后单击OK。此时,Developer Studio就创建了一个空的ICONDEMO.C文件, 键入程序。
/*------------------------------------------
ICONDEMO.C -- Icon Demonstration Program
------------------------------------------*/

#include <windows.h>
#include "resource.h"
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
  TCHAR szAppName[] = TEXT ("IconDemo") ;
  HWND hwnd ;
  MSG msg ;
  WNDCLASS wndclass ;
  wndclass.style = CS_HREDRAW | CS_VREDRAW ;
  wndclass.lpfnWndProc = WndProc ;
  wndclass.cbClsExtra = 0 ;
  wndclass.cbWndExtra = 0 ;
  wndclass.hInstance = hInstance ;
  wndclass.hIcon = LoadIcon (hInstance,
  MAKEINTRESOURCE (IDI_ICON)) ;
  wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
  wndclass.hbrBackground=GetStockObject(WHITE_BRUSH) ;
  wndclass.lpszMenuName = NULL ;
  wndclass.lpszClassName = szAppName ; 
  
   if (!RegisterClass (&wndclass))
  {
    MessageBox (NULL, TEXT ("This 
    program requires Windows NT!"),
    szAppName, MB_ICONERROR) ;
    return 0 ;
  }

  hwnd = CreateWindow (szAppName, TEXT ("Icon Demo"),
  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)
{
  static HICON hIcon ;
  static int cxIcon, cyIcon, cxClient, cyClient ;
  HDC hdc ;
  HINSTANCE hInstance ;
  PAINTSTRUCT ps ;
  int x, y ;
  switch (message)
  {
    case WM_CREATE :
    hInstance = ((LPCREATESTRUCT) lParam)->hInstance ;
    hIcon = LoadIcon (hInstance, MAKEINTRESOURCE (IDI_ICON)) ;
    cxIcon = GetSystemMetrics (SM_CXICON) ;
    cyIcon = GetSystemMetrics (SM_CYICON) ;
    return 0 ;

    case WM_SIZE :
    cxClient = LOWORD (lParam) ;
    cyClient = HIWORD (lParam) ;
    return 0 ;

    case WM_PAINT :
    hdc = BeginPaint (hwnd, &ps) ;
    for (y = 0 ; y < cyClient ; y += cyIcon)
      for (x = 0 ; x < cxClient ; x += cxIcon)
        DrawIcon (hdc, x, y, hIcon) ;

    EndPaint (hwnd, &ps) ;
    return 0 ;

    case WM_DESTROY :
    PostQuitMessage (0) ;
    return 0 ;
  }
  
  return DefWindowProc (hwnd, message, wParam,lParam) ;
}

  如果试图编译该程序,因为在程序开头引用的RESOURCE.H文件并不存在,所以会产生错误。然而,您不必直接创建 RESOURCE.H文件,而是由 Developer Studio为您创建一个。可以通过将资源描述文件添加到项目中来做到这一点。从File菜单中选择New,选择 Files选项卡,单台 Resource Script,在 File Name域中键人 ICONDEMO,单击OK此时, Developer Studio创建两个文本文件: ICONDEMO.RC(资源描述文件)和RESOURCE.H(允许C源代码文件和资源描述文件引用相同的已定义标识符)。不必直接编辑这两个文件,让 Developer Studio来维护它们。如果您想查看资源描述文件和 RESOURCE.H,而不与 Developer Studio产生干扰,可以用记事本程序打开它们。除非对您所做的很有把握,否则不要轻易地更改它们。请记住,只有当您有明确的操作命令或重新编译项目时,Developer Studio才会保存这些文件的新版本。
  资源描述文件是文本文件。它包括这些资源的可用文本形式表达的描述,例如菜单和对话框。资源描述文件也包括对非文本资源的二进制文件的引用,例如图标和自定义的鼠标光标。
  现在,已经存在RESOURCE.H文件,您可以试着重新编译一下ICONDEMO。现在会出现一条报错信息,指出IDI_ICON还没被定义。这个标识符第一次出现在下面的语句中:wndclass.hIcon= LoadIcon(hInstance, MAKEINTRESOURCE(IDI-ICON));在前面的程序中这个语句是由下面的语句代替的:wndclass.hIcon=LoadIcon(NULL,IDI-APPLICATION);之所以改变语句是因为,以前我们为应用程序使用的是标准图标,而这里我们的目的是使用自定义图标。
  创建一个图标,在 Developer Studio的 File View窗口中,您会看到两个文件——ICONDEMO.C和ICONDEMO.RC。单击CONDEMO.C后,就可以编辑源代码。单击ICONDEMO.RC后,就可以把资源添加到文件中或编辑已存在的资源。要添加图标,从Insert菜单上选择Resource。单击要添加的资源,也就是图标,然后单击New按钮。现在呈现的是一个空白的 32像素 x 32像素的图标,可以向其中填充颜色。您会看到带有一组绘图工具和可用颜色的浮动的工具栏。注意颜色工具栏中包括两个与颜色无关的选项,它们有时指“屏幕”和“反屏幕”。当像素用“屏幕”着色时,它实际上是透明的。不管图标在什么表面上显示,图标来着色的部分会显示出底色。这样我们就可以创建非矩形的图标。
  双击围绕图标的区域,会出现Icon Properties对话框,该对话框能够更改图标的ID和文件名。
  现在选择一种颜色(如红色),并在图标上画一个大的B, 此时程序应该能够编译并很好地运行。
Develope Studio通过资源编译器 RC.EXE编译资源。文本资源描述文件被转化为二进制形式,也就是具有扩展名.RES的文件。然后,该已编译的资源文件随同 .obj和.lib文件一起在LINK步骤中被指定。这就是资源被添加到最终的.EXE文件中的方式。当运行ICONDEMO时,程序图标显示在标题栏的左上角和任务栏中。

菜 单

  菜单是 Windows程序提供的一致用户界面的最重要的部分,而在程序中添加菜单是 Windows编程中相对简单的部分。在 Develope Studio中定义菜单。每个选的菜单项被赋予唯一的ID号。在窗口类结构中指定菜单名。当用户选择一个菜项时,Windows给您的程序发送包含该ID号的WM_COMMAND消息。
  窗口的菜单条紧接在标题栏的下方显示,这个菜单条有时被称为“主菜单”或“顶层单”。列在顶层菜单的项通常是下拉式菜单,也叫做“弹出式菜单”或“子菜单”。您也以定义多重嵌套的弹出式菜单,也就是说,在弹出式菜单上的项可以访问另一个弹出菜单。有时弹出式菜单上的项调用对话框以获得更多的信息(对话框在标题栏的最左端,很多父窗口都显示程序的小图标,这个图标激活系统某单,这实际上是另一个弹出式菜单。
  弹出式菜单的各项可以是“被选中的”,这意味着Window在菜单文本的左端显一个小的复选标记,复选标记让用户知道从菜单中选中了哪些选项。这些选项之间以是互斥的,也可以是不互斥的。顶层菜单项不能被选中。
  顶层菜单或弹出式菜单项可以被“启用”、“禁用”或“灰化”。“激活”和“不激活”时候被用作“启用”和“禁用”的同义词。标以启用或禁用的菜单项在用户看来是一样的,但是灰化的菜单项是使用灰色文本来显示的。从用户的角度看,启用、禁用和灰化的菜单项都是可以“选择的”加亮的“,也就是说,用户可以使用鼠标选择被禁用的菜单项,或者将反显光标条移动到禁用的菜单上,或者使用菜单项的关键字母来选择该菜单项。然而,从程序员的角度来看,启用、禁用和灰化菜单项的功能是不同的。Window只为启用的菜单项向程序发送WM_ COMMAND消息。对当前不合法的选项,可以禁用和灰化菜单项。如果想使用户知道选择是不合法的,那么您可以灰化它。
  当您创建或更改程序中的菜单时,把顶层菜单和每一个弹出式菜单想象成各自独立的菜单是有用的。顶层菜单有一个菜单句柄,在顶层菜单中的每一个弹出式菜单也都自己的菜单句柄。系统菜单(也是一个弹出式菜单)也有菜单句柄。
  菜单中的每一项都有三个特性。第一个特性是菜单中显示什么,它可以是字串或位图。第二个特性是WM_COMMAND消息中Windows发送给程序的菜单ID,或者是在用户选择菜单项时Windows显示的弹出式菜单的句柄。第三个特性是菜单项的属性,包括是否被禁用、灰化或被选中。
  当您创建或更改程序中的菜单时,把顶层菜单和每一个弹出式菜单想象成各自独立的菜单是有用的。顶层菜单有一个菜单句柄,在顶层菜单中的每一个弹出式菜单也都自己的菜单句柄。系统菜单(也是一个弹出式菜单)也有菜单句柄。
  如果在 Menu Items Properties对话框中复选Grayed选项,则菜单项是不能激活的,它的文本是灰色的,该项不产生WM_COMMAND消息。如果选中Inactive选项,则菜单项也是不能激活的,在弹出式菜单的项上,可以在字符串中使用制表符\t。紧接着\t的文本被放置它起的作用。字符串中的\a便跟着它的文本右对齐。指定的ID值是Windows发送给窗口过程中菜单消息中的数值。在菜单中ID值应该是唯一的。按照惯例,我使用以 IDM(“ID for a Menu”)开头的标识符。
二、在程序中引用菜单
  大多数Windows应用程序在资源描述文件中只有一个菜单。您可以给菜单起一个与程序名相同文字的名称。程序员经常将程序名用于菜单名,以便相同的字符串可以用于窗口类、程序的图标名和菜单名。然后,程序在窗口的定义中为菜单引用该名称:wndclass.lpszMenuName=szAppName;
  虽然访问菜单资源的常用的方法是在窗口类中指定菜单,也可以使用其他方法,Windows 应用程序可以使用LoadMenu函数将菜单资源加载到内存中,如同LoadIcon 和LoadCursor 函数一样。LoadMenu返回一个菜单句柄。如果您在资源描述文件中为菜单使用了名称,语句如下:
  hMenu=LoadMenu(hInstance,TEXT(“MyMenu”));
如果使用了数值,那么LoadMenu调用采用如下形式:  hMenu=LoadMenu(hInstance,MAKEINTRESOURCE(ID_MENU));
然后,您可以将这个菜单句柄作为CreateWindow的第9个参数:
  在这种情况下,CreateWindow调用中指定的菜单可以覆盖窗口类中指定的任何菜单。如果CreateWindow的第9个参数是NULL,那么您可以把窗口类中的菜单看作是基于这个窗口类的窗口默认菜单。这样,您可以为基于同一窗口类的几个窗口使用不同的菜单。
  您也可以在窗口类中指定NULL菜单,并且在CreateWindow调用中也指定NULL菜单,然后在窗口被创建后再给窗口指定一个菜单:
  SetMenu(hwnd,hMenu);
  这种形式使您可以动态地修改窗口的菜单。当窗口被清除时,同窗口相关的所有菜单都将被清除。同窗口不相关的菜单在程序结束前通过调用 DestroyMenu被显式地清除。
三、菜单和消息
  当用户选择一个菜单项时,Windows通常向窗口过程发送几个不同的消息。在大多数情况下,您的程序可以忽略大部分消息,只需把它们传递给DestroyWindowProc即可。WM_INITMENU就是这一类的消息,它具有下列参数:
  wParam:主菜单句柄
  lParam: 0
  wParam 值是主菜单句柄,即使用户选择的是系统菜单中的项。Windows程序通常忽略 WM_INITMENU消息。尽管在选中该项之前的消息已经给程序提供了修改菜单的机会,但是我们觉得此刻改变顶层菜单对用户来说是很为难的。
  程序也接收WM_MENUSELECT消息。随着用户在菜单项中移动光标或者鼠标,程序会收到许多WM_MENUSELECT消息。这对实现那些包含对菜单项的文本描述的状态栏是很有帮助的。WM_MENUSELECT的参数如下所示: 
  LOWORD(wParam):选中项:菜单ID或者弹出式菜单句柄 HIWORD (wParam):选择标志
  lParam:包含选中项的菜单句柄
  最重要的菜单消息是WM_COMMAND,它表明用户已经从菜单中选中了一个被启用的菜单项。在子窗口控制中的WM_COMMAND消息也可以由于窗口控制产生。如果为菜单和子窗口控制使用同一ID码,那么您可以通过lParam的值来区别它们,菜单项的lParam值为0,见表

菜单
控制
LOWORD(wParam)
菜单ID
控制ID
HIWORD(wParam)
0
通知码
IParam
0
子窗口句柄

四、示例程序
  让我们来看一个简单的例子。程序所示的MENUDEMO程序在主菜单中有5项—File、Edit、Background、Timer和Help,每一项有 一个弹出式菜单。MENUDEMO只完成了最简单、最常用的菜单处理操作,包括捕获WM_COMMAND消息和检查wParam的低位字。

/*-----------------------------------------
MENUDEMO.C
-----------------------------------------*/

#include <windows.h>
#include "resource.h"

#define ID_TIMER 1
LRESULT CALLBACK WndProc (HWND,UINT, WPARAM, LPARAM);
TCHAR szAppName[] = TEXT ("MenuDemo") ;
int WINAPI WinMain (HINSTANCE hInstance,HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow)
{
  HWND hwnd ;
  MSG msg ;
  WNDCLASS wndclass ;

  wndclass.style = CS_HREDRAW | CS_VREDRAW ;
  wndclass.lpfnWndProc = WndProc ;
  wndclass.cbClsExtra = 0 ;
  wndclass.cbWndExtra = 0 ;
  wndclass.hInstance = hInstance ;
  wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
  wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
  wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
  wndclass.lpszMenuName = szAppName ;
  wndclass.lpszClassName = szAppName ;
  if (!RegisterClass (&wndclass))
  {
    MessageBox (NULL, TEXT("This program requires Windows NT!"),szAppName, MB_ICONERROR) ;
    return 0 ;
  }

  hwnd = CreateWindow (szAppName, 
             TEXT ("Menu Demonstration"),
             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)
{
  static int idColor [5] = { WHITE_BRUSH, LTGRAY_BRUSH,GRAY_BRUSH,DKGRAY_BRUSH,BLACK_BRUSH };
  static int iSelection = IDM_BKGND_WHITE ;
  HMENU hMenu ;

  switch (message)
  {
    case WM_COMMAND:
    hMenu = GetMenu (hwnd) ;

    switch (LOWORD (wParam))
    {
      case IDM_FILE_NEW:
      case IDM_FILE_OPEN:
      case IDM_FILE_SAVE:
      case IDM_FILE_SAVE_AS:
      MessageBeep (0) ;
      return 0 ;

      case IDM_APP_EXIT:
      SendMessage (hwnd, WM_CLOSE, 0, 0) ;
      return 0 ;
      case IDM_EDIT_UNDO:
      case IDM_EDIT_CUT:
      case IDM_EDIT_COPY:
      case IDM_EDIT_PASTE:
      case IDM_EDIT_CLEAR:
      MessageBeep (0) ;
      return 0 ;

      case IDM_BKGND_WHITE: // Note: Logic below
      case IDM_BKGND_LTGRAY: // assumes that IDM_WHITE
      case IDM_BKGND_GRAY: // through IDM_BLACK are
      case IDM_BKGND_DKGRAY: // consecutive numbers in
      case IDM_BKGND_BLACK: // the order shown here.
      CheckMenuItem (hMenu, iSelection, MF_UNCHECKED) ;
      iSelection = LOWORD (wParam) ;
      CheckMenuItem (hMenu, iSelection, MF_CHECKED) ;

      SetClassLong(hwnd,
             GCL_HBRBACKGROUND,
            (LONG) GetStockObject (idColor [LOWORD (wParam) - IDM_BKGND_WHITE]));

      InvalidateRect (hwnd, NULL, TRUE) ;
      return 0 ;

       case IDM_TIMER_START:
       if (SetTimer (hwnd, ID_TIMER,1000, NULL))
       {
         EnableMenuItem (hMenu, IDM_TIMER_START,MF_GRAYED) ;
         EnableMenuItem (hMenu, IDM_TIMER_STOP,MF_ENABLED) ;
       }
       return 0 ;

       case IDM_TIMER_STOP:
       KillTimer (hwnd, ID_TIMER) ;
       EnableMenuItem (hMenu, IDM_TIMER_START,MF_ENABLED) ;
       EnableMenuItem (hMenu, IDM_TIMER_STOP,MF_GRAYED) ;
       return 0 ;

        case IDM_APP_HELP:
        MessageBox (hwnd,
              TEXT("Help not yet implemented!"),
              szAppName,
              MB_ICONEXCLAMATION | MB_OK);
        return 0 ;

       case IDM_APP_ABOUT:
       MessageBox (hwnd,
              TEXT ("Menu Demonstration Program\n") TEXT ("(c) Charles Petzold,
              1998"),
              szAppName,
              MB_ICONINFORMATION | MB_OK) ;
       return 0 ;
     }
     break ;

     case WM_TIMER:
     MessageBeep (0) ;
     return 0 ;

     case WM_DESTROY:
     PostQuitMessage (0) ;
     return 0 ;
   }

    return DefWindowProc (hwnd, message, wParam, lParam) ;
}

  当收到弹出式菜单File和Edit各项有关的WM_COMMAND消息时,MENUDEMO程序只使系统发出蜂鸣声。Background弹出式菜单列出MENUDEMO用来给背景着色的5种现有画刷。Background弹出式菜单上的5种画剧相互排斥。当MENUDEMO.C收到一个WM_COMMAND消息,而该消息中wParam是Background弹出式菜单中的5项之一时,它必从先前选中的背景颜色中清除复选标记,并把标记添加到新的背景颜色上。为此,首要得到菜单句柄:
hMenu=GetMenu (hwnd);
CheckMenuItem函数用来取消当前选中的项:
  CheckMenuItem(hMenu,iSelection,MF_UNCHECKED);iSelection的值被设置为wParam的值,新的背景颜色被选中 ,窗口类中的背景颜色于是被替换为新的背景颜色,窗口客户区变为无效状态,Windows使用新的背景颜色清除窗口。
  在MENUDEMO中的File和Edit弹出式菜单的格式与其他Windows程序中的格式非常类似。Windows的目的之一是为用户提供一种可识别的界面,而不要求用户为每个程序重新学习基本概念。如果File和Edit菜单在每个Windows程序中看起来都一样,并且都使用同样的字母和Alt键来进行选择,那么当然不会需要什么帮助了。
  除了File和Edit弹出式菜单外,大多数Windows程序的菜单都是不同的。当设计一个菜单时,您应该看一看现有的Windows程序以尽量保持一致。同时记住,修改一个菜单通常只需要修改资源描述文件,而不必修改程序代码。即使以后要改变菜单项的位置,也不会有多大的问题。
五、建立菜单函数
  在程序的资源描述文件中定义菜单通常是在窗口中添加菜单的最简单方法,但不是唯一的方法。如果您没有使用资源描述文件,那么可以使用CreateMenu和AppendMenu两个函数在程序中创建菜单。在定义完菜单后,您可以将菜单句柄发送给CreateWindow,或者使用SetMenu来设置窗口的菜单。以下是具体的做法。CreateMenu简单地把一个句柄返回给新菜单:
  hMenu = CreateMenu();菜单一开始为空。AppendMenu将菜单项插入菜单中。您必须为顶层菜单项和每一个弹出式菜单提供不同的莱单句柄。弹出式菜单是单独构成的,然后将弹出式菜单句柄插入顶层菜单。
下面的程序中所示的代码就是用这种方法创建菜单的。

#include <windows.h>

#define IDM_NEW 101
#define IDM_OPEN 102 
#define IDM_SAVE 103
#define IDM_SAVEAS 104 
#define IDM_PRINT 105
#define IDM_EXIT 106 
#define IDM_CUT 201
#define IDM_COPY 202
#define IDM_PASTE 203
#define IDM_DELETE 204

Int WINAPI

WinMain(HINSTANCE,HINSTANCE,LPSTR,int);
LRESULT CALLBACK
WndProc(HWND,UINT,WPARAM,LPARAM);
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)
{
  WNDCLASSEX wcex;
  wcex.cbSize = sizeof(WNDCLASSEX);
  wcex.style = CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS;
  wcex.lpfnWndProc = (WNDPROC)WndProc;
  wcex.cbClsExtra = 0;
  wcex.cbWndExtra = 0;
  wcex.hInstance = hInstance;
  wcex.hIcon = LoadIcon(NULL,(LPCTSTR)IDI_APPLICATION);
  wcex.hCursor = LoadCursor(NULL,IDC_ARROW);
  wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
  wcex.lpszMenuName = NULL;
  wcex.lpszClassName = "KeyMessageCls";
  wcex.hIconSm = LoadIcon(NULL,(LPCTSTR)IDI_APPLICATION); 

  if(!RegisterClassEx(&wcex)) return FALSE;

  HMENU hMenu = CreateMenu();
  HMENU hPopup = CreatePopupMenu();
  AppendMenu(hPopup,MF_STRING,IDM_NEW,"&New");
  AppendMenu(hPopup,MF_STRING,IDM_OPEN,"&Open");
  AppendMenu(hPopup,MF_STRING,IDM_SAVE,"&Save");
  AppendMenu(hPopup,MF_STRING,IDM_SAVEAS,"Save &As...");
  AppendMenu(hPopup,MF_STRING,IDM_PRINT,"&Print");
  AppendMenu(hPopup,MF_SEPARATOR,0,NULL);
  AppendMenu(hPopup,MF_STRING,IDM_EXIT,"E&xit");
  AppendMenu(hMenu,MF_POPUP,(int)hPopup,"&File");
  hPopup = CreatePopupMenu();
  AppendMenu(hPopup,MF_STRING,IDM_CUT,"Cu&t");
  AppendMenu(hPopup,MF_STRING,IDM_COPY,"&Copy");
  AppendMenu(hPopup,MF_STRING,IDM_PASTE,"&Paste");
  AppendMenu(hPopup,MF_STRING,IDM_DELETE,"&Delete");
  AppendMenu(hMenu,MF_POPUP,(int)hPopup,"&Edit");
  HWND hWnd = CreateWindowEx(NULL,
                "KeyMessageCls",
                "Key Message Control",
               WS_OVERLAPPEDWINDOW,
               100,
               100,
               500,
               350,
               NULL,
               hMenu,
               hInstance,
               NULL);

  if(!hWnd) return FALSE;

  ShowWindow(hWnd, nCmdShow);
  UpdateWindow(hWnd);
  MSG msg;

  while(GetMessage(&msg, NULL, 0, 0))
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }

  return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{
  switch(message)
  {
    case WM_COMMAND:
    if(wParam == IDM_EXIT)
      PostQuitMessage(0);
    break;

    case WM_DESTROY:
    PostQuitMessage(0);
    break;

    default:
    return DefWindowProc(hWnd,message,wParam,lParam);
  }
  
  return 0;
}

  使用资源描述文件菜单模板更容易、更清楚一些。不鼓励使用这里的方法定义菜单,而只是提供了一种实现某单的方法。当然,您可以使用包含所有菜单项字符串、ID和标志等的结构数组来压缩代码大小。

键盘加速健

  加速键是产生WM_COMMAND消息(有些情况下是WM_SYSCOMMAND)的键组合。许多时候,程序使用加速键来重复常用菜单项的动作(然而,加速键还可以用于执行非菜单功能)。例如,许多Windows程序在Edit菜单中都包含Delete或Clear选项,这些程序习惯上都将Del键指定为该选项的加速键。用户可以通过Alt键从菜单中选择Delete 选项,或者只需按下加速键Del。当窗口过程收到一个WM_COMMAND消息时,它不必确定使用的是菜单还是加速键。
一、为什么要使用加速健

  为什么不能直接捕获WM_KEYDOWN或WM_CHAR消息而自己来实现同样的菜单功能呢?好处在哪里?对于一个单窗口应用程序,可以捕获键盘消息,但是使用加速键可以得到一些好处:您不需要复制菜单和加速键逻辑。
  对于有多个窗口和多个窗口过程的应用程序来说,加速键是非常重要的。Windows将键盘消息发送给当前活动窗口的窗口过程。然而对于加速键,Windows把WM_COMMOND消息发送给窗口过程,该窗口过程的句柄在Windows 函数TranslateAccelerator中给出。通常这是主窗口,也是拥有菜单的窗口,这意味着无需每个窗口过程都复制加速键的操作逻辑。
  如果在主窗口的客户区中使用了非模态对话框或者子窗口,那么这种好处就变得非常重要。如果定义一个特定的加速键,以便在不同的窗口之间移动,那么只需要一个窗口过程有这个逻辑,子窗口不会收到加速键引发的WM_COMMAND消息。
二、安排加速健的几条规则
  从理论上讲,您可以使用任何虚拟键或者字符键同Shift键、Ctrl键或Alt键一起来定义加速键。然而,您应该尽力使得应用程序之间协调一致,并且尽量避免干扰Windows的键盘使用。在加速键中,应该避免使用 Tab、Enter、Esc和 Spacebar键,因为这些键常常用于完成系统功能。
  加速健最常用的功能是操作程序Edit菜单中的各项。为这些菜单项推荐的加速键, 因此通常都要支持如下表所列的新旧两套加速键。

功能旧加速键新加速键
UndoAlt+BackspaceCtrl+Z
CutShift+DelCtrl+X
CopyCtrl+InsCtrl+C
PasteShift+InsCtrl+V
Delete或ClearDelDel

三、加速健表
  可以在DeveloperStudio中定义加速键表。为了在程序中加载加速键表更容易,给它与程序名相同的名称(与菜单和图标名也相同)。
  每个加速键都有在 Accel Properties对话框中定义的 ID和按键组合。如果您已经定义了菜单,则菜单 ID会出现在组合框中,因此不用键入它们。
  加速键可以是虚拟键代码,也可以是SCII字符与Shift、Ctrl或Aft键的组合。您可以通过在字母前键入”^”,来指定带有Ctrl键的ASCII字符,也可以从组合框中选取虚拟键。
  为菜单项定义加速键时,应该将键的组合包含到菜单项的文本中。制表符(\t)将文本与加速键分割开,将加速键列在第二列。为了在菜单中给加速键做标记,可以使用文本 Ctrl、Shift或Alt之后跟上一个十号和一个键名(例如, Shift+或 Ctrl+F6)。
四、加载加速健表
  在程序中,使用LoadAccelerators函数来把加速键表加载到内存中,并获得该表的句柄。LoadAccelerators语句非常类似于LoadIcon、LoadCursor、 LoadMenu语句。
首先,把加速键表的句柄定义为类型HANDLE:
HANDLE hAccel;
然后加载加速键表:
  hAccel= LoadAccelerators (hInstance,TEXT(“MyAccelerators”));正如图标、光标和菜单一样,您可以使用一个数值代替加速键表的名称,然后在LoadAccelerators语句中和MAKEINTRESOURCE宏一起使用该数值,或者把它放在双引号内,前面冠以字符“#”。
五、键盘代码转换
  现在我们将讨论这样的三行代码,到目前为止创建的所有Windows程序中都使用过它们。这些代码是标准的消息循环:

while (GetMessage (&msg, NULL, 0, 0))
{
  TranslateMessage (&msg) ;
  DispatchMessage (&msg) ;
}

下面修改选段代码,以便使用加速键:

while (GetMessage (&msg, NULL, 0, 0))
{
  if(!TranslateAccelerator (hwnd,hAccel,&msg))
  {
    TranslateMessage (&msg) ;
    DispatchMessage (&msg) ;
  }
}
  TranslateAccelerator函数确定存放在msg消息结构中的消息是不是键盘消息。如果是,该函数将查找句柄为hAccel的加速键表。如果找到了一个匹配,则调用句桶为hwnd的窗口的窗口过程。如果加速键ID与系统菜单的菜单项一致,则消息就是WM_SYSCOMMAND;否则,消息为WM_COMMAND。
  当 TranslateAccelerator返回时,如果消息已经被转换(并且已经被发送给窗口过程),那么返回值为非0;否则,返回值为0;如果 TranslateAccelerator返回一个非0值,则不调用TranslateMessage和DispatchMessage,而是经过循环回到GetMessage调用中。
  TranslateMessage中的参数hwnd看起来有点累赘,因为消息循环中的其他三个函数都没有要求这个参数。此外,消息结构本身(结构变量msg)有一个叫做hwnd的成员,它是窗口句柄。
  这是该函数不同的原因:msg结构的域由GetMessage调用填充。当GetMessage的第二个参数为NULL时,函数检索应用程序所有窗口的消息。当GetMessage返回时,msg 结构的 hwnd是将要获得消息的窗口的窗口句柄。
然而,当 TranslateMessage把键盘消息转换为WM_COMMAND或WM_SYSCOMMAND消息时,
  它使用函数的第一个参数指定的窗口句柄hwnd来代替窗口句柄msg.hwnd。Windows就是这样把所有加速键消息发送给同一窗口过程的,即使另一个应用窗口当前拥有输入焦点。当模态对话框或者消息框拥有输入焦点时,TranslateMessage不转换键盘消息,因为这些窗口的消息是不经过程序的消息循环的。
  在某些情况下,当您的程序的另一个窗口(比如一个非模态对话框)拥有输入焦点时,您也许不想转换加速键。您将在后面看到如何处理这种情况。
六、接收加速键消息
  当加速键与系统菜单中的菜单项相对应时,TranslateAccelerator给窗口过程发送一个WM_SYSCOMMAND消息,否则,TranslateAccelerator给窗口过程发送一个 WM_COMMAND消息。下表所示了几种可能接收到的WM_COMMAND消息,这些消息用于加速键、菜单命令,以及子窗口控制。

加速键
菜单
控制
LOWORD(wParam)
加速键ID
菜单ID
控制ID
HIWORD(wParam)
1
0
通知码
IParam
0
0
子窗口句柄

如果加速键与一个菜单项对应,那么窗口过程还会收到WM_INITMENU、WM_INITMENUPOPUP和 WM_MENUSELECT消息,就好像选中了菜单选项一样。在处理 WM_INITMENUPOPUP时,程序往往启用和禁用弹出式菜单中的菜单项,因此,在使用加速键时,您仍然能够实现这类功能。如果加速健与一个禁用或者灰化的菜单项相对应,那么,TranslateAccelerator函数就不会向窗口过程发送WM_COMMAAND或WM_SYSCOMMAND消息。
  如果活动窗口已经被最小化,那么TranslateAccelerator将为与启用的系统菜单项相对应的加速键向窗口过程发送WM_SYSCOMMAND消息,而不是WM_COMMAND消息。 
  TranslateAccelerator也会为没有任何菜单项与之对应的加速键发送该窗口过程的WM_COMMAND消息。
七、莱单与加速键应用程序

#include <windows.h>
#include "resource.h"

#define ID_EDIT 1

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

TCHAR szAppName[] = TEXT ("Pop") ;
int WINAPI WinMain (HINSTANCE hInstance, 
HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
  HACCEL hAccel ;
  HWND hwnd ;
  MSG msg ;
  WNDCLASS wndclass ;

  wndclass.style = CS_HREDRAW | CS_VREDRAW ;
  wndclass.lpfnWndProc = WndProc ;
  wndclass.cbClsExtra = 0 ;
  wndclass.cbWndExtra = 0 ;
  wndclass.hInstance = hInstance ;
  wndclass.hIcon = LoadIcon (hInstance, szAppName) ;
  wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
  wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
  wndclass.lpszMenuName = szAppName ;
  wndclass.lpszClassName = szAppName ;

  if (!RegisterClass (&wndclass))
  {
    MessageBox (NULL, TEXT ("This program requires Windows NT!"),
    szAppName, MB_ICONERROR) ;
    return 0 ;
  }
  hwnd = CreateWindow (szAppName,
             szAppName,
             WS_OVERLAPPEDWINDOW,
             GetSystemMetrics (SM_CXSCREEN) / 4,
             GetSystemMetrics (SM_CYSCREEN) / 4,
             GetSystemMetrics (SM_CXSCREEN) / 2,
             GetSystemMetrics (SM_CYSCREEN) / 2,
             NULL,
             NULL,
             hInstance,
             NULL) ;

  ShowWindow (hwnd, iCmdShow) ;
  UpdateWindow (hwnd) ;
  hAccel = LoadAccelerators (hInstance, szAppName) ;

  while (GetMessage (&msg, NULL, 0, 0))
  {
    if (!TranslateAccelerator (hwnd, hAccel, &msg))
    {
      TranslateMessage (&msg) ;
      DispatchMessage (&msg) ;
    }
  }
  return msg.wParam ;
}

AskConfirmation (HWND hwnd)
{
  return MessageBox (hwnd,
            TEXT ("Really want to close Pop?"),
            szAppName,
            MB_YESNO | MB_ICONQUESTION) ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  static HWND hwndEdit ;
  int iSelect, iEnable ;
  switch (message)
  {
    case WM_CREATE:
    hwndEdit = CreateWindow (TEXT ("edit"), NULL,
    WS_CHILD | WS_VISIBLE | WS_HSCROLL |
    WS_VSCROLL | WS_BORDER | ES_LEFT | 
    ES_MULTILINE | ES_AUTOHSCROLL | ES_AUTOVSCROLL,
     0, 0, 0, 0,
     hwnd,
    (HMENU) ID_EDIT,
    ((LPCREATESTRUCT) lParam)->hInstance,
     NULL) ;
    return 0 ;

    case WM_SETFOCUS:
    SetFocus (hwndEdit) ;
    return 0 ;

    case WM_SIZE: 
    MoveWindow (hwndEdit, 0, 0, LOWORD (lParam),HIWORD (lParam), TRUE) ;
    return 0 ;

    case WM_INITMENUPOPUP:
    if (lParam == 1)
    {
      EnableMenuItem ((HMENU) wParam, IDM_EDIT_UNDO,
      SendMessage (hwndEdit, EM_CANUNDO, 0, 0) ?
      MF_ENABLED : MF_GRAYED) ;

      EnableMenuItem ((HMENU) wParam, IDM_EDIT_PASTE,
      IsClipboardFormatAvailable (CF_TEXT) ?
      MF_ENABLED : MF_GRAYED) ;

      iSelect = SendMessage (hwndEdit, EM_GETSEL, 0, 0) ;

      if (HIWORD (iSelect) == LOWORD (iSelect))
        iEnable = MF_GRAYED ;
      else
        iEnable = MF_ENABLED ;

      EnableMenuItem ((HMENU) wParam,
      IDM_EDIT_CUT, iEnable) ;
      EnableMenuItem ((HMENU) wParam,
      IDM_EDIT_COPY, iEnable) ;
      EnableMenuItem ((HMENU) wParam,
      IDM_EDIT_CLEAR, iEnable) ;
      return 0 ;
    }
    break ;

    case WM_COMMAND:
    if (lParam)
    {
      if (LOWORD (lParam) == ID_EDIT &&
        (HIWORD (wParam) == EN_ERRSPACE ||
        HIWORD (wParam) == EN_MAXTEXT))
        MessageBox (hwnd,
              TEXT ("Edit control out of space."),
              szAppName,
              MB_OK | MB_ICONSTOP) ;
      return 0 ;
    }
    else switch (LOWORD (wParam))
    {
      case IDM_FILE_NEW:
      case IDM_FILE_OPEN:
      case IDM_FILE_SAVE:
      case IDM_FILE_SAVE_AS:
      case IDM_FILE_PRINT:
      MessageBeep (0) ;
      return 0 ;

      case IDM_APP_EXIT:
      SendMessage (hwnd, WM_CLOSE, 0, 0) ;
      return 0 ;

      case IDM_EDIT_UNDO:
      SendMessage (hwndEdit, WM_UNDO, 0, 0) ;
      return 0 ;

      case IDM_EDIT_CUT:
      SendMessage (hwndEdit, WM_CUT, 0, 0) ;
      return 0 ;

      case IDM_EDIT_COPY:
      SendMessage (hwndEdit, WM_COPY, 0, 0) ;
      return 0 ;

      case IDM_EDIT_PASTE:
      SendMessage (hwndEdit, WM_PASTE, 0, 0) ;
      return 0 ;

      case IDM_EDIT_CLEAR:
      SendMessage (hwndEdit, WM_CLEAR, 0, 0) ;
      return 0 ;

      case IDM_EDIT_SELECT_ALL:
      SendMessage (hwndEdit, EM_SETSEL, 0, -1) ;
      return 0 ;

      case IDM_HELP_HELP:
      MessageBox(hwnd,TEXT("Help not yet implemented!"),szAppName,             MB_OK|MB_ICONEXCLAMATION) ;
      return 0 ;

      case IDM_APP_ABOUT:
      MessageBox (hwnd, TEXT (" Charles Petzold, 1998"),
      szAppName, MB_OK | MB_ICONINFORMATION) ;
      return 0 ;
    }
    break ;

    case WM_CLOSE:
    if (IDYES == AskConfirmation (hwnd))
    DestroyWindow (hwnd) ;
    return 0 ;

    case WM_QUERYENDSESSION:
    if (IDYES == AskConfirmation (hwnd))
    return 1 ;
    else
    return 0 ;

    case WM_DESTROY:
    PostQuitMessage (0) ;
    return 0 ;
  }
  return DefWindowProc (hwnd, message, wParam, lParam) ;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值