Search code examples
windowswinapigdi

Creating a window-relative DeviceContext on multiple monitors


I'm working on a legacy (1999) codebase, which has an annoying bug where scrolling in some GUI elements doesn't redraw properly when the window is placed on a non-primary monitor.

As far as I can make out (not being very familiar with Windows's APIs) the problem is that the code fetches the DeviceContext to draw to using GetDC(hwnd), which from what I can make out of the docs gets a DC for the primary monitor only (but the docs aren't terribly clear, TBH).

I've managed to draw things on the screen, using basically:

RECT rect;
GetWindowRect(hwnd, &rect);
HMONITOR monitor = MonitorFromRect(&rect, MONITOR_DEFAULTTOPRIMARY);
MONITORINFOEX minfo;
minfo.cbSize = sizeof(MONITORINFOEX);
GetMonitorInfo(monitor, &minfo);
HDC = CreateDC(NULL, minfo.szDevice, NULL, NULL);

This gets things drawn to the correct monitor, but the application clearly expects something that's window-relative, because everything ends up in the top-left corner of the desktop and not in the window.

Now, my googling seems to indicate that running the paint code twice using EnumDisplayMonitors(GetDC(), &rect, PaintCallback, NULL) should do the right thing. Unfortunately, my code isn't C or C++. It's a SmallTalk image (and support ended 15 years ago or so, so complaining to the vendor is out), and I'm simply not sure if the paint handling happens in code that I have access to, or if it's deep enough in the guts of the SmallTalk that I can't get to it.

Thus my question: is it possible to create a DC that's relative to my window's client area (by tweaking the DC from CreateDC perhaps)? I realize this will probably break if the window straddles two monitors, but that's at least less broken than the current state of things.

UPDATE:

I've managed to run the rendering code twice, using EnumDisplayMonitors, but that crashes in weird ways (which is most likely a SmallTalk problem; the compiler is old and idiosyncratic, but debugging code this deep in the stack is problematic).

To answer the comments: I think the code basically wants to draw on windows, yes. The SmallTalk objects representing the various GUI elements carry around window handles that are used to create DCs with GetDC(hwnd), so that's easy enough. So it sounds like GetDC(hwnd) should get a DC that does the right thing, in this case; it could be that the ST code is caching the DCs somewhere and GetDC will return a different DC when the window is moved to a different screen (which sounds plausible from my cursory knowledge of this).


Solution

  • It looks like the problem was indeed caching of the DCs created by GetDC. I hacked the rendering code to not use the caches (more or less, the code is a bit tangled), and from a first look it looks like that did the trick.

    For posterity, in case someone googles for Visual Smalltalk Enterprise and finds this answer, what I did was to edit GraphicsTool>>ifNilHandle: to not actually check if the handle is nil, but always run the block to get a fresh handle and return true at the end. This way, fresh DCs are fetched every time.

    UPDATE:

    This fixes the immediate problem I was trying to fix, but unfortunately breaks a couple of other things (most notably WindowBuilder Pro, the GUI construction tool-kit). The problem is clearly over-eager caching of DCs somewhere in the code, but it needs to be fixed in a much more focussed manner than the sledgehammer approach above.