0
votes

Can someone knows which handle to use (instead of m_hWnd) to match the active window handle reported by the hook callback?

I thought the m_hWnd of my MFC dialog based application would match the active handle window reported by the hook callback.

Below a small example (MFC dialog based application) displaying its main window handle in the title bar (m_hWnd) and the the active window handle in a CEdit control.

enter image description here

I set a hook to detect when the active window changes

h_event_hook = SetWinEventHook(EVENT_OBJECT_FOCUS, EVENT_OBJECT_FOCUS, NULL, 
                               &window_change_hook, 0, 0, WINEVENT_OUTOFCONTEXT);
void CALLBACK window_change_hook(HWINEVENTHOOK hWinEventHook, DWORD event, 
                                 HWND hwnd, LONG idObject, LONG idChild, 
                                 DWORD dwEventThread, DWORD dwmsEventTime)
{
    ::PostMessage(h_this_app_wnd, WM_UPDATE_ACTIVE_WINDOW_CEDIT, (WPARAM)0, (LPARAM)hwnd);
}

The dialog title displays its handle as follow:

CString hwnd_text;
hwnd_text.Format("MonitorActiveWindow (%p)", m_hWnd);
SetWindowText(hwnd_text);

Below the MWE (minimal working example) that reproduce the problem:

#include "stdafx.h"
#include "MonitorActiveWindow.h"
#include "MonitorActiveWindowDlg.h"
#include "afxdialogex.h"
#include <string>

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

#define WM_UPDATE_ACTIVE_WINDOW_CEDIT           WM_APP + 0x1001

static HWND h_this_app_wnd;
static HWINEVENTHOOK h_event_hook;

void CALLBACK window_change_hook(HWINEVENTHOOK hWinEventHook, DWORD event, 
                                 HWND hwnd, LONG idObject, LONG idChild, 
                                 DWORD dwEventThread, DWORD dwmsEventTime)
{
    ::PostMessage(h_this_app_wnd, WM_UPDATE_ACTIVE_WINDOW_CEDIT, (WPARAM)0, (LPARAM)hwnd);
}

LRESULT CMonitorActiveWindowDlg::OnUpdateActiveWindowCedit(WPARAM w_param, LPARAM l_param)
{
    const auto h_wnd = reinterpret_cast<HWND>(l_param);
    CString text;
    text.Format("%p", h_wnd);
    GetDlgItem(IDC_EDIT1)->SetWindowText(text);
    return 0;
}

BOOL CMonitorActiveWindowDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();

    // Set the icon for this dialog.  The framework does this automatically
    //  when the application's main window is not a dialog
    SetIcon(m_hIcon, TRUE);         // Set big icon
    SetIcon(m_hIcon, FALSE);        // Set small icon

    // TODO: Add extra initialization here

    // Display the main window handle
    CString hwnd_text;
    hwnd_text.Format("MonitorActiveWindow (%p)", m_hWnd);
    SetWindowText(hwnd_text);

    // Keep a copy of m_hWnd just so the callback can call PostMessage
    h_this_app_wnd = m_hWnd;

    // Set a hook to detect when the active window changes
    h_event_hook = SetWinEventHook(EVENT_OBJECT_FOCUS, EVENT_OBJECT_FOCUS, NULL, 
                                   &window_change_hook, 0, 0, WINEVENT_OUTOFCONTEXT);

    return TRUE;  // return TRUE  unless you set the focus to a control
}

void CMonitorActiveWindowDlg::OnDestroy()
{
    CDialogEx::OnDestroy();
    UnhookWinEvent(h_event_hook);
}

CMonitorActiveWindowDlg::CMonitorActiveWindowDlg(CWnd* pParent /*=NULL*/)
    : CDialogEx(IDD_MONITORACTIVEWINDOW_DIALOG, pParent)
{
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CMonitorActiveWindowDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_EDIT1, m_output_cedit);
}

BEGIN_MESSAGE_MAP(CMonitorActiveWindowDlg, CDialogEx)
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_WM_DESTROY()
    ON_MESSAGE(WM_UPDATE_ACTIVE_WINDOW_CEDIT, OnUpdateActiveWindowCedit)
END_MESSAGE_MAP()

void CMonitorActiveWindowDlg::OnPaint()
{
    if (IsIconic())
    {
        CPaintDC dc(this); // device context for painting

        SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

        // Center icon in client rectangle
        int cxIcon = GetSystemMetrics(SM_CXICON);
        int cyIcon = GetSystemMetrics(SM_CYICON);
        CRect rect;
        GetClientRect(&rect);
        int x = (rect.Width() - cxIcon + 1) / 2;
        int y = (rect.Height() - cyIcon + 1) / 2;

        // Draw the icon
        dc.DrawIcon(x, y, m_hIcon);
    }
    else
    {
        CDialogEx::OnPaint();
    }
}

HCURSOR CMonitorActiveWindowDlg::OnQueryDragIcon()
{
    return static_cast<HCURSOR>(m_hIcon);
}
2
MFC creates not only a App Window, but inside it a MainFrm window, and inside that one or more ChildFrm, and in each one your CView-derived class has a Window that encompasses the client-draw space.Aganju
What problem are you trying to solve? Your question merely explains some expectations you have, without even succeeding at that. Since HWNDs uniquely identify windows, two windows cannot have the same window handle, obviously. It is unclear, why you would expect that, or what problem you are really trying to solve.IInspectable
I am writing a servers allowing clients to send message to the top instance of the specified application.Less White

2 Answers

2
votes

The edit control appears to be read-only edit control. This control get default focus, and the window handle for this edit control is reported. You can change the edit control style to "Disabled" so that it doesn't steal focus from parent dialog.

If you are not interested in focus window, you can look for foreground window EVENT_SYSTEM_FOREGROUND, example

SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, 
        NULL, &window_change_hook, 0, 0, WINEVENT_OUTOFCONTEXT);
...

void CALLBACK window_change_hook(HWINEVENTHOOK, DWORD, HWND hwnd, LONG, LONG, DWORD, DWORD)
{
    CString str;
    GetWindowText(GetAncestor(hwnd, GA_ROOT), str.GetBuffer(100), 100);
    str.ReleaseBuffer();
    ::SetDlgItemText(h_this_app_wnd, IDC_EDIT1, str);
}
1
votes

Just if someone is interested, below the solution I will use for my server to be able to send a message to the instance of the specified application that is on the top. It consists to modify the callback to retrieve the main window handle from the hwnd received.

HWND get_real_parent(HWND h_wnd)
{
    const auto h_parent = GetAncestor(h_wnd, GA_PARENT);
    if (!h_parent || h_parent == GetDesktopWindow())
        return nullptr;

    return h_parent;
}

HWND get_main_window_handle(HWND h_wnd)
{
    auto h_main_window = h_wnd;
    auto h_parent_window = h_wnd;
    while (h_parent_window != nullptr)
    {
        h_parent_window = get_real_parent(h_parent_window);
        if (h_parent_window != nullptr)
        {
            h_main_window = h_parent_window;
        }
    }
    return h_main_window;
}

void CALLBACK window_change_hook(HWINEVENTHOOK hWinEventHook, DWORD event, 
                                 HWND hwnd, LONG idObject, LONG idChild, 
                                 DWORD dwEventThread, DWORD dwmsEventTime)
{  
    hwnd = get_main_window_handle(hwnd);
    ::PostMessage(h_this_app_wnd, WM_UPDATE_ACTIVE_WINDOW_CEDIT, (WPARAM)0, (LPARAM)hwnd);
}