How can I write a program that monitors another window for a change in size or position?

Raymond

A few days ago, a reader bemoaned, “There’s no way to update the position/aspect ratio of live window thumbnails unless you poll.”

Today’s Little Program monitors another window for a change in its size and position, without polling. It’s basically another variation on the basic “window monitoring” pattern. This time, instead of monitoring the title, we monitor the location (which is the combination of size, position, and shape).

#define UNICODE
#define _UNICODE
#define STRICT
#include <windows.h>
#include <stdio.h>

HWND g_hwndMonitor;

void CALLBACK WinEventProc(
  HWINEVENTHOOK hook,
  DWORD event,
  HWND hwnd,
  LONG idObject,
  LONG idChild,
  DWORD idEventThread,
  DWORD time)
{
  if (hwnd == g_hwndMonitor &&
      idObject == OBJID_WINDOW &&
      idChild == CHILDID_SELF &&
      event == EVENT_OBJECT_LOCATIONCHANGE) {
    RECT rc;
    if (GetWindowRect(g_hwndMonitor, &rc)) {
        printf("window rect is (%d,%d)-(%d,%d)\n",
            rc.left, rc.top, rc.right, rc.bottom);
    }
  }
}

int __cdecl main(int, char**)
{
  g_hwndMonitor = FindWindow(L"Awesome Program", nullptr);
  DWORD processId;
  DWORD threadId = GetWindowThreadProcessId(g_hwndMonitor, &processId);
  HWINEVENTHOOK hook = SetWinEventHook(
    EVENT_OBJECT_LOCATIONCHANGE,
    EVENT_OBJECT_LOCATIONCHANGE,
    nullptr,
    WinEventProc,
    processId,
    threadId,
    WINEVENT_OUTOFCONTEXT);
  MessageBox(nullptr, L"Press OK when bored", L"Title", MB_OK);

  UnhookWinEvent(hook);
  return 0;
}

The program starts by identifying the window it wants to monitor. This is your application’s business logic, so I’ll just fake it with a FindWindow.

We get the thread and process ID for the window and use it to register a thread-specific accessibility event hook, filtered to location changes.

In the event callback, we see if the notification is for the window we are monitoring. If so, we get the window bounds and print it. The attempt to get the window bounds might fail if the window has been destroyed, so watch out for that.¹

¹ One way to track object destruction is with, yup, another accessibility hook, this time watching for EVENT_OBJECT_DESTROY.

12 comments

Comments are closed. Login to edit/delete your existing comments

  • 紅樓鍮

    Is there a list of all the macros that I should define before including Windows.h? I knew UNICODE, WIN32_LEAN_AND_MEAN and NOMINMAX, but was not aware of STRICT.

    • Ivan K

      I was reminded of a fact about C, HANDLE, and STRICT over the holidays: there is no STRICT “struct typedef” for straight HANDLEs (probably for a reason I can’t spend time to think of right now). I wrote a little personal utility DLL that’s about 80% Win32 calls and so decided to use straight C in MSVC 2019 out of dice roll or nostalgia or whatever. I defined STRICT before winapi includes and enabled level all the warnings and code analysis rules. Even with that there are no warnings in C (any of the language standard settings) if you do, for example:

      BOOL ret = DeviceIoControl(/* _In_ HANDLE */ "Hello, World!", IOCTL_whatever, blah...);

      C++ would of course complain.

  • Peter Cooper Jr.

    At the risk of being completely off topic, did 2020 not have an end-of-year link clearance post? It’s something I look forward to each year and I’d hate to see it go. Thanks!

  • Ray

    Is it a good idea to watch every (visible) window on the system with this? I always wanted to write a small program to adjust windows opening a few pixels away from the screen border on Windows 10 (a bug I see with many apps, including cmd, apparently due to the removed window border thickness).

    • Christian Schwang

      Should you ever get somewhere with this: I’m in. This bug has been irritating me for many years.

      • Ray

        Same, I just haven’t researched a “proper” or at least good way to listen to new windows appearing on screen yet. I let you know if I (ever) set something up.

        • switchdesktopwithfade@hotmail.com

          To track window opening I think you could just use the same pattern above but with EVENT_OBJECT_CREATE on all processes and threads.

  • switchdesktopwithfade@hotmail.com

    I integrated this with my thumbnail code and now it updates great. Thanks!

  • Robin Krom

    For those who need something like this in .NET / C#, I wrote some “modern” abstractions for doing low level Win32 things in my open source project here: https://github.com/dapplo/Dapplo.Windows

    This has some most of the low level code implemented for you and is using reactive extensions, making this a bit more functional.

    For example, tracking the location could be done like this:

                // Register the events
                var winEventObservable = WinEventHook.Create(WinEvents.EVENT_OBJECT_LOCATIONCHANGE)
                    // As there is minimal information coming from the observable, we like to get details on the window
                    .Select(info => InteropWindowFactory.CreateFor(info.Handle).Fill())
                    // Only process those where the caption (title) is set
                    .Where(interopWindow => !string.IsNullOrEmpty(interopWindow?.Caption))
                    // Do something with the information
                    .Subscribe(interopWindow =>
                    {
                        // Check if the window has any information
                        if (interopWindow.Info.HasValue)
                        {
                            Debug.WriteLine($"Window title change: handle {interopWindow.Handle} - Title: {interopWindow.Caption} moved to: {interopWindow.Info.Value.Bounds}");
                        }
                    });
    

    If you are done, dispose the observable and the registration is removed for you.
    Create a ticket on github if there are any questions.

    P.S.
    Registering to WinEventProc only works if you have a message pump, for console applications this doesn’t work.
    If you need to have it in a console application, there is also a fairly simple solution for that…