如何使用纯 WINAPI 在渐变背景上显示控件?

Posted

技术标签:

【中文标题】如何使用纯 WINAPI 在渐变背景上显示控件?【英文标题】:How do I show controls on top of a gradient background with pure WINAPI? 【发布时间】:2020-11-26 02:53:25 【问题描述】:

在我的程序中,我试图创建一个子窗口(通过CreateWindow("STATIC", ...))来包含一些其他控件,例如编辑框和按钮。但是,我希望这个静态控件的背景是渐变的。

我的目标是让子窗口看起来像这样:

到目前为止,我的努力已经使创建的窗口具有可见的控件,但一旦使用WM_ERASEBKGND 重新绘制,控件就会隐藏在绘制的渐变后面。

我可以找到很多关于绘制渐变(或其他此类图形背景)以及独立创建带有控件的窗口的示例。但我还没有找到任何同时拥有两者的资源。

这是我的示例代码:

#include <windows.h>

#pragma comment( lib, "Msimg32" )   // load that dll for GradientFill

// Global macros
inline COLOR16 ToColor16(BYTE byte)  return byte << 8; 
inline COLOR16 RVal16(COLORREF color)  return ToColor16(GetRValue(color)); 
inline COLOR16 GVal16(COLORREF color)  return ToColor16(GetGValue(color)); 
inline COLOR16 BVal16(COLORREF color)  return ToColor16(GetBValue(color)); 

// Global variables
HINSTANCE hInst;

// Forward declarations
LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

// Entry point
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow)

    // Register the window class.
    const TCHAR CLASS_NAME[] = "mcvewinapi";

    WNDCLASS wc =  ;

    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = CLASS_NAME;
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.hCursor = LoadCursor(nullptr, IDC_ARROW);

    RegisterClass(&wc);

    // Create the window.
    HWND hwnd = CreateWindowEx(
        0,                              // Optional window styles.
        CLASS_NAME,                     // Window class
        "MCVE WINAPI",                  // Window text
        WS_OVERLAPPEDWINDOW,            // Window style

        // Size and position
        300,                    //horizontal position
        50,                     //vertical position
        700,                    //width
        500,                    //height

        NULL,       // Parent window    
        NULL,       // Menu
        hInstance,  // Instance handle
        NULL        // Additional application data
    );

    hInst = hInstance;

    if (hwnd == NULL) 
        return 0;
    

    ShowWindow(hwnd, nCmdShow);

    // Run the message loop.
    MSG msg =  ;
    while (GetMessage(&msg, NULL, 0, 0))
    
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    

    return 0;


// vill geven area with vertical gradient
void VerticalGradient(HDC hDC, const RECT &RectToFill, COLORREF rgbTop, COLORREF rgbBottom) 
    GRADIENT_RECT gradRect;
    TRIVERTEX TriVert[2];

    // references for the verticies
    gradRect.UpperLeft = 0;     // point to TriVert[0]
    gradRect.LowerRight = 1;    // point to TriVert[1]

    //setup top of gradient attributes
    TriVert[0].x = RectToFill.left - 1;
    TriVert[0].y = RectToFill.top - 1;
    TriVert[0].Red = RVal16(rgbTop);
    TriVert[0].Green = GVal16(rgbTop);
    TriVert[0].Blue = BVal16(rgbTop);
    TriVert[0].Alpha = 0x0000;

    //setup bottom of gradient attributes
    TriVert[1].x = RectToFill.right;
    TriVert[1].y = RectToFill.bottom;
    TriVert[1].Red = RVal16(rgbBottom);
    TriVert[1].Green = GVal16(rgbBottom);
    TriVert[1].Blue = BVal16(rgbBottom);
    TriVert[1].Alpha = 0x0000;

    // draw the shaded rectangle
    GradientFill(hDC, TriVert, 2, &gradRect, 1, GRADIENT_FILL_RECT_V);


// discover area to fill with gradient
void vFill(HWND ctrlhwnd) 
    HDC gBoxDC;
    HWND gBoxH;
    RECT gBoxR;
    POINT xWhere;

    // get rectangle from control
    gBoxH = ctrlhwnd;
    GetWindowRect(gBoxH, &gBoxR);

    // get DC for control
    gBoxDC = GetDC(gBoxH);

    // load up RECT from POINT
    xWhere =  gBoxR.left, gBoxR.top ;
    ScreenToClient(gBoxH, &xWhere);
    gBoxR.left = xWhere.x + 2;
    gBoxR.top = xWhere.y + 2;

    // load up RECT from POINT
    xWhere =  gBoxR.right, gBoxR.bottom ;
    ScreenToClient(gBoxH, &xWhere);
    gBoxR.right = xWhere.x - 1;
    gBoxR.bottom = xWhere.y - 1;

    //paint area
    VerticalGradient(gBoxDC, gBoxR, RGB(250, 191, 145), RGB(191, 191, 191));

    ReleaseDC(gBoxH, gBoxDC);


// Processes messages for the main window.
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 
    switch (message) 
        case WM_CREATE:
            struct wAnchor 
                int udPos;
                int lrPos;
                int width;
                int height;
            ;
            wAnchor Section1[4], TextLine;
            unsigned int LineHeight, k;
            HWND FillBox;

            LineHeight = 24;

            Section1[1].lrPos = 5;
            Section1[1].udPos = 20;
            Section1[1].width = 325;
            Section1[1].height = 360;

            TextLine.lrPos = Section1[1].lrPos + 5;
            TextLine.udPos = Section1[1].udPos + 5;
            TextLine.width = 0;
            TextLine.height = LineHeight;

            k = 1;

            FillBox = CreateWindow("STATIC",
                "",
                WS_VISIBLE | WS_CHILD | WS_BORDER | WS_EX_CLIENTEDGE,
                Section1[1].lrPos,
                Section1[1].udPos,
                Section1[1].width,
                Section1[1].height,
                hWnd,                   // child of parent
                (HMENU)8200,
                hInst,
                NULL);

            CreateWindow("STATIC",
                "Section title",
                WS_VISIBLE | WS_CHILD | WS_BORDER | SS_CENTER | WS_EX_CLIENTEDGE,
                TextLine.lrPos,
                TextLine.udPos + (TextLine.height * k++),
                TextLine.width + 315,
                TextLine.height,
                FillBox,                // child of FillBox
                (HMENU)8201,
                hInst,
                NULL);

            CreateWindow("STATIC",
                "Entry:",
                WS_VISIBLE | WS_CHILD | WS_BORDER | WS_EX_CLIENTEDGE,
                TextLine.lrPos,
                TextLine.udPos + (TextLine.height * k),
                TextLine.width + 75,
                TextLine.height,
                FillBox,                // child of FillBox
                (HMENU)8202,
                hInst,
                NULL);

            CreateWindow("EDIT", // edit bos
                "109",
                WS_VISIBLE | WS_CHILD | WS_BORDER | WS_TABSTOP | ES_MULTILINE,
                TextLine.lrPos + 75,
                TextLine.udPos + (TextLine.height * k),
                TextLine.width + 35,
                TextLine.height,
                FillBox,                // child of FillBox
                (HMENU)8203,
                hInst,
                NULL);

            CreateWindow(
                "BUTTON",       // Predefined class; Unicode assumed 
                "test",         // Button text 
                WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON,  // Styles 
                50,             // x position 
                150,            // y position 
                50,             // Button width
                30,             // Button height
                FillBox,        // child of FillBox
                (HMENU)8204,    // No menu
                hInst,
                NULL);          // Pointer not needed
            UpdateWindow(FillBox);
            break;
        case WM_PAINT: 
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            EndPaint(hWnd, &ps);
        
            break;
        case  WM_ERASEBKGND:
            vFill(GetDlgItem(hWnd, 8200)); //fill background with gradient
            return 1L;
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
    
    return 0;

最初,在我调整窗口大小之前不绘制渐变,然后渐变覆盖控件。当然,我希望根据需要重新绘制渐变,但控件保持可见。

我该如何做到这一点?

【问题讨论】:

【参考方案1】:

您可以将WS_CLIPCHILDREN 样式添加到父窗口:

FillBox = CreateWindow(TEXT("STATIC"),
    TEXT(""),
    WS_VISIBLE | WS_CHILD | WS_BORDER | WS_EX_CLIENTEDGE | WS_CLIPCHILDREN,
    Section1[1].lrPos,
    Section1[1].udPos,
    Section1[1].width,
    Section1[1].height,
    hWnd,                   // child of parent
    (HMENU)8200,
    hInst,
    NULL);

在绘制父窗口时,这将不包括其子窗口,这对我有用:

【讨论】:

【参考方案2】:

如果您要创建一个窗口来充当某些控件的容器,那么显而易见的选择通常是创建一个对话框。它已经(自动)处理了你现在正在做的大部分事情,以及一些你可能想要但还没有做的事情(比如使用标签在控件之间移动)。

因此,例如,从通用 Windows 应用程序开始,我更改了默认的 About 框以大致托管您想要的控件,然后在应用程序初始化时将其作为无模式对话框弹出。

// frame.cpp : Defines the entry point for the application.
//

#include "framework.h"
#include "frame.h"

#pragma comment( lib, "Msimg32" )   // load that dll for GradientFill


#define MAX_LOADSTRING 100

// Global Variables:
HINSTANCE hInst;                                // current instance
WCHAR szTitle[MAX_LOADSTRING];                  // The title bar text
WCHAR szWindowClass[MAX_LOADSTRING];            // the main window class name

// Forward declarations of functions included in this code module:
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)

    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // TODO: Place code here.

    // Initialize global strings
    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_FRAME, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    // Perform application initialization:
    if (!InitInstance (hInstance, nCmdShow))
    
        return FALSE;
    

    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_FRAME));

    MSG msg;

    // Main message loop:
    while (GetMessage(&msg, nullptr, 0, 0))
    
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        
    

    return (int) msg.wParam;




//
//  FUNCTION: MyRegisterClass()
//
//  PURPOSE: Registers the window class.
//
ATOM MyRegisterClass(HINSTANCE hInstance)

    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_FRAME));
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_FRAME);
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassExW(&wcex);


//
//   FUNCTION: InitInstance(HINSTANCE, int)
//
//   PURPOSE: Saves instance handle and creates main window
//
//   COMMENTS:
//
//        In this function, we save the instance handle in a global variable and
//        create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)

   hInst = hInstance; // Store instance handle in our global variable

   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

   if (!hWnd)
   
      return FALSE;
   

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

   return TRUE;


//
//  FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  PURPOSE: Processes messages for the main window.
//
//  WM_COMMAND  - process the application menu
//  WM_PAINT    - Paint the main window
//  WM_DESTROY  - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

    static HWND dialog;

    switch (message)
    
    case WM_CREATE:
        dialog = CreateDialog(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
        ShowWindow(dialog, SW_SHOW);
        break;
    case WM_COMMAND:
        
            int wmId = LOWORD(wParam);
            // Parse the menu selections:
            switch (wmId)
            
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            
        
        break;
    case WM_PAINT:
        
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: Add any drawing code that uses hdc here...
            EndPaint(hWnd, &ps);
        
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    
    return 0;


inline COLOR16 ToColor16(BYTE byte)  return byte << 8; 
inline COLOR16 RVal16(COLORREF color)  return ToColor16(GetRValue(color)); 
inline COLOR16 GVal16(COLORREF color)  return ToColor16(GetGValue(color)); 
inline COLOR16 BVal16(COLORREF color)  return ToColor16(GetBValue(color)); 

// Message handler for about box.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)

    UNREFERENCED_PARAMETER(lParam);
    switch (message)
    
    case WM_INITDIALOG:
        return (INT_PTR)TRUE;

    case WM_ERASEBKGND: 
        COLORREF rgbTop(RGB(250, 191, 145));
        COLORREF rgbBottom(RGB(191, 191, 191));

        RECT RectToFill;
        GetClientRect(hDlg, &RectToFill);

        GRADIENT_RECT gradRect;
        TRIVERTEX TriVert[2];

        // references for the verticies
        gradRect.UpperLeft = 0;     // point to TriVert[0]
        gradRect.LowerRight = 1;    // point to TriVert[1]

        //setup top of gradient attributes
        TriVert[0].x = RectToFill.left - 1;
        TriVert[0].y = RectToFill.top - 1;
        TriVert[0].Red = RVal16(rgbTop);
        TriVert[0].Green = GVal16(rgbTop);
        TriVert[0].Blue = BVal16(rgbTop);
        TriVert[0].Alpha = 0x0000;

        //setup bottom of gradient attributes
        TriVert[1].x = RectToFill.right;
        TriVert[1].y = RectToFill.bottom;
        TriVert[1].Red = RVal16(rgbBottom);
        TriVert[1].Green = GVal16(rgbBottom);
        TriVert[1].Blue = BVal16(rgbBottom);
        TriVert[1].Alpha = 0x0000;

        HDC dc = GetDC(hDlg);
        GradientFill(dc, TriVert, 2, &gradRect, 1, GRADIENT_FILL_RECT_V);
        ReleaseDC(hDlg, dc);
        return TRUE;
    

    case WM_COMMAND:
        if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
        
            EndDialog(hDlg, LOWORD(wParam));
            return (INT_PTR)TRUE;
        
        break;
    
    return (INT_PTR)FALSE;

我并没有真正完成从模式对话框到无模式对话框的转换(例如,无模式对话框通常没有 OK 按钮,也没有处理其被按下的代码,但它仍然足以理解这个想法它的外观:

【讨论】:

一个对话框会更容易,但是我的例子只是来自一个更大的项目。我的需要实际上是在选项卡控件的一页上拥有这样的容器。但感谢您的帮助。

以上是关于如何使用纯 WINAPI 在渐变背景上显示控件?的主要内容,如果未能解决你的问题,请参考以下文章

请问一下CSS3样式中如何让背景渐变与背景图片共存啊!

为了使用 winapi 显示推文,我可以选择的最佳控件是啥?

如何为 UILabel 的文本添加渐变,而不是背景?

如何让网页渐渐变暗?

使用 WinAPI 创建具有透明背景的文本标签

如何在同一个元素上组合背景图像和 CSS3 渐变?