Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I create a Win32 control to enclose other Win32 controls?

The Setup: years ago, we developed a nice C++ cross-platform that managed many of the issues of writing common-code source between Mac OS X Windows. (we won't into the massive downsides of that approach — we developed this in 1993!).

In order to simplify development of reusable components, we recently added a "pane" concept to contain multiple controls and user items, essentially handling the hierarchical nature of drawing and other events, such as keystrokes and mouse clicks.

We successfully architected this approach on the Mac OS X (Carbon) side. However, attempting to move that approach over to Windows (XP SP3 and beyond) has resulted in a myriad of messy problems: non-stop re-drawing of window contents, and events not being passed down into our "panes".

On Windows, each Pane translates into a "Window", and I suspect that this may be the root of the problem: overlapping the items "under" an enclosing item may be interfering with drawing and event propagation.

Is there an accepted method of programmatically adding controls into a grouped hierarchy? Or are there specific FLAGS that must be set to accomplish this?

(NOTE: although we are presently compatible with XP SP3, we don't need to be — we could target our minimum OS as Windows 7. We are presently developing with VS 2010)

Stephen

like image 454
SMGreenfield Avatar asked Feb 04 '13 22:02

SMGreenfield


People also ask

What is a static control?

A static control is a control that enables an application to provide the user with informational text and graphics that typically require no response.

Is a single line edit control?

The single-line edit control has the ES_PASSWORD style. By default, edit controls with this style display an asterisk for each character that is typed by the user. This example, however, uses the EM_SETPASSWORDCHAR message to change the default character from an asterisk to a plus sign (+).

What are edit controls?

An edit control is a rectangular control window typically used in a dialog box to enable the user to enter and edit text. Edit Control Text Operations. The system automatically processes all user-initiated text operations and notifies the application when the operations are completed.

Is Win32 a window?

Win32 is the 32-bit application programming interface (API) for versions of Windows from 95 onwards. The API consists of functions implemented, as with Win16, in system DLLs. The core DLLs of Win32 are kernel32. dll, user32.


1 Answers

A simple breakdown of how it usually works.

When you create a parent window (hereafter called "pane") that contains children, the flickering is usually caused by the window procedure of the parent window processing the WM_ERASEBKGND message, and drawing "on top" of all of its children before instructing the children to redraw themselves.

As Nik Bougalis stated, when creating the parent pane, if you create it with the CS_CLIPCHILDREN style, no drawing performed by the DefWindowProc() will occur within the bounds of any of the pane's children's' bounds (rects or regions). So the "screen-space" occupied by the child window or control, is completely the responsibility of the child control itself.

For most standard Windows controls, this is fine. That will solve the flickering problem.

As for messages: The children's windows each get their WM_MOUSEMOVE, WM_LBUTTONDOWN, WM_KEYDOWN (among others) messages. Windows sends those messages to the actual window with focus.

If you want the parent window to get these, you'll have to do something called sub-classing the window procedures of the children's windows (controls). Then in your new WndProc(), you'll have a handler for the messages you want to catch and send them to the parent pane's HWND.

Here is a simple working example of embedding control within controls. It shows sub-classing and passing messages back up-stream.

RIGHT-CLICK either of the two children, the EDIT control or the BUTTON, in the blue "pane"!!

#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);
}

Most native windows control automatically notify their parents via WM_COMMAND or WM_NOTIFY messages. Like, when the text is changed in an edit control. It sends a WM_COMMAND message to its parent window containing.

  • A - Its handle
  • B - It's an identifier
  • C - The notification code (event), in this case, EN_CHANGE.

So you can intercept those messages and forward them via SendMessage() to wherever.

When you get into custom-drawing controls, you will need to know about the following:

  1. What a Device Context is.
  2. Handling WM_PAINT messages.
  3. Perhaps WM_PRINTCLIENT messages.
  4. BitBlt()
  5. Perhaps Memory Device Contexts.

A Memory device context is an invisible place for you to do your drawing operations. Like bkausbk said, a buffer. In programs, I write that I need to add a little panache too, I draw to a memory device context. I then use BitBlt() in a WM_PAINT event (from the system) to copy the memory device context on which all my windows and their children have drawn onto the device context of the window to be displayed.

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

Anyways, there's a lot to learn about it, but I hope that the small program above can help you and give you a template for playing around with it!

like image 165
Justin Jack Avatar answered Oct 03 '22 00:10

Justin Jack