9.6 列表框类

摘录于《Windows程序(第5版,珍藏版).CHarles.Petzold 著》P320

        本章讨论的最后一个预定义子窗口控件是列表框。列表框是将一批文本字符串显示在一个具有滚动功能的方框中的控件。通过发送消息到列表框的窗口过程,程序可以添加或删除列表中的字符创。当列表框中的一个项目被选中时,列表框控件便发送 WM_COMMAND 消息到其父窗口。然后父窗口确定哪个项目被选中。

        列表框可以是单选的或多选的。后者允许用户从列表框中选择一个以上的项目。当一个列表框获得输入焦点时,它会再列表框的一个项目周围显示虚线框。这种光标并不表示列表框中的选中项。被选中的项目通过高亮显示来表示。

        在单选列表框中,用户可以按动空格键选择光标所在的项目。方向键可以同时移动光标和当前的选择,并可以滚动列表框的内容。上下翻页键也可通过移动光标来滚动列表框,但不会移动选的项目。按下一个字母键可移动光标和当前选择到以那个字母开头的第一项(或下一项)。通过在某一项上单击或双击鼠标也可以选中该项。

        在多选列表框中,空格键用于切换光标所在项目的选择状态。(如果该项目已经被选定,它的选定状态会被取消。)方向键取消所有以前选定的项目,并移动光标和选中项,就像在单选列表框中一样。然而,Ctrl 键加方向键可以移动光标,但不移动选中项。Shift 键加方向键可以扩展选中项。

        单击或双击多选列表框中的一个项目,会取消所有先前选定的项目,只选择被单击的项目。然而,在单击一个项目的同时按下 Shift 键会切换该项目的选择状态而不该任何其他项目的选择状态。

9.6.1 列表框的样式

        在调用 CreateWindow 函数时,指定“列表框”窗口类和 WS_CHILD 样式,就会创建一个列表框子窗口控件。然而,这个默认的列表框样式不能发送 WM_COMMAND 消息到父窗口。也就是说,程序需要(通过发送到列表框控件的消息)查问列表框有关选择的的项目。因此,列表框控件几乎总是包括列表框样式标识符 LBS_NOTIFY,以使父窗口收到从列表框发来的 WM_COMMAND 信息。如果想让列表框对项目进行排序,也可以使用另一种较常见的样式 LBS_SORT。

        默认情况下,列表框是单选的。多选列表框相对罕见。如果想创建一个的话,可以使用 LBS_MULTIPLESEL 样式。通常,当一个新项目添加到列表滚动框时,列表框会自我更新。你可以指定 LBS_NOREDRAW 样式,防止这种情况出现。不过,你可能不应该使用这一样式。相反,可以暂时阻止列表框控件的自我更新,为此可使用 WM_SETREDRAW 消息,我会在后面描述。

        默认情况下,列表框窗口过程只会显示项目列表而不会显示任何边框。可以使用窗口样式标识符 WS_BORDER 添加一个边框。而如果想添加一个可以用鼠标垂直滚动的滚动条,可使用窗口样式标识符 WS_VSCROLL。

        在 Windows 头文件中定义了一个叫 LBS_STANDARD 的列表框样式,包含最常用的样式。它的定义如下:

{ LBS_NOTIFY | LBS_SORT | WS_VSCROLL | WS_BORDER }  
还可以使用 WS_SIZEBOX 和 WS_CAPTION 标识符,但这些将允许用户调整列表框的尺寸,以及在父窗口的客户区移动它。

        列表框的宽度应该适应最长字符串的长度再加上滚动条的宽度。可以使用以下语句获得垂直滚动条的宽度:

GetSystemMetrics (SM_CXVSCROLL); 

通过将一个字符的高度乘以要出现在视图中项目的数量,变可以计算出列表框的高度。

9.6.2  向列表框中添加字符串

        在创建列表框后,下一步就是向其中添加文本字符串。可以使用 SendMessage 向列表框的窗口过程发送消息来实现。文本字符串通常可以通过数字索引来引用,索引 0 为最上面的项目。在后面的例子中,hwndList 是子窗口列表框控件的句柄,iIndex 是索引的值。在为 SendMessage 传入一个文本字符串时,lParam 参数是指向一个以空字符结尾的字符串的指针。

        在大多数这些例子中,如果窗口过程为存储列表框的内容而用完了可用的存储空间,那么 SendMessage 调用可能返回 LB_ERRSPACE 值(定义为 -2)。如果发生其他原因的错误,SendMessage 返回 LB_ERR(-1),如果操作成功,返回 LB_OKAY(0)。可以测试 SendMessage 是否为非零值来找出这两种错误。

        如果使用了 LBS_SORT 样式(或者如果将字符串以你所希望的顺序放入列表框),那么添加字符串最简单的方法便是使用 LB_ADDSTRING 消息:

SendMessage (hwndList, LB_ADDSTRING, 0, (LPARAM) szString);  

如果未使用 LBS_SORT,则可以用 LB_INSERTSTRING 消息,通过指定索引值,将字符串插入到列表框中:

SendMessage (hwndList, LB_INSERTSTRING, iIndex, (LPARAM) szString);  

例如,如果 iIndex 等于 4,那么 szString 会成为索引值为 4 的新字符串——从顶部开始算起的第五个字符串,这是因为计数从 0 开始。任何在这个位置之下的字符串会被向下移动。如果 iIndex 值为 -1,字符串会被添加到最底部。你可以对 LBS_SORT 样式的列表框使用 LB_INSERTSTRING,但列表框的内容不会重新排序。(你也可以使用 LB_DIR 消息插入字符串到一个列表框,有关这个主题,我会再本章快结束的时候详细讨论。)

        可以使用 LB_DELETESTRING 消息,从列表框的制订索引位置处删除一个字符串:

SendMessage (hwndList, LB_DELETESTRING, iIndex, 0);  

可以使用 LB_RESETCONTENT 来清除列表框中的所有项目:

SendMessage (hwndList, LB_RESETCONTENT, 0, 0);  

        列表框窗口过程在增加或删除列表框的项目时会更新显示。如果有大量的字符串要添加或删除,可能需要暂时抑制这一操作,这可以通过关掉控件的重绘标志来实现

SendMessage (hwndList, LB_SETREDRAW, FALSE, 0);  

完成之后,可以重新将重绘标志开启

SendMessage (hwndList, LB_SETREDRAW, TRUE, 0);  

        用 LBS_NOREDRAW 样式产生的列表框最初开始时就会关闭重绘标志。

9.6.3  项目的选择和提取

        在 SendMessage 调用执行下面所示的任务时,通常会返回一个值。如果发生了错误,此值便设置为 LB_ERR(定义为 -1)。

        在把一些项目加入列表框之后,可以用下面的方法了解列表框中有多少个项目:

iCount = SendMessage (hwndList, LB_GETCOUNT, 0, 0);

        其他一些函数调用在用于单选列表框和多选列表框时有一些不同。让我们先看看单选列表框的情况。

        通常,允许用户从列表框中进行选择。但是,如果要突出一个默认选中项,可以使用以下语句:

SendMessage (hwndList, LB_SETCURSEL, iIndex, 0);
在这个调用中,如果把 iParam 设置为 -1,将取消选中所有项。

        也可以根据起始字符选择一个项目:

iIndex = SendMessage(hwndList, LB_SELECTSTRING, iIndex,
                     (LPARAM) szSearchString);
在这个 SendMessage 调用中,作为 iParam 参数给出的 iIndex 指定了从哪一个索引开始搜索最先匹配 szSearchString 的项目。如果 iIndex 值为 -1,那么从顶端处开始查找。SendMessage 返回被选中项目的索引,如果没有首字符匹配 szSearchString 则返回 LB_ERR。

        在收到来自列表框的 WM_COMMAND 消息时(或在其他任何时间),可以使用 LB_GETCURSEL 得到当选选中项的索引值。

iIndex = SendMessage (hwndList, LB_GETCURSEL, 0, 0);
如果没有项目被选中,iIndex 返回值将是 LB_ERR。

        可通过以下语句获得列表框中任何字符串的长度:

iLength = SendMessage (hwndList, LB_GETTEXTLEN, iIndex, 0);
并把该项目复制到一个文本缓冲区:

iLength = SendMessage (hwndList, LB_GETTEXT, iIndex,
                      (LPARAM) szBuffer);
在这两种情况下,iLength 返回值都是字符串的长度。szBuffer 数组必须足够大以包括字符串的长度和字符串终止符。你应该利用 LB_GETTEXTLEN 来为字符串的存储预先分配好空间。

        对于多选列表框,不能使用 LB_SETCURSEL,LB_GETCURSEL 或 LB_SELECTSTRING。相反,应使用 LB_SETSEL,在不影响其他项目的选中状态的情况下,设置某个特定项目的选择状态:

SendMessage (hwndList, LB_SETSEL, wParam, iIndex);
如果 wParam 参数是非零值,则选择并高亮显示该项目;如果是 0,则取消选择。如果 lParam 的参数是 -1,那么要么所有项目都被选中,要么所有项目都取消选中。也可以检验一个特定项目的选择状态,使用以下语句即可:

iSelect= SendMessage (hwndList, LB_GETSEL, iIndex, 0);
如果 iIndex 指定的项目已被选中,则 iSelect 被设置为非零值;如果并非如此,则设置为 0。

9.6.4  接收来自列表框的消息

        当用户用鼠标单击一个列表框时,这个列表框将获得输入焦点。父窗口可以通过下面的调用把输入焦点给一个列表框控件:

SetFocus (hwndList);
当一个列表框具有输入焦点时,光标移动键、字符键和空格键也可用于从列表框中选择项目

        一个列表框控件会发送 WM_COMMAND 信息到其父窗口。相应的 wParam 和 lParam 变量的含义和在按钮控件、编辑控件中是一样的:

 LOWORD(wParam) 子窗口 ID
 HIWORD(wParam) 通知码
 lParam 子窗口句柄

        通知码和它们的值如下:

 LBN_ERRSPACE -2
 LBN_SELCHANGE 1
 LBN_DBLCLK 2
 LBN_SELCANCEL 3
 LBN_SETFOCUS 4
 LBN_KILLFOCUS 5

只有在列表框窗口样式包含了 LBS_NOTIFY 时,列表框控件才会向父窗口发送 LBN_SELCHANGE 和 LBN_DBLCLK。

        LBN_ERRSPACE 代码表明,该列表框控件已经用尽了它的空间。LBN_SELCHANGE 代码表明当前的选择发生了变化;这些消息可能出现在当用户在列表框中移动高亮显示的选中项时,或是通过空格键切换选择状态时,又或者是用鼠标单击一个项目时。LBN_DBCLK 代码表明列表框中的某个项目已被鼠标双击。(通知码 LBN_SELCHANGE 和 LBN_DBLCLK 的值表明了鼠标单击的次数。)

        根据你的应用程序,你可能想要使用 LBN_SELCHANGE 或 LBN_DBLCLK,或同时使用这二者。程序将收到许多 LBN_SELCHANGE 消息,但只有当用户用鼠标双击时 LBN_DBLCLK 才会出现。如果程序使用了双击,则需要提供一个键盘接口,以提供 LBN_DBLCLK 的等效功能。

9.6.5  简单的列表框程序 

        在知道了如何创建一个列表框,如何添加列表框的文本项目,如何接收来自列表框的消息,以及如何提取字符串之后,现在是时候编写程序了。ENVIRON 程序在它的客户区内使用一个列表框来显示当前操作系统的环境变量(如 PATH 和 WINDIR)。当你选中一个环境变量时,相应的环境字符串便会显示在客户区的顶部。

/*--------------------------------------------------------
   ENVIRON.C --   Environment List Box
                 (c) Charles Petzold, 1998
  --------------------------------------------------------*/
#include <windows.h>

#define ID_LIST 1
#define ID_TEXT 2

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

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     static TCHAR szAppName[] = TEXT ("Environ");
     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) (COLOR_WINDOW + 1);
     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("Environment List Box"),
                          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 ;
}

void FillListBox (HWND hwndList)
{
    int     iLength;
    TCHAR * pVarBlock, * pVarBeg, * pVarEnd, * pVarName;

    pVarBlock = GetEnvironmentStrings();    // Get pointer to environment block

    while (*pVarBlock)
    {
        if (*pVarBlock != '=') // Skip variable names beginning with '='
        {
            pVarBeg = pVarBlock;            // Beginning of variable name
            while (*pVarBlock++ != '=');    // Scan until '='
            pVarEnd = pVarBlock - 1;        // Points to '=' sign
            iLength = pVarEnd - pVarBeg;    // Length of variable name

                // Allocate memory for the variable name and terminating
                // Zero. Copy the variable name and append a zero.

            pVarName = (TCHAR *)calloc(iLength + 1, sizeof(TCHAR));
            CopyMemory(pVarName, pVarBeg, iLength * sizeof (TCHAR));
            pVarName[iLength] = '\0';

                // Put the variable name in the list box and free memory.

            SendMessage(hwndList, LB_ADDSTRING, 0, (LPARAM) pVarName);
            free (pVarName);
        }
        while (*pVarBlock++ != '\0');   // Scan until terminating zero
    }
    FreeEnvironmentStrings(pVarBlock);
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     static HWND hwndList, hwndText;
     int         iIndex, iLength, cxChar, cyChar;
     TCHAR     * pVarName, * pVarValue;

     switch (message)
     {
     case WM_CREATE:
          cxChar = LOWORD(GetDialogBaseUnits());
          cyChar = HIWORD(GetDialogBaseUnits());

            // Create listbox and static text windows.

          hwndList = CreateWindow(TEXT("listbox"), NULL,
                                  WS_CHILD | WS_VISIBLE | LBS_STANDARD,
                                  cxChar, cyChar * 3,
                                  cxChar * 16 + GetSystemMetrics(SM_CXVSCROLL),
                                  cyChar * 5,
                                  hwnd, (HMENU) ID_LIST,
                                  (HINSTANCE) GetWindowLong(hwnd, GWL_HINSTANCE),
                                  NULL);

          hwndText = CreateWindow(TEXT("static"), NULL,
                                  WS_CHILD | WS_VISIBLE | SS_LEFT,
                                  cxChar, cyChar,
                                  GetSystemMetrics(SM_CXSCREEN), cyChar,
                                  hwnd, (HMENU) ID_TEXT,
                                  (HINSTANCE) GetWindowLong(hwnd, GWL_HINSTANCE),
                                  NULL);

          FillListBox(hwndList);
          return 0;

     case WM_SETFOCUS:
          SetFocus(hwndList);
          return 0;

     case WM_COMMAND:
          if (LOWORD(wParam) == ID_LIST && HIWORD(wParam) == LBN_SELCHANGE)
          {
                    // Get current selection.

                iIndex = SendMessage(hwndList, LB_GETCURSEL, 0, 0);
                iLength = SendMessage(hwndList, LB_GETTEXTLEN, iIndex, 0) + 1;
                pVarName = (TCHAR *)calloc(iLength, sizeof (TCHAR));
                SendMessage(hwndList, LB_GETTEXT, iIndex, (LPARAM) pVarName);

                    // Get environment string.
                iLength = GetEnvironmentVariable(pVarName, NULL, 0);
                pVarValue = (TCHAR *)calloc(iLength, sizeof(TCHAR));
                GetEnvironmentVariable(pVarName, pVarValue, iLength);

                    // Show it in window.

                SetWindowText (hwndText, pVarValue);
                free(pVarName);
                free(pVarValue);
          }
         return 0;

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

        ENVIRON 程序创建了两个子窗口:一个 LBS_STANDARD 样式的列表框和一个 SS_LEFT(左对齐文本)样式的静态窗口。它使用 GetEnvironmentStrings 函数获得了一个指向内存块的指针,该内存块中有所有环境变量的名称和值。ENVIRON 程序在 FillListBox 函数中对这个内存块进行解析,并向列表框的窗口过程发送 LB_ADDSTRING 消息来把每个字符串写入到列表框中。

        在运行 ENVIRON 程序的时候,可以使用鼠标或键盘选择一个环境变量。每次改变选择时,列表框都会给父窗口(这里是 WndProc)发送一条 WM_COMMAND 消息。WndProc 收到 WM_COMMAND 消息后,会检查 wParam 的低位字是否等于 ID_LIST(列表框的子 ID),以及 wParam 的高位字(通知码)是否等于 LBN_SELCHANGE。如果是,它便使用 LB_GETCURSEL 消息得到选中项的索引值,并使用 LB_GETTEXT 得到选中项的文本,即环境变量的名称。ENVIRON 程序使用 C 函数 GetEnvironmentVariable 获得环境变量相对应的环境字符串,再使用 SetWindowText 把这个字符串送到静态子窗口控件去显示。

9.6.6  列出文件

        我总是把最好的留到最后讲:LB_DIR 是列表框中功能最强的消息。下面的函数调用可将文件目录列表写入列表框中,这个文件目录列表可包括子目录和有效的磁盘驱动器:

SendMessage (hwndList, LB_DIR, iAttr, (LPARAM) szFileSpec);

使用文件属性代码

        iAttr 参数是文件属性代码。文件属性代码的最低字节可以是下表中数值的组合。

iAttr数  值属  性
 DDL_READWRITE 0x0000 普通文件
 DDL_READONLY 0x0001 只读文件
 DDL_HIDDEN 0x0002 隐藏文件
 DDL_SYSTEM 0x0004 系统文件
 DDL_DIRECTORY 0x0010 子目录
 DDL_ARCHIVE 0x0020 设置了存档位的文件

        紧接着的最高字节提供了一些额外的搜索条件:

iAttr数  值选  项
 DDL_DRIVES 0x4000 包括驱动器字符
 DDL_EXCLUSIVE 0x8000 只搜索指定的值
这里的 DDL 前缀 表示 “dialog directory list” (对话框目录列表)。

        当 LB_DIR 消息的 iAttr 值是 DDL_READWRITE 时,列表框中会列出普通文件、只读文件以及存档位已设置的文件。如果 iAttr 值是 DDL_DIRECTORY,那么除了以上的文件外,这份清单还将列出带有方括号的目录名以及子目录名。组合值 DDL_DRIVES | DDL_DIRECTORY 会将这个列表扩展到包括所有有效驱动器,驱动器字符用短划线分隔。

        如果 iAttr 的最高位被设置,则它只列出具有某种标志的文件,而不列出一般的文件。例如,对于 Windows 文件备份程序,你可能希望只列出自从上次备份处理依赖所有被修改过的文件。这类文件的存档位已被设置,所以如果希望列出这些文件,可以使用 DDL_EXCLUSIVE | DDL_ARCHIVE。

文件列表的排序

        lParam 参数是一个指向文件限定字符串(例如 “*.*”)的指针。该文件限定不影响列表框中包含的子目录。

        你也许想在显示有文件列表的列表框上使用 LBS_SORT 消息。列表框将首先列出满足文件限定条件的所有文件名称,然后(可选性地)列出子目录的名称。第一个子目录列表将采取以下形式:

        [..]

这种“双点”子目录可以让用户返回上一级目录。(如果列表中的文件已在根目录,则不会出现这种双点符号。)最后,特定的子目录名称会用以下方式列出:

        [SUBDIR]

后面紧跟(同样是可选地)有效磁盘驱动器的清单,其形式如下:

        [-A-]

9.6.7  Windows 的 HEAD 程序

        UNIX 操作系统上有一个众所周知的实用程序叫“head”,它能显示一个文件的最初几行。让我们使用列表框,写出适用于 Windows 的类似程序。HEAD 在列表框中列出了所有文件和子目录。在文件名上双击鼠标,或者当文件名被选择时按下回车键,可以显示这个文件。使用这些方法之一还可以更改子目录。该程序可在 HEAD 窗口的右侧客户区上显示出文件开头的内容,内容的大小不超过 8KB。

/*--------------------------------------------------------
   HEAD.C --   Displays beginning (head) of file
                 (c) Charles Petzold, 1998
  --------------------------------------------------------*/
#include <windows.h>

#define ID_LIST 1
#define ID_TEXT 2
#define MAXREAD 8192
//#define DIRATTR (DDL_READWRITE | DDL_READONLY | DDL_HIDDEN | DDL_SYSTEM | \
//                 DDL_DIRECTORY | DDL_ARCHIVE | DDL_DRIVES)
#define DIRATTR (DDL_READONLY | DDL_READONLY | DDL_HIDDEN | DDL_SYSTEM | \
                 DDL_DIRECTORY | DDL_ARCHIVE | DDL_DRIVES)
#define DTFLAGS (DT_WORDBREAK | DT_EXPANDTABS | DT_NOCLIP | DT_NOPREFIX)

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

WNDPROC OldList;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     static TCHAR szAppName[] = TEXT ("head");
     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) (COLOR_BTNFACE + 1);
     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("head"),
                          WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
                          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 BOOL    bValidFile;
     static BYTE    buffer[MAXREAD];
     static HWND    hwndList, hwndText;
     static RECT    rect;
     static TCHAR   szFile[MAX_PATH + 1];
     HANDLE         hFile;
     HDC            hdc;
     int            i, cxChar, cyChar;
     PAINTSTRUCT    ps;
     TCHAR          szBuffer[MAX_PATH + 1];

     switch (message)
     {
     case WM_CREATE:
          cxChar = LOWORD(GetDialogBaseUnits());
          cyChar = HIWORD(GetDialogBaseUnits());

          rect.left = 20 * cxChar;
          rect.top = 3 * cyChar;

          hwndList = CreateWindow(TEXT("listbox"), NULL,
                                  WS_CHILD | WS_VISIBLE | LBS_STANDARD,
                                  cxChar, cyChar * 3,
                                  cxChar * 13 + GetSystemMetrics(SM_CXVSCROLL),
                                  cyChar * 10,
                                  hwnd, (HMENU) ID_LIST,
                                  (HINSTANCE) GetWindowLong(hwnd, GWL_HINSTANCE),
                                  NULL);

          GetCurrentDirectory(MAX_PATH + 1, szBuffer);

          hwndText = CreateWindow(TEXT("static"), szBuffer,
                                  WS_CHILD | WS_VISIBLE | SS_LEFT,
                                  cxChar, cyChar, cxChar * MAX_PATH, cyChar,
                                  hwnd, (HMENU) ID_TEXT,
                                  (HINSTANCE) GetWindowLong(hwnd, GWL_HINSTANCE),
                                  NULL);

          OldList = (WNDPROC) SetWindowLong(hwndList, GWL_WNDPROC,
                                                (LPARAM) ListProc);

          SendMessage(hwndList, LB_DIR, DIRATTR, (LPARAM) TEXT("*.*"));
          return 0;

     case WM_SIZE:
          rect.right  = LOWORD(lParam);
          rect.bottom = HIWORD(lParam);
          return 0;

     case WM_SETFOCUS:
          SetFocus(hwndList);
          return 0;

     case WM_COMMAND:
          if (LOWORD(wParam) == ID_LIST && HIWORD(wParam) == LBN_DBLCLK)
          {
              if (LB_ERR == (i = SendMessage(hwndList, LB_GETCURSEL, 0, 0)))
                break;

              SendMessage(hwndList, LB_GETTEXT, i, (LPARAM) szBuffer);

              if (INVALID_HANDLE_VALUE != (hFile = CreateFile(szBuffer,
                        GENERIC_READ, FILE_SHARE_READ, NULL,
                        OPEN_EXISTING, 0, NULL)))
              {
                  CloseHandle(hFile);
                  bValidFile = TRUE;
                  lstrcpy(szFile, szBuffer);
                  GetCurrentDirectory(MAX_PATH + 1, szBuffer);

                  if (szBuffer[lstrlen(szBuffer) - 1] != '\\')
                        lstrcat(szBuffer, TEXT("\\"));
                  SetWindowText (hwndText, lstrcat(szBuffer, szFile));

              }
              else
              {
                  bValidFile = FALSE;
                  szBuffer[lstrlen(szBuffer) - 1] = '\0';

                        // If setting the directory doesn't work, maybe it's
                        // a drive change. so try that.

                  if (!SetCurrentDirectory(szBuffer + 1))
                  {
                      szBuffer[3] = ':';
                      szBuffer[4] = '\0';
                      SetCurrentDirectory(szBuffer + 2);
                  }

                        // Get the new directory name and fill the list box.

                  GetCurrentDirectory(MAX_PATH + 1, szBuffer);
                  SetWindowText(hwndText, szBuffer);
                  SendMessage(hwndList, LB_RESETCONTENT, 0, 0);
                  SendMessage (hwndList, LB_DIR, DIRATTR,
                                        (LPARAM) TEXT("*.*"));
              }
              InvalidateRect(hwnd, NULL, TRUE);
          }
         return 0;

     case WM_PAINT:
          if (!bValidFile)
            break;

          if (INVALID_HANDLE_VALUE == (hFile = CreateFile(szFile,
                    GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL)))
          {
              bValidFile = FALSE;
              break;
          }

          ReadFile(hFile, buffer, MAXREAD, (PDWORD)&i, NULL);
          CloseHandle(hFile);

                // i now equals the number of bytes in buffer.
                // Commence getting a device context for display text.

          hdc = BeginPaint(hwnd, &ps);
          SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));
          SetTextColor(hdc, GetSysColor(COLOR_BTNTEXT));
          SetBkColor(hdc, GetSysColor(COLOR_BTNFACE));

                // Assume the file is ASCII

          DrawTextA(hdc, (char*)buffer, i, &rect, DTFLAGS);

          EndPaint(hwnd, &ps);
          return 0;

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

LRESULT CALLBACK ListProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    if (message == WM_KEYDOWN && wParam == VK_RETURN)
        SendMessage(GetParent(hwnd), WM_COMMAND,
                    MAKELONG(1, LBN_DBLCLK), (LPARAM) hwnd);

    return CallWindowProc(OldList, hwnd, message, wParam, lParam);
}

        在 ENVIRON 程序中,只要我们用鼠标或键盘选择一个环境变量,程序就会显示出环境字符串。但是如果在 HEAD 程序中也用这种“先选择再显示”的办法,该程序也许会运行得十分缓慢,因为随着你在列表框中不断地选择不同的文件名,文件会被不断地打开和关闭。为了解决这个问题,HEAD 限制文件或子目录的选择要考鼠标双击。这带来了一个新的问题,因为列表框控件没有对应鼠标双击的自动键盘接口。所以正如我们所知的,在可能的情况下,我们应该提供键盘接口。

        有解决方案吗?当然有,答案就是采用窗口子类。HEAD 程序中的列表框子类函数名为 ListProc。它只捕捉 WM_KEYDOWN 消息,当发现 wParam 等于 VK_RETURN 时,它向其父窗口发送一个带有 LBN_DBLCLK 通知码的 WM_COMMAND 消息。函数 WndProc 在处理 WM_COMMAND 时,调用了 Windows 的 CreateFile 函数来检查列表中的选中项。如果 CreateFile 返回一个错误,则表明被选中的不是一个文件,因此它可能是一个子目录。然后 HEAD 使用 SetCurrentDirectory 来改变子目录。如果 SetCurrentDirectory 也返回一个错误,则程序假定用户选择的是一个驱动器号。改变驱动器也需要调用 SetCurrentDirectory 函数。但是需要注意的是,驱动器字符签名不要加短划线,并且驱动器字符后面要加冒号。如果一切顺利,该函数会发给列表框一个 LB_RESETCONTENT 消息,以清除所有的内容。它还会发出 LB_DIR 消息,把新选中的子目录中的文件写入列表框。

        WndProc 函数中的 WM_PAINT 消息处理部分调用了 Windows 的 CreateFile 函数,以便打开该文件。CreateFile 函数将返回文件的句柄,这个文件句柄将被传递给 Windows 函数 ReadFile 和 CloseHandle。

        至此,我们在本章中第一次遇到了一个涉及 Unicode 的问题。在理想情况下,操作系统也许应该可以识别文本文件,从而 ReadFile 函数可以把 ASCII 文件转换为 Unicode 文本或从 Unicode 文件转换为 ASCII 文本。但是情况并非如此,ReadFile 只是读取文件的字节而不做任何的转换。这意味着,函数 DrawTextA(在编译可执行程序时没有定义 UNICODE 标识符)会将文本解释为 ASCII,而函数 DrawTextW(Unicode 版本)会将文本假设为 Unicode。

        因此,程序应该做的事就是试图弄清文件是 ASCII 文本还是 Unicode 文本,然后相应的调用 DrawTextA 或者 DrawTextW。而为了简化起见,HEAD 没有考虑这么多因素,而是直接调用了 DrawTextA。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值