自定义窗口的玻璃效果(边框,标题栏...)

http://msdn.microsoft.com/en-us/library/bb688195(VS.85).aspx

Custom Window Frame Using DWM
使用DWM自定义窗口边框

Custom Window Frame Using DWM
Custom Window Frame Using DWM

This topic demonstrates how to use the Windows Vista Desktop Window Manager (DWM) APIs to create custom window frames for your application.

这篇文章揭示如何使用windows vista desktop window manager(DWM) APIs为你的应用程序创建自定义的窗口边框.

 Introduction

简介

In Windows Vista, the appearance of the non-client areas of application windows (the title bar, icon, window border, and caption buttons) is controlled by the DWM. Using the DWM APIs, you can manipulate the way the DWM handles the rendering of a window's frame.

在vista系统中,应用程序的非客户区域(标题栏,图标,边框,标题栏按钮)的外观都是DWM控制的.使用DWM的API,你可以控制DWN渲染窗口的边框.

One feature of the DWM APIs is the ability to extend the application frame into the client area. This enables you to integrate client user interface (UI), such as a toolbar, into the frame, giving the UI controls a more prominent place in the application UI. For example, Windows Internet Explorer 7 on Windows Vista integrates the navigation bar into the window frame by extending the top of the frame.

DWM AP的一个特性是允许扩展应用程序边框到客户端区域。这使你可以集成客户端的用户界面,比如工具栏,到外框中,使某些控件显示在系统界面的最突出的位置。比如,vista上的IE7就通过扩展上边框在外框集成了导航条。

 

The ability to extend the window frame also enables you to create custom frames while maintaining the window's look and feel. For example, Microsoft Office Word 2007 draws the Office Button and the Quick Access Toolbar inside the custom frame while providing the standard Minimize, Maximize, and Close caption buttons.

扩展窗口边框的能力同样可以使你可以创建自定义的边框来维护你的整个窗口的外观和感觉统一。比如 Microsoft Office Word 2007画Office按钮和快速访问工具栏在自定义窗口中,并使用了标准的最大化,最小化,关闭按钮。

To create custom frames using the DWM APIs, caption button hit testing, window resizing and moving, and painting on the extended frame. These include extending the frame into the client area, providing custom client area measurements, and hit testing for the custom frame area.

为了创建自定义的边框,包括了需要标题栏点击测试,窗口大小调整和移动,绘制扩展边框等任务。还包括了扩展边框到客户端区域,提供自己计算的客户端区域的大小,和自己的边框区域点击测试。

 Extending the Client Frame

The functionality to extend the frame into the client area is exposed by the DwmExtendFrameIntoClientArea function. To extend the frame, you pass the target window handle along with the margin inset values to DwmExtendFrameIntoClientArea. The margin inset values determine how much to extend the frame for the four sides of a window (top, bottom, left, and right).

The following code demonstrates the DwmExtendFrameIntoClientArea call used to extend the frame.

使用DwmExtendFrameIntoClientArea 扩展窗口边框。传一个窗口的句柄和边的宽度集合给DwmExtendFrameIntoClientArea。margin集合决定了扩展的四个边框的宽度。

// Handle window activation.
if (message == WM_ACTIVATE)
{
    // Extend the frame into the client area.
    MARGINS margins;

    margins.cxLeftWidth = LEFTEXTENDWIDTH;      //8
    margins.cxRightWidth = RIGHTEXTENDWIDTH;    //8
    margins.cyBottomHeight = BOTTOMEXTENDWIDTH; //20
    margins.cyTopHeight = TOPEXTENDWIDTH;       //27

    hr = DwmExtendFrameIntoClientArea(hWnd, &margins);

    if (!SUCCEEDED(hr))
    {
        // Handle error.
    }

    fCallDWP = true;
    lRet = 0;
}

Note that the frame extension is done within the WM_ACTIVATE message rather than WM_CREATE. This ensures that frame extension is handled properly when the window is at its default size and when it is maximized.

请注意,边框扩展是在WM_ACTIVATE 的时候完成,而不是 WM_CREATE。这样何以确保窗口在缺省大小和最大都能正确处理窗口扩展。

The following image shows a standard window frame (on the left) and the same window frame with an extended frame (on the right). The frame is extended using the previous code sample and the default Visual Studio WNDCLASS(EX) background (COLOR_WINDOW +1).

下边的图片显示了相同的边框,左边是标准窗口和右边含窗口扩展,使用缺省窗口背景COLOR_WINDOW +1。

 

 Note that the only difference between the two windows is the missing thin black line border of the client region from the window on the right. The reason for the missing black border is that it is incorporated into the extended frame, but the rest of the client area is not. For the extended frames to be visible, the regions underlying each of the extended frame's sides must have pixel data that has an alpha value of 0. The black border around the client region has pixel data in which all color values (red, green, blue, and alpha) are set to 0. The rest of the background does not have the alpha value set to 0, so the rest of the extended frame is not visible.

注意,上图只能看出右边少了黑色的客户区域的边框。原因是原来的黑色的区域边框,已经成为外框扩展的一部分了,但是客户区域没有。(vcommon注:换言之,外框变大了,客户区域没有相应变小,挡到了外框)。如果希望外框可见可见,那么盖住外框扩展部分的区域必须是alpha为0的像素数据。在黑色边框内部的区域则按平常的色彩值,比如设置为0。如果你没有把其余部分的背景色的alpha不设置为0,那么外框就是不可见得。

(vcommon注:所以自己要自己维护实际的客户区域,和原来直接GetClientRect拿到的已经不一样了)

The easiest way to ensure that the extended frames are visible is to paint the entire client region black. To accomplish this, initialize the hbrBackground member of your WNDCLASS(EX) structure to the handle of the stock BLACK_BRUSH. The following image shows the same standard window frame (left) and extended frame (right) as shown previously; this time, however, hbrBackground is set to the BLACK_BRUSH handle obtained from the GetStockObject function.

最简单的办法就是整个客户区域设置黑色。初始WNDCLASS(EX)的hbrBackground 为BLACK_BRUSH

Removing the Standard Frame

去掉标准边框

After you've extended the frame of your application and made it visible, you can remove the standard frame. Removing the standard frame enables you to control the width of each side of the frame rather than simply extending out the standard frame.

在扩展了边框并且使边框可见之后,你可以接下来去掉标准的边框。这样你就可以控制每一边框的宽度和样子。

To remove the standard window frame, you need to handle the WM_NCCALCSIZE message, specifically when the wParam is TRUE and the return is 0. By handling the message in this way, your application uses the entire window region as the client area, removing the standard frame.

为了实现他,你需要处理WM_NCCALCSIZE 消息,当wParam 为 TRUE 的时候,然后返回0。处理这个消息的目的是,把整个窗口作为客户区域,这样就可以去掉边框。

(vcommon注:windows 窗口的行为,WM_NCCALCSIZE 消息,当wParam 为 TRUE 的时候,直接返回0,则默认整个窗口为客户区域,否则要处理NCCALCSIZE_PARAMS的lParam)

Unfortunately, the results of handling the WM_NCCALCSIZE message are not visible until the client region needs to be resized. Until then, the initial view of the window will appear with the standard frame and extended borders. To overcome this, you must either resize your window or perform an action that initiates a WM_NCCALCSIZE message at window creation. This can be accomplished using the SetWindowPos function that enables you to move your window and resize it. The following code demonstrates a call to SetWindowPos that forces a WM_NCCALCSIZE message to be sent using the current window rectangle attributes and the SWP_FRAMECHANGED flag.

不幸的是,只有客户区域被resize的时候WM_NCCALCSIZE 才出现,在这之前,window已经被创建,并且显示了出来。为了克服这个问题,我们必须在WM_CREATE九要先调整窗口大小。可以使用SetWindowPos实现。如下:

// Handle window creation.
if (message == WM_CREATE)
{
    RECT rcClient;
    GetWindowRect(hWnd, &rcClient);

    // Inform application of the frame change.
    SetWindowPos(
        hWnd, 
        NULL, 
        rcClient.left, rcClient.top,
        RECTWIDTH(rcClient), RECTHEIGHT(rcClient),
        SWP_FRAMECHANGED);

    fCallDWP = true;
    lRet = 0;
}

 

The following image shows the standard frame (left) and the newly extended frame without the standard frame (right).

 

Drawing in the Extended Frame Window

绘制扩展窗口边框

By removing the standard frame, you lose the automatic drawing of the application icon and title. To add these back to your application, you must draw them yourself. To do this, let's look at the change that has occurred to your client area.

因为移除了标准边框,你就失去了自动画系统图标和标题的功能。为了增加这个功能,你必须自己绘制。

让我们先看下客户区域发生了什么改变。

With the removal of the standard frame, your client area now consists of the entire window, extended frame and all. This even includes the region where the caption buttons are drawn. In the following side-by-side comparison, the client area for both the standard frame and the custom extended frame is highlighted in red. The client area for the standard frame window (left) is the black region. On the extended frame window (right), the client area is the entire window.

当移除了标准边框后,你的窗口区域覆盖了整个窗口,包括扩展边框和所有的东西。这甚至包括了标题栏按钮的部分。如下的比较,客户区域被标成红色。

 

Because the entire window is your client area, you can simply draw what you want in the extended frame. So, to add a title to your application, you just need to draw text to the appropriate region.

The following image shows themed text drawn on the custom caption frame. The title is drawn using the DrawThemeTextEx function. To view the code that paints the title, see Appendix B: Painting the Caption Title.

因为所有区域都是客户区域,你可以想怎么画就怎么画。如果加个标题,就画个字串在合时的位置。如附录B

(vcommon:为了模拟标准window的标题字体,我们不能简单的画字,必须要根据系统主题相关的字体,大小绘制标题)

Note   When drawing in your custom frame application, be careful when placing UI controls. Since the entire window is your client region, you must adjust your UI control placement for each frame width if you do not wish them to appear on or in the extended frame.

注意,你必须时刻注意界面控件的位置,因为整个窗口都是客户区域,防止界面出现在扩展边框上。

 

Enabling Hit Testing for the Custom Frame

启用点击测试

An unfortunate side effect of removing the standard frame is the loss of the default resizing and move behavior. For your application to properly emulate the standard window behavior, you will need to implement logic to handle caption button hit testing and frame resizing/moving.

去掉标准边框的另外一个缺陷就是没有了缺省的大小调整和窗口移动行为。所以要通过点击测试去实现。

For caption button hit testing, DWM provides the DwmDefWindowProc function. To properly hit test the caption buttons in custom frame scenarios, messages should first be passed to DwmDefWindowProc for handling. DwmDefWindowProc returns TRUE if a message is handled; otherwise, FALSE. If the message is not handled by DwmDefWindowProc, your application should handle the message itself or pass the message onto DefWindowProc.

为了使标题栏的button可以响应点击测试(vcommon注:否则只会响应move和resize),可以使用DwmDefWindowProc。返回true,则我们就不需要处理了(vcommon注:点击到了标题栏按钮上)否则我们自己处理它或者默认实现DefWindowProc

For frame resizing and moving, your application must provide the hit testing logic and handle frame hit test messages. Frame hit test messages are sent to you via the WM_NCHITTEST message, even if your application creates a custom frame without the standard frame.

为了自己处理resize和moving,点击测试的逻辑处理如下。边框发WM_NCHITTEST给应用程序,而不管又没有标准边框。

The following code demonstrates handling the WM_NCHITTEST message when the DwmDefWindowProc does not handle it. To see the code of the HitTestNCA function, see Appendix C: HitTestNCA Function.

附录C演示了如何处理WM_NCHITTEST消息:

// Handle hit testing in the NCA if not handled by DwmDefWindowProc.
if ((message == WM_NCHITTEST) && (lRet == 0))
{
    lRet = HitTestNCA(hWnd, wParam, lParam);

    if (lRet != HTNOWHERE)
    {
        fCallDWP = false;
    }
}

 

Appendix A: Sample Window Procedure

 

The following code sample demonstrates a window procedure and its supporting worker functions used to create a custom frame application.

//
//  Main WinProc.
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    bool fCallDWP = true;
    BOOL fDwmEnabled = FALSE;
    LRESULT lRet = 0;

    // Winproc worker for custom frame issues.
    if (SUCCEEDED(DwmIsCompositionEnabled(&fDwmEnabled))){
        lRet = CustomCaptionProc(hWnd, message, wParam, lParam, &fCallDWP);
    }

    // Winproc worker for the rest of the application.
    if (fCallDWP)
    {
        lRet = AppWinProc(hWnd, message, wParam, lParam);
    }

    return lRet;
}

//
// Message handler for handling the custom caption messages.
//
LRESULT CustomCaptionProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool* pfCallDWP)
{
    LRESULT lRet = 0;
    HRESULT hr = S_OK;
    bool fCallDWP = true; // Pass on to DefWindowProc?

    fCallDWP = !DwmDefWindowProc(hWnd, message, wParam, lParam, &lRet);

    // Handle window creation.
    if (message == WM_CREATE)
    {
        RECT rcClient;
        GetWindowRect(hWnd, &rcClient);

        // Inform application of the frame change.
        SetWindowPos(
            hWnd, 
            NULL, 
            rcClient.left, rcClient.top,
            RECTWIDTH(rcClient), RECTHEIGHT(rcClient),
            SWP_FRAMECHANGED);

        fCallDWP = true;
        lRet = 0;
    }

    // Handle window activation.
    if (message == WM_ACTIVATE)
    {
        // Extend the frame into the client area.
        MARGINS margins;

        margins.cxLeftWidth = LEFTEXTENDWIDTH;      //8
        margins.cxRightWidth = RIGHTEXTENDWIDTH;    //8
        margins.cyBottomHeight = BOTTOMEXTENDWIDTH; //20
        margins.cyTopHeight = TOPEXTENDWIDTH;       //27

        hr = DwmExtendFrameIntoClientArea(hWnd, &margins);

        if (!SUCCEEDED(hr))
        {
            // Handle error.
        }

        fCallDWP = true;
        lRet = 0;
    }

    if (message == WM_PAINT)
    {
        HDC hdc;
        {
            hdc = BeginPaint(hWnd, &ps);
            PaintCustomCaption(hWnd, hdc);
            EndPaint(hWnd, &ps);
        }

        fCallDWP = true;
        lRet = 0;
    }

    // Handle the non-client size message.
    if ((message == WM_NCCALCSIZE) && (wParam == TRUE))
    {
        // Calculate new NCCALCSIZE_PARAMS based on custom NCA inset.
        NCCALCSIZE_PARAMS *pncsp = reinterpret_cast<NCCALCSIZE_PARAMS*>(lParam);

        pncsp->rgrc[0].left   = pncsp->rgrc[0].left   + 0;
        pncsp->rgrc[0].top    = pncsp->rgrc[0].top    + 0;
        pncsp->rgrc[0].right  = pncsp->rgrc[0].right  - 0;
        pncsp->rgrc[0].bottom = pncsp->rgrc[0].bottom - 0;

        lRet = 0;
        // No need to pass message on to the DefWindowProc.
        fCallDWP = false;
    }

    // Handle hit testing in the NCA if not handled by DwmDefWindowProc.
    if ((message == WM_NCHITTEST) && (lRet == 0))
    {
        lRet = HitTestNCA(hWnd, wParam, lParam);

        if (lRet != HTNOWHERE)
        {
            fCallDWP = false;
        }
    }

    *pfCallDWP = fCallDWP;

    return lRet;
}

//
// Message handler the application.
//
LRESULT AppWinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    int wmId, wmEvent;
    PAINTSTRUCT ps;
    HDC hdc;
    HRESULT hr; 
    LRESULT result = 0;

    switch (message)
    {
    case WM_CREATE:
        {

        }
        break;
    case WM_COMMAND:
        wmId    = LOWORD(wParam);
        wmEvent = HIWORD(wParam);
        // Parse the menu selections:
        switch (wmId)
        {
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
        break;
    case WM_PAINT:
        {
            hdc = BeginPaint(hWnd, &ps);
            PaintCustomCaption(hWnd, hdc);
            // TODO: Add any drawing code here...

            EndPaint(hWnd, &ps);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

 

Appendix B: Painting the Caption Title

The following code demonstrates how to paint a caption title on the extended frame. This function must be called within the BeginPaint and EndPaint calls.

// Paint the title on the custom frame.
void PaintCustomCaption(HWND hWnd, HDC hdc)
{
    RECT rcClient;
    GetClientRect(hWnd, &rcClient);

    HTHEME hTheme = OpenThemeData(NULL, L"CompositedWindow::Window");
    if (hTheme)
    {
        HDC hdcPaint = CreateCompatibleDC(hdc);
        if (hdcPaint)
        {
            int cx = RECTWIDTH(rcClient);
            int cy = RECTHEIGHT(rcClient);

            // Define the BITMAPINFO structure used to draw text.
            // Note that biHeight is negative. This is done because
            // DrawThemeTextEx() needs the bitmap to be in top-to-bottom
            // order.
            BITMAPINFO dib = { 0 };
            dib.bmiHeader.biSize            = sizeof(BITMAPINFOHEADER);
            dib.bmiHeader.biWidth           = cx;
            dib.bmiHeader.biHeight          = -cy;
            dib.bmiHeader.biPlanes          = 1;
            dib.bmiHeader.biBitCount        = BIT_COUNT;
            dib.bmiHeader.biCompression     = BI_RGB;

            HBITMAP hbm = CreateDIBSection(hdc, &dib, DIB_RGB_COLORS, NULL, NULL, 0);
            if (hbm)
            {
                HBITMAP hbmOld = (HBITMAP)SelectObject(hdcPaint, hbm);

                // Setup the theme drawing options.
                DTTOPTS DttOpts = {sizeof(DTTOPTS)};
                DttOpts.dwFlags = DTT_COMPOSITED | DTT_GLOWSIZE;
                DttOpts.iGlowSize = 15;

                // Select a font.
                LOGFONT lgFont;
                HFONT hFontOld = NULL;
                if (SUCCEEDED(GetThemeSysFont(hTheme, TMT_CAPTIONFONT, &lgFont)))
                {
                    HFONT hFont = CreateFontIndirect(&lgFont);
                    hFontOld = (HFONT) SelectObject(hdcPaint, hFont);
                }

                // Draw the title.
                RECT rcPaint = rcClient;
                rcPaint.top += 8;
                //rcPaint.right -= 125;
                rcPaint.left += 8;
                //rcPaint.bottom = 50;
                DrawThemeTextEx(hTheme, hdcPaint, 0, 0, szTitle, -1, DT_LEFT | DT_WORD_ELLIPSIS, &rcPaint, &DttOpts);

                // Blit text to the frame.
                BitBlt(hdc, 0, 0, cx, cy, hdcPaint, 0, 0, SRCCOPY);

                SelectObject(hdcPaint, hbmOld);
                if (hFontOld)
                {
                    SelectObject(hdcPaint, hFontOld);
                }
                DeleteObject(hbm);
            }
            DeleteDC(hdcPaint);
        }
        CloseThemeData(hTheme);
    }
}

 

Appendix C: HitTestNCA Function

The following code shows the HitTestNCA function used in Enabling Hit Testing for the Custom Frame. This function handles the hit testing logic for the WM_NCHITTEST when DwmDefWindowProc does not handle the message.

// Hit test the frame for resizing and moving.
LRESULT HitTestNCA(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
    // Get point for the hit test.
    POINT ptMouse = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};

    // Get the window rectangle.
    RECT rcWindow;
    GetWindowRect(hWnd, &rcWindow);

    // Get frame rectangle, adjusted for the style without a caption.
    RECT rcFrame = { 0 };
    AdjustWindowRectEx(&rcFrame, WS_OVERLAPPEDWINDOW & ~WS_CAPTION, FALSE, NULL);

    // Determine if hit test is for resizing, default middle (1,1).
    USHORT uRow = 1;
    USHORT uCol = 1;
    bool fOnResizeBorder = false;

    // Determine if the point is at the top or bottom of the window.
    if ( ptMouse.y >= rcWindow.top && ptMouse.y < rcWindow.top + TOPEXTENDWIDTH)
    {
        fOnResizeBorder = (ptMouse.y < (rcWindow.top - rcFrame.top));
        uRow = 0;
    }
    else if ( ptMouse.y < rcWindow.bottom && ptMouse.y >= rcWindow.bottom - BOTTOMEXTENDWIDTH)
    {
        uRow = 2;
    }

    // Determine if the point is at the left or right of the window.
    if ( ptMouse.x >= rcWindow.left && ptMouse.x < rcWindow.left + LEFTEXTENDWIDTH)
    {
        uCol = 0; //left side
    }
    else if ( ptMouse.x < rcWindow.right && ptMouse.x >= rcWindow.right - RIGHTEXTENDWIDTH)
    {
        uCol = 2; //right side
    }

    // Hit test (HTTOPLEFT, ... HTBOTTOMRIGHT)
    LRESULT hitTests[3][3] = 
    {
        { HTTOPLEFT,    fOnResizeBorder ? HTTOP : HTCAPTION,    HTTOPRIGHT    },
        { HTLEFT,       HTNOWHERE,     HTRIGHT       },
        { HTBOTTOMLEFT, HTBOTTOM, HTBOTTOMRIGHT },
    };

    return hitTests[uRow][uCol];
}

 

posted @ 2009-12-09 18:17  vcommon  阅读(4876)  评论(0编辑  收藏