Reputation: 589
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.
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);
}
Upvotes: 0
Views: 855
Reputation: 31599
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);
}
Upvotes: 2
Reputation: 589
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);
}
Upvotes: 1