如何创建一个 Win32 控件来包含其他 Win32 控件?

Posted

技术标签:

【中文标题】如何创建一个 Win32 控件来包含其他 Win32 控件?【英文标题】:How do I create a Win32 control to enclose other Win32 controls? 【发布时间】:2013-02-04 22:56:42 【问题描述】:

安装程序:多年前,我们开发了一个不错的 C++ 跨平台,它管理了许多在 Mac OS X Windows 之间编写通用代码源的问题。 (我们不会讨论这种方法的巨大缺点——我们在 1993 年开发了它!)。

为了简化可重用组件的开发,我们最近添加了一个“窗格”概念以包含多个控件和用户项,主要处理绘图和其他事件(例如击键和鼠标点击)的分层性质。

我们成功地在 Mac OS X(Carbon)端构建了这种方法。然而,试图将这种方法转移到 Windows(XP SP3 及更高版本)导致了无数混乱的问题:不停地重新绘制窗口内容,以及没有将事件传递到我们的“窗格”中。

在 Windows 上,每个 Pane 都转换为一个“窗口”,我怀疑这可能是问题的根源:在封闭项目“下方”重叠项目可能会干扰绘图和事件传播。

是否存在以编程方式将控件添加到分组层次结构中的公认方法?或者是否必须设置特定的 FLAGS 才能完成此操作?

(注意:虽然我们目前与 XP SP3 兼容,但我们不需要这样做——我们可以将我们的最低操作系统定位为 Windows 7。我们目前正在使用 VS 2010 进行开发)

斯蒂芬

【问题讨论】:

你说的是遏制,然后你说的是重叠。控件是窗格的子控件吗? 为窗格设置WS_CLIPCHILDREN 样式应该可以满足您的需求。如果不是,那么您需要更详细地解释您要达到的目标。 “重叠”是指窗口可能包含 1 到(可能)10 个“窗格”。窗格内部可以是控件——甚至是其他窗格。窗格有自己的“边界”,旨在“包围”我希望与它们关联的所有子项。所以 - 一个窗口可能有一个窗格,它的大小是其父窗口的整个内容的大小。通过重叠,我是说根据定义,任何控件或子窗格都位于其父窗格的“下方”。因此,单击该控件可能会变成单击父窗格,而不是子控件。 您可以使用SS_SIMPLESS_OWNERDRAW 样式创建STATIC 控件。它可以“包含”其他控件,但单击这些控件不会转到父级,而是转到每个窗口本身。查看:msdn.microsoft.com/en-us/library/windows/desktop/… 了解详情。 在绘图导致闪烁的情况下,有几种技术。看看msdn.microsoft.com/en-us/library/ms969905.aspx 内存后缓冲区可以用于绘图,所有绘图都应该在 WM_PAINT 内(尤其是在不使用内存 DC 的情况下)。 【参考方案1】:

它通常如何工作的简单细分。

当您创建一个包含子窗口的父窗口(以下称为“窗格”)时,闪烁通常是由于父窗口的窗口过程处理 WM_ERASEBKGND 消息,并在其所有子窗口“顶部”绘制之前指导孩子们重新画自己。

正如 Nik Bougalis 所说,在创建父窗格时,如果您使用 CS_CLIPCHILDREN 样式创建它,则 DefWindowProc() 执行的任何绘制都不会发生在任何窗格的子边界(矩形或区域)的范围内。所以子窗口或控件所占用的“屏幕空间”,完全是子控件本身的责任。

对于大多数标准 Windows 控件,这很好。这将解决闪烁的问题。

关于消息: 孩子们的窗口每个都会收到他们的 WM_MOUSEMOVE、WM_LBUTTONDOWN、WM_KEYDOWN(以及其他)消息。 Windows 将这些消息发送到具有焦点的实际窗口。

如果您希望父窗口获得这些,您将不得不做一些称为子类化子窗口(控件)的窗口过程的事情。然后在您的新 WndProc() 中,您将有一个处理程序来处理您想要捕获的消息并将它们发送到父窗格的 HWND。

这是一个在控件中嵌入控件的简单工作示例。它显示了子类化和将消息传递回上游。

右键单击蓝色“窗格”中的两个子控件(EDIT 控件或 BUTTON)中的任一个!!

#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <stdio.h>

LRESULT __stdcall WndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam );
LRESULT __stdcall FrameProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam );
LRESULT __stdcall SubClassProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam );

int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hpi, LPSTR lpcl, int ncs) 

    WNDCLASSEX wcex;
    MSG msg;
    HWND hWnd=0, hFrame=0, hEdit=0, hButton=0, hCheckBox=0;
    ATOM ca=0, caframe=0;
    RECT cr;
    HBRUSH framecolor;
int cx=0;
    char *ptr=(char *)&wcex;
    int i =0;
    for (;i<sizeof(wcex);i++) 
        ptr[i]=0;
    
    wcex.cbSize=sizeof(wcex);
    wcex.hbrBackground = (HBRUSH) COLOR_WINDOW;
    wcex.hCursor = LoadCursor(0, IDC_ARROW);
    wcex.lpfnWndProc = &WndProc;
    wcex.lpszClassName = "mywnd";
    wcex.hInstance = hInstance;
    wcex.style = CS_HREDRAW|CS_VREDRAW;

    ca = RegisterClassEx(&wcex);

    for (i=0;i<sizeof(wcex);i++) 
        ptr[i]=0;
    
    wcex.cbSize=sizeof(wcex);
    framecolor = CreateSolidBrush(0xFFA100);
    wcex.hbrBackground = (HBRUSH) framecolor;
    wcex.hCursor = LoadCursor(0, IDC_ARROW);
    wcex.lpfnWndProc = &FrameProc;
    wcex.lpszClassName = "myframe";
    wcex.hInstance = hInstance;
    wcex.style = CS_HREDRAW|CS_VREDRAW;
    caframe = RegisterClassEx(&wcex);


    hWnd = CreateWindowExA(0, (LPCSTR)ca, "My Window", WS_CLIPCHILDREN|WS_VISIBLE|WS_SYSMENU|WS_SIZEBOX, 100, 100, 500, 500, 0, 0, hInstance, 0);
    GetClientRect(hWnd, &cr);
    hFrame = CreateWindowExA(0, (LPCSTR)caframe, "", WS_VISIBLE|WS_BORDER|WS_CHILD|WS_CLIPCHILDREN, 10, 10, ((cr.right-cr.left)-20), ((cr.bottom-cr.top)-20), hWnd, (HMENU) 1, hInstance, 0);
    cx = ((cr.right-cr.left)-20)/2;
    hEdit = CreateWindowExA(0, "Edit", "Edit Control", WS_CHILD|WS_VISIBLE|WS_BORDER, 10, 10, cx, 20, hFrame, (HMENU) 2, hInstance, 0);
    hButton = CreateWindowExA(0, "Button", "Click Me!", WS_CHILD|WS_VISIBLE, cx+20, 10, 70, 20, hFrame, (HMENU) 3, hInstance, 0);

    /* Sub-Class the children */
    SetWindowLongPtr(hEdit, GWLP_USERDATA, GetWindowLongPtr(hEdit, GWLP_WNDPROC));
    SetWindowLongPtr(hButton, GWLP_USERDATA, GetWindowLongPtr(hButton, GWLP_WNDPROC));
    SetWindowLongPtr(hEdit, GWLP_WNDPROC, (LONG)&SubClassProc);
    SetWindowLongPtr(hButton, GWLP_WNDPROC, (LONG)&SubClassProc);

    if (!hWnd) 
        return -1;
    
    while ( GetMessage(&msg, 0, 0, 0) ) 
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    
    DestroyWindow(hWnd);
    DeleteObject(framecolor);
    return 0;


LRESULT __stdcall WndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) 
    RECT rc;
    switch (uMsg) 
    case WM_WINDOWPOSCHANGING:
        GetClientRect(hWnd, &rc);
        SetWindowPos(GetDlgItem(hWnd, 1), 0, 0, 0, ((rc.right-rc.left)-20), ((rc.bottom-rc.top)-20), SWP_NOZORDER|SWP_NOMOVE);
        break;
    case WM_CLOSE:
        PostQuitMessage(0);
        break;
    default:
        break;
    
    return DefWindowProc(hWnd, uMsg, wParam, lParam);
   

LRESULT __stdcall FrameProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) 
    PAINTSTRUCT ps;
    WINDOWPOS *wp=0;
    POINT p;
    short wmid=0, wmevent=0;
    char message[300];
    switch (uMsg) 
    case WM_WINDOWPOSCHANGING:
        wp = (WINDOWPOS *)lParam;
        SetWindowPos(GetDlgItem(hWnd, 2), 0, 0, 0, (wp->cx/2), 20, SWP_NOMOVE|SWP_NOZORDER);
        SetWindowPos(GetDlgItem(hWnd, 3), 0, (wp->cx/2)+20, 10, 0, 0, SWP_NOSIZE|SWP_NOZORDER);
        break;
    case WM_RBUTTONDOWN:
        p.x = (lParam & 0x0000ffff);
        p.y = (lParam >> 16 );
        sprintf(message, "The \"frame\" got a WM_RBUTTONDOWN message!\nx: %i\ny: %i\n",
            p.x, p.y);
        MessageBox(GetParent(hWnd), message, "Message", MB_ICONINFORMATION);
        break;
    case WM_COMMAND:
        wmid = (wParam & 0x0000ffff);
        wmevent = wParam>>16;
        switch (wmid) 
        case 3:
            if (wmevent==BN_CLICKED) 
                MessageBox(GetParent(hWnd), "You clicked my button!", "Notice", MB_OK);
            
            break;
        default:
            break;
        
        break;
    case WM_PAINT:
        break;
    default:
        break;
    
    return DefWindowProc(hWnd, uMsg, wParam, lParam);


LRESULT __stdcall SubClassProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) 
    WNDPROC wp=0;
    POINT p;
    HWND hParent=0;
    char message[300];
    wp = (WNDPROC)GetWindowLongPtr(hWnd, GWLP_USERDATA);
    if (!wp) 
        return DefWindowProc(hWnd, uMsg, wParam, lParam);
    
    if (uMsg==WM_RBUTTONDOWN) 
        p.x = (lParam & 0x0000ffff);
        p.y = (lParam >> 16 );
        sprintf(message, "Right-Click in control!\nx: %i\ny: %i\n\nNow, we'll convert this to the coordinates of the frame and pass the message up-stream!",
            p.x, p.y);
        hParent = GetParent(hWnd);
        MessageBox(GetParent(hParent), message, "Message", MB_ICONINFORMATION);
        ClientToScreen(hWnd, &p);
        ScreenToClient(hParent, &p);
        SendMessage(hParent, WM_RBUTTONDOWN, wParam, MAKELPARAM(p.x, p.y));
    
    return CallWindowProc( wp, hWnd, uMsg, wParam, lParam);

大多数本机 Windows 控件通过 WM_COMMAND 或 WM_NOTIFY 消息自动通知其父级。就像在编辑控件中更改文本时一样。它向其父窗口发送一条 WM_COMMAND 消息,其中包含。

A - 它的把手 B - 这是一个标识符 C - 通知代码(事件),在本例中为 EN_CHANGE。

所以你可以拦截这些消息并通过 SendMessage() 将它们转发到任何地方。

当您进入自定义绘图控件时,您需要了解以下内容:

    什么是设备上下文。 处理 WM_PAINT 消息。 可能是 WM_PRINTCLIENT 消息。 BitBlt() 可能是内存设备上下文。

内存设备上下文是您进行绘图操作的不可见位置。就像 bkausbk 说的,一个缓冲区。在程序中,我写到我也需要添加一点华丽,我绘制到内存设备上下文。然后,我在 WM_PAINT 事件(来自系统)中使用 BitBlt() 将我的所有窗口及其子窗口在其上绘制的内存设备上下文复制到要显示的窗口的设备上下文中。

PAINTSTRUCT ps;
case WM_PAINT:
    BeginPaint(hWnd, &ps);
    BitBlt(ps.hdc, 0, 0, cx, cy, hMemDC, 0, 0, SRCCOPY);
    EndPaint(hWnd &ps);

不管怎样,有很多东西要学,但希望上面的小程序可以帮到你,给你一个模板来玩玩!

【讨论】:

【参考方案2】:

可以按照这些来实施:

    在创建父控件(即窗格)时使用 WS_CLIPSIBLING|WS_CLIPCHILDREN 标志。 避免在消息循环中出现 WM_ERASEBKGND 消息并为此返回 TRUE。这取决于使用语义。 - https://www.codeproject.com/articles/2078/guide-to-win32-paint-for-intermediates 当有自定义绘图时,使用双缓冲绘图。 - http://www.winprog.org/tutorial/bitmaps.html 为了处理子窗口消息,使用 MFC 方法,即让子窗口先处理消息。这可以通过覆盖 windowproc 来完成。

【讨论】:

以上是关于如何创建一个 Win32 控件来包含其他 Win32 控件?的主要内容,如果未能解决你的问题,请参考以下文章

带有子控件的 Win32 自定义控件

如何在 vc++ win32 中制作可滚动窗口

Visual Basic 出现多个奇怪错误 - 无法设置控件的 Win32 父窗口

如何修改汇编win32中 static控件的字体颜色

c++ win32输出一个文本

获取 Win32 TreeView 控件的宽度