Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why am I getting WM_MOUSEACTIVATE when I click on my child window? I have it change focus to the parent, which screws child kill focus logic

Tags:

c

focus

winapi

I have a custom window class intended as an editor view, and since I'm handling keyboard events directly, I want to have a standard edit control on top for entering text. My problem is simple: I also want to grab focus when the user clicks on the custom window class, so I handle WM_MOUSEACTIVATE:

    case WM_MOUSEACTIVATE:
        SetFocus(hwnd);
        return MA_ACTIVATE;

But I also want the edit control to disappear when it loses focus:

    case WM_COMMAND:
        if (HIWORD(wparam) == EN_KILLFOCUS) {
            MessageBeep(-1);
            ShowWindow(edit, SW_HIDE);
            return 0;
        }
        return DefWindowProc(hwnd, msg, wparam, lparam);

Except that for some reason, the WM_MOUSEACTIVATE handler fires when I click on the (visible) edit control; this causes it to lose and regain focus, but not before I hide the edit control!

The complete test program is below; if you click anywhere in the window's client area, you'll see the edit control. Click it once to give it focus, then click it again. You should hear a beep, followed by the edit control disappearing. Common Controls 6 required.

This also happens if I subclass the edit control and handle WM_KILLFOCUS there. (This was what I originally had.)

What's going on? Thanks.

// 22-23 august 2014
// edited from wineditoverlaytest 22 august 2014
// scratch Windows program by pietro gagliardi 17 april 2014
// fixed typos and added toWideString() 1 may 2014
// borrows code from the scratch GTK+ program (16-17 april 2014) and from code written 31 march 2014 and 11-12 april 2014
#define _UNICODE
#define UNICODE
#define STRICT
#define _GNU_SOURCE     // needed to declare asprintf()/vasprintf()
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <windows.h>
#include <commctrl.h>       // needed for InitCommonControlsEx() (thanks Xeek in irc.freenode.net/#winapi for confirming)
#include <windowsx.h>

#ifdef  _MSC_VER
#error sorry! the scratch windows program relies on mingw-only functionality! (specifically: asprintf())
#endif

HMODULE hInstance;
HICON hDefaultIcon;
HCURSOR hDefaultCursor;
HFONT controlfont;

void panic(char *fmt, ...);

HWND area;
HWND edit;

LRESULT CALLBACK wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    RECT r;

    switch (msg) {
    case WM_SIZE:
        GetClientRect(hwnd, &r);
        MoveWindow(area, r.left, r.top, r.right - r.left, r.bottom - r.top, TRUE);
        return 0;
    case WM_CLOSE:
        PostQuitMessage(0);
        return 0;
    default:
        return DefWindowProc(hwnd, msg, wparam, lparam);
    }
    panic("oops: message %ud does not return anything; bug in wndproc()", msg);
}

LRESULT CALLBACK areawndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    switch (msg) {
    case WM_MOUSEACTIVATE:
        SetFocus(hwnd);
        return MA_ACTIVATE;
    case WM_COMMAND:
        if (HIWORD(wparam) == EN_KILLFOCUS) {
            MessageBeep(-1);
            ShowWindow(edit, SW_HIDE);
            return 0;
        }
        return DefWindowProc(hwnd, msg, wparam, lparam);
    case WM_LBUTTONUP:
        MoveWindow(edit, GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam), 100, 20, TRUE);
        ShowWindow(edit, SW_SHOW);
        return 0;
    default:
        return DefWindowProc(hwnd, msg, wparam, lparam);
    }
    panic("oops: message %ud does not return anything; bug in wndproc()", msg);
}

HWND makeMainWindow(void)
{
    WNDCLASS cls;
    HWND hwnd;

    ZeroMemory(&cls, sizeof (WNDCLASS));
    cls.lpszClassName = L"mainwin";
    cls.lpfnWndProc = wndproc;
    cls.hInstance = hInstance;
    cls.hIcon = hDefaultIcon;
    cls.hCursor = hDefaultCursor;
    cls.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
    if (RegisterClass(&cls) == 0)
        panic("error registering window class");
    hwnd = CreateWindowEx(0,
        L"mainwin", L"Main Window",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL, hInstance, NULL);
    if (hwnd == NULL)
        panic("opening main window failed");
    return hwnd;
}

void buildUI(HWND mainwin)
{
#define CSTYLE (WS_CHILD | WS_VISIBLE)
#define CXSTYLE (0)
#define SETFONT(hwnd) SendMessage(hwnd, WM_SETFONT, (WPARAM) controlfont, (LPARAM) TRUE);
    // build GUI here; use CSTYLE and CXSTYLE in CreateWindowEx() and call SETFONT() on each new widget

    WNDCLASS cls;

    ZeroMemory(&cls, sizeof (WNDCLASS));
    cls.lpszClassName = L"area";
    cls.lpfnWndProc = areawndproc;
    cls.hInstance = hInstance;
    cls.hIcon = hDefaultIcon;
    cls.hCursor = hDefaultCursor;
    cls.hbrBackground = (HBRUSH) (COLOR_GRADIENTACTIVECAPTION + 1);
    if (RegisterClass(&cls) == 0)
        panic("error registering area window class");
    area = CreateWindowEx(0,
        L"area", L"",
        WS_CHILD | WS_VISIBLE,
        CW_USEDEFAULT, CW_USEDEFAULT,
        CW_USEDEFAULT, CW_USEDEFAULT,
        mainwin, NULL, hInstance, NULL);
    if (area == NULL)
        panic("opening main window failed");

    edit = CreateWindowEx(WS_EX_CLIENTEDGE,
        L"edit", L"",
        WS_CHILD | ES_AUTOHSCROLL | ES_LEFT | ES_NOHIDESEL | WS_TABSTOP,
        0, 0, 0, 0,
        area, NULL, hInstance, NULL);
    if (edit == NULL)
        panic("edit creation failed");
    SETFONT(edit);
}

void firstShowWindow(HWND hwnd);
void initwin(void);

int main(int argc, char *argv[])
{
    HWND mainwin;
    MSG msg;

    initwin();

    mainwin = makeMainWindow();
    buildUI(mainwin);
    firstShowWindow(mainwin);

    for (;;) {
        BOOL gmret;

        gmret = GetMessage(&msg, NULL, 0, 0);
        if (gmret == -1)
            panic("error getting message");
        if (gmret == 0)
            break;
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return 0;
}

DWORD iccFlags =
//  ICC_ANIMATE_CLASS |         // animation control
//  ICC_BAR_CLASSES |               // toolbar, statusbar, trackbar, tooltip
//  ICC_COOL_CLASSES |          // rebar
//  ICC_DATE_CLASSES |          // date and time picker
//  ICC_HOTKEY_CLASS |          // hot key
//  ICC_INTERNET_CLASSES |      // IP address entry field
//  ICC_LINK_CLASS |                // hyperlink
//  ICC_LISTVIEW_CLASSES |          // list-view, header
//  ICC_NATIVEFNTCTL_CLASS |        // native font
//  ICC_PAGESCROLLER_CLASS |        // pager
//  ICC_PROGRESS_CLASS |            // progress bar
    ICC_STANDARD_CLASSES |      // "one of the intrinsic User32 control classes"
//  ICC_TAB_CLASSES |               // tab, tooltip
//  ICC_TREEVIEW_CLASSES |      // tree-view, tooltip
//  ICC_UPDOWN_CLASS |          // up-down
//  ICC_USEREX_CLASSES |            // ComboBoxEx
//  ICC_WIN95_CLASSES |         // some of the above
    0;

void initwin(void)
{
    INITCOMMONCONTROLSEX icc;
    NONCLIENTMETRICS ncm;

    hInstance = GetModuleHandle(NULL);
    if (hInstance == NULL)
        panic("error getting hInstance");
    hDefaultIcon = LoadIcon(NULL, MAKEINTRESOURCE(IDI_APPLICATION));
    if (hDefaultIcon == NULL)
        panic("error getting default window class icon");
    hDefaultCursor = LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW));
    if (hDefaultCursor == NULL)
        panic("error getting default window cursor");
    icc.dwSize = sizeof (INITCOMMONCONTROLSEX);
    icc.dwICC = iccFlags;
    if (InitCommonControlsEx(&icc) == FALSE)
        panic("error initializing Common Controls");
    ncm.cbSize = sizeof (NONCLIENTMETRICS);
    if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS,
        sizeof (NONCLIENTMETRICS), &ncm, 0) == 0)
        panic("error getting non-client metrics for getting control font");
    controlfont = CreateFontIndirect(&ncm.lfMessageFont);
    if (controlfont == NULL)
        panic("error getting control font");
}

void panic(char *fmt, ...)
{
    char *msg;
    TCHAR *lerrmsg;
    char *fullmsg;
    va_list arg;
    DWORD lasterr;
    DWORD lerrsuccess;

    lasterr = GetLastError();
    va_start(arg, fmt);
    if (vasprintf(&msg, fmt, arg) == -1) {
        fprintf(stderr, "critical error: vasprintf() failed in panic() preparing panic message; fmt = \"%s\"\n", fmt);
        abort();
    }
    // according to http://msdn.microsoft.com/en-us/library/windows/desktop/ms680582%28v=vs.85%29.aspx
    lerrsuccess = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL, lasterr,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR) &lerrmsg, 0, NULL);
    if (lerrsuccess == 0) {
        fprintf(stderr, "critical error: FormatMessage() failed in panic() preparing GetLastError() string; panic message = \"%s\", last error in panic(): %ld, last error from FormatMessage(): %ld\n", msg, lasterr, GetLastError());
        abort();
    }
    // note to self: use %ws instead of %S (thanks jon_y in irc.oftc.net/#mingw-w64)
    if (asprintf(&fullmsg, "panic: %s\nlast error: %ws\n", msg, lerrmsg) == -1) {
        fprintf(stderr, "critical error: asprintf() failed in panic() preparing full report; panic message = \"%s\", last error message: \"%ws\"\n", msg, lerrmsg);
        abort();
    }
    fprintf(stderr, "%s\n", fullmsg);
    va_end(arg);
    exit(1);
}

void firstShowWindow(HWND hwnd)
{
    // we need to get nCmdShow
    int nCmdShow;
    STARTUPINFO si;

    nCmdShow = SW_SHOWDEFAULT;
    GetStartupInfo(&si);
    if ((si.dwFlags & STARTF_USESHOWWINDOW) != 0)
        nCmdShow = si.wShowWindow;
    ShowWindow(hwnd, nCmdShow);
    if (UpdateWindow(hwnd) == 0)
        panic("UpdateWindow(hwnd) failed in first show");
}
like image 803
andlabs Avatar asked Oct 20 '25 05:10

andlabs


1 Answers

From the docs for WM_MOUSEACTIVATE:

The parent window receives this message only if the child window passes it to the DefWindowProc function

So I would say the edit control is doing this - it's passing WM_MOUSEACTIVATE to DefWindowProc, which then passes the message to the parent window.

If you really want to use this method, you would need to subclass the edit control to stop it passing the message through to DefWindowProc.

Or, change your approach, and use WM_LBUTTONDOWN instead.

like image 107
Jonathan Potter Avatar answered Oct 21 '25 19:10

Jonathan Potter



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!