使用 Windows

本节中的示例介绍如何执行以下任务:

创建主窗口

应用程序创建的第一个窗口通常是主窗口。 使用 CreateWindowEx 函数创建主窗口,指定窗口类、窗口名称、窗口样式、大小、位置、菜单句柄、实例句柄和创建数据。 主窗口属于应用程序定义的窗口类,因此在创建主窗口之前,必须注册该窗口类并为该类提供窗口过程。

大多数应用程序通常使用 WS_OVERLAPPEDWINDOW 样式来创建主窗口。 此样式为窗口提供标题栏、窗口菜单、调整大小边框以及最小化和最大化按钮。 CreateWindowEx 函数返回一个唯一标识窗口的句柄。

以下示例创建一个属于应用程序定义的窗口类的主窗口。 窗口名称主窗口将显示在窗口的标题栏中。 通过将 WS_VSCROLLWS_HSCROLL 样式与 WS_OVERLAPPEDWINDOW 样式相结合,除了 WS_OVERLAPPEDWINDOW 样式提供的组件之外,应用程序还会创建一个主窗口,其中包含水平滚动条和垂直滚动条。 CW_USEDEFAULT 常量的四个匹配项将窗口的初始大小和位置设置为系统定义的默认值。 通过指定 NULL 而不是菜单句柄,窗口将具有为窗口类定义的菜单。

HINSTANCE hinst; 
HWND hwndMain; 
 
// Create the main window. 
 
hwndMain = CreateWindowEx( 
    0,                      // no extended styles           
    "MainWClass",           // class name                   
    "Main Window",          // window name                  
    WS_OVERLAPPEDWINDOW |   // overlapped window            
             WS_HSCROLL |   // horizontal scroll bar        
             WS_VSCROLL,    // vertical scroll bar          
    CW_USEDEFAULT,          // default horizontal position  
    CW_USEDEFAULT,          // default vertical position    
    CW_USEDEFAULT,          // default width                
    CW_USEDEFAULT,          // default height               
    (HWND) NULL,            // no parent or owner window    
    (HMENU) NULL,           // class menu used              
    hinst,                  // instance handle              
    NULL);                  // no window creation data      
 
if (!hwndMain) 
    return FALSE; 
 
// Show the window using the flag specified by the program 
// that started the application, and send the application 
// a WM_PAINT message. 
 
ShowWindow(hwndMain, SW_SHOWDEFAULT); 
UpdateWindow(hwndMain); 

请注意,前面的示例在创建主窗口后调用 ShowWindow 函数。 这样做是因为系统在创建主窗口后不会自动显示主窗口。 通过将 SW_SHOWDEFAULT 标志传递给 ShowWindow,应用程序允许启动应用程序的程序设置主窗口的初始显示状态。 UpdateWindow 函数向窗口发送其第一条 WM_PAINT 消息。

创建、枚举和调整子窗口的大小

可以使用子窗口将窗口的工作区划分为不同的功能区域。 创建子窗口就像创建主窗口一样,可以使用 CreateWindowEx 函数。 要创建应用程序定义的窗口类的窗口,必须注册该窗口类并在创建子窗口之前提供窗口过程。 必须为子窗口提供 WS_CHILD 样式,并在创建子窗口时为子窗口指定父窗口。

以下示例通过创建三个大小相等的子窗口,将应用程序主窗口的工作区划分为三个功能区域。 每个子窗口的高度与主窗口的工作区的高度相同,但每个子窗口都是其宽度的三分之一。 主窗口创建子窗口,以响应主窗口在其自己的窗口创建过程中接收的 WM_CREATE 消息。 由于每个子窗口都有 WS_BORDER 样式,因此每个子窗口都有一个细线边框。 此外,由于未指定 WS_VISIBLE 样式,因此每个子窗口最初都是隐藏的。 还要注意,每个子窗口都被分配了一个子窗口标识符。

主窗口响应 WM_SIZE 消息来调整子窗口的大小和位置,主窗口在其大小更改时接收该消息。 为了响应 WM_SIZE,主窗口使用 GetClientRect 函数检索其工作区的维度,然后将维度传递给 EnumChildWindows 函数。 EnumChildWindows 将句柄传递给每个子窗口,进而传递给应用程序定义的 EnumChildProc 回调函数。 此函数通过调用 MoveWindow 函数来调整每个子窗口的大小和位置;大小和位置基于主窗口工作区的维度和子窗口的标识符。 之后,EnumChildProc 调用 ShowWindow 函数以使窗口可见。

#define ID_FIRSTCHILD  100 
#define ID_SECONDCHILD 101 
#define ID_THIRDCHILD  102 
 
LONG APIENTRY MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 
{ 
    RECT rcClient; 
    int i; 
 
    switch(uMsg) 
    { 
        case WM_CREATE: // creating main window  
 
            // Create three invisible child windows. 

            for (i = 0; i < 3; i++) 
            { 
                CreateWindowEx(0, 
                               "ChildWClass", 
                               (LPCTSTR) NULL, 
                               WS_CHILD | WS_BORDER, 
                               0,0,0,0, 
                               hwnd, 
                               (HMENU) (int) (ID_FIRSTCHILD + i), 
                               hinst, 
                               NULL); 
            }
 
            return 0; 
 
        case WM_SIZE:   // main window changed size 
 
            // Get the dimensions of the main window's client 
            // area, and enumerate the child windows. Pass the 
            // dimensions to the child windows during enumeration. 
 
            GetClientRect(hwnd, &rcClient); 
            EnumChildWindows(hwnd, EnumChildProc, (LPARAM) &rcClient); 
            return 0; 

        // Process other messages. 
    } 
    return DefWindowProc(hwnd, uMsg, wParam, lParam); 
} 
 
BOOL CALLBACK EnumChildProc(HWND hwndChild, LPARAM lParam) 
{ 
    LPRECT rcParent; 
    int i, idChild; 
 
    // Retrieve the child-window identifier. Use it to set the 
    // position of the child window. 
 
    idChild = GetWindowLong(hwndChild, GWL_ID); 
 
    if (idChild == ID_FIRSTCHILD) 
        i = 0; 
    else if (idChild == ID_SECONDCHILD) 
        i = 1; 
    else 
        i = 2; 
 
    // Size and position the child window.  
 
    rcParent = (LPRECT) lParam; 
    MoveWindow(hwndChild, 
               (rcParent->right / 3) * i, 
               0, 
               rcParent->right / 3, 
               rcParent->bottom, 
               TRUE); 
 
    // Make sure the child window is visible. 
 
    ShowWindow(hwndChild, SW_SHOW); 
 
    return TRUE;
}

销毁窗口

可以使用 DestroyWindow 函数销毁窗口。 通常,应用程序会在销毁窗口之前发送 WM_CLOSE 消息,从而为窗口提供在销毁窗口之前提示用户进行确认的机会。 当用户从窗口菜单单击关闭时,包含窗口菜单的窗口会自动接收 WM_CLOSE 消息。 如果用户确认应销毁窗口,应用程序将调用 DestroyWindow。 从屏幕上删除消息后,系统发送 WM_DESTROY 消息给窗口。 为了响应 WM_DESTROY,窗口保存其数据并释放其分配的所有资源。 主窗口通过调用 PostQuitMessage 函数退出应用程序来结束对 WM_DESTROY 的处理。

以下示例显示如何在销毁窗口之前提示用户确认。 为了响应 WM_CLOSE,将显示一个对话框,其中包含取消按钮。 如果用户单击,则调用 DestroyWindow;否则不会销毁窗口。 当应用程序处理 WM_CLOSE 消息时,在所有情况下都会返回 0。 由于被销毁的窗口是主窗口,因此该示例调用 PostQuitMessage 以响应 WM_DESTROY

case WM_CLOSE: 
 
    // Create the message box. If the user clicks 
    // the Yes button, destroy the main window. 
 
    if (MessageBox(hwnd, szConfirm, szAppName, MB_YESNOCANCEL) == IDYES) 
        DestroyWindow(hwndMain); 
    return 0; 
 
case WM_DESTROY: 
 
    // Post the WM_QUIT message to 
    // quit the application terminate. 
 
    PostQuitMessage(0); 
    return 0;

使用分层窗口

若要使对话框显示为半透明窗口,请首先像往常一样创建对话框。 然后,在 WM_INITDIALOG 设置窗口扩展样式的分层位,并使用所需的 alpha 值调用 SetLayeredWindowAttributes。 代码可能如下所示:

// Set WS_EX_LAYERED on this window 
SetWindowLong(hwnd, 
              GWL_EXSTYLE, 
              GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_LAYERED);

// Make this window 70% alpha
SetLayeredWindowAttributes(hwnd, 0, (255 * 70) / 100, LWA_ALPHA);

请注意,SetLayeredWindowAttributes 的第三个参数的值范围从 0 到 255,0 表示窗口完全透明,255 表示窗口完全不透明。 此参数模拟 AlphaBlend 函数更通用的 BLENDFUNCTION

若要使此窗口再次完全不透明,请通过调用 SetWindowLong 删除 WS_EX_LAYERED 位,然后要求窗口重新绘制。 删除该位是为了让系统知道它可以释放一些与分层和重定向相关的内存。 代码可能如下所示:

// Remove WS_EX_LAYERED from this window styles
SetWindowLong(hwnd, 
              GWL_EXSTYLE,
              GetWindowLong(hwnd, GWL_EXSTYLE) & ~WS_EX_LAYERED);

// Ask the window and its children to repaint
RedrawWindow(hwnd, 
             NULL, 
             NULL, 
             RDW_ERASE | RDW_INVALIDATE | RDW_FRAME | RDW_ALLCHILDREN);

若要使用分层子窗口,应用程序必须在清单中声明自己支持 Windows 8。

对于 Windows 10/11,用户可以在其 app.manifest 中包含此兼容性片段,以使其支持 Windows 10:

...
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
    <application>
      <!-- Windows 10 GUID -->
      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
    </application>
</compatibility>
...

有关修改应用程序清单的更多信息,请单击此处阅读:应用程序清单