7

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

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

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

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

是否有一种可接受的方法以编程方式将控件添加到分组层次结构中?或者是否必须设置特定的 FLAGS 才能完成此操作?

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

斯蒂芬

4

2 回答 2

3

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

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

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

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

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

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

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

右键单击蓝色“窗格”中的两个孩子中的任何一个,编辑控件或按钮!

#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() 将它们转发到任何地方。

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

  1. 什么是设备上下文。
  2. 处理 WM_PAINT 消息。
  3. 也许是 WM_PRINTCLIENT 消息。
  4. BitBlt()
  5. 也许是内存设备上下文。

内存设备上下文是您进行绘图操作的不可见位置。就像 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);

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

于 2013-09-13T09:36:23.223 回答
1

可以遵循这些来实施:

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