GetWindowRect has offset in Windows 10 - winapi

I use GetWindowRect in my program to get the coordinates of a window on the desktop. This works fine on a dozen or so PCs I have here for testing (XP-W8.1) but not on my one W10 PC with touch screen: the coordinates have an offset to the top-left. I googled both for a bug in Windows 10 and for something monitor related but cannot find anything, yet this seems like a commonly used function and the problem is easy to reproduce.
Anyone got any clues?
Code, simplified:
hwnd = FindWindow(NULL, windowname);
if (hwnd) {
TRect r;
GetWindowRect(hwnd, &r);
}
HWND hdt = GetDesktopWindow();
HDC dcdt = GetWindowDC(hdt);
// bitblt canvas to get window (won't work for high DPI setting)

Your program is not DPI aware and so is subject to DPI virtualization. That means that the coordinates returned by functions like GetWindowRect will be virtualized coordinates and so will not match the true screen coordinates.

Related

Winapi - How to achieve equivalent of LWA_COLORKEY for MS Edge browser

My goal is to pick a color that will be transparent on the Microsoft Edge browser (i.e. so we can see the windows behind where that color occurs in Edge). I was able to achieve what I want on Mathematica windows in the past with the following lines of Python code:
win32gui.SetWindowLong(hwnd,win32con.GWL_EXSTYLE,
win32gui.GetWindowLong(hwnd,win32con.GWL_EXSTYLE) | win32con.WS_EX_LAYERED)
winxpgui.SetLayeredWindowAttributes(hwnd,win32api.RGB(*transparentColor),50,win32con.LWA_COLORKEY)
where hwnd is the window's handle and transparentColor is the RGB values for the desired color. Now I'm experimenting with Jupyter and wanted to do the same to Edge since Jupyter runs from a browser, but the above is pretty much ignored on Edge (depending on the color, some parts of the menu may become transparent, but not the body of the window). My research so far leads me to believe that this is because Edge (or any other modern browser) does not use GDI and ignores the LWA_COLORKEY request. I tried using LWA_ALPHA instead of LWA_COLORKEY and that does work (makes the whole window semi-transparent), but this is not the effect I'm looking for. I get the same results if I use win32gui.SetLayeredWindowAttributes.
Is it possible in any way for an external application to achieve the intent of the code above for the Edge browser application? From Rita Han's research, in her answer and comments, it is clearly possible, since it works for her. The question is why doesn't it work on my systems (I've tried on two computers).
Updated following a request for more information:
My version of Edge was 44.18362.449.0 but I have now updated it to 81.0.416.72 with the same results. The screen capture below shows two windows, the one on the left is MS Paint where I created an image called "almostBlack_010000.png" and the one on the right is Edge displaying the same image. I have set WS_EX_LAYERED and LWA_COLORKEY on both of these windows using the code above, using color (1,0,0) as the key (i.e. very dark red). As you can see, the Paint window shows my desktop through the "black" rectangle, whereas Edge does not.
A few additional notes:
I have also tried the with Internet Explorer and Chrome with the same negative results.
The transparent area on MS Paint is click through, i.e. clicking in that area is like clicking on the desktop or any window that happens to be behind.
When I use the RGB color (0,0,0) as the key, the whole window becomes click through. This happens with Edge as well, but black pixels still do not become transparent.
The code above would throw a Python traceback if there was an error. For example, if I provide a non-existing window handle in the call to SetLayeredWindowAttributes, I get a traceback with the following last line:
error: (1400, 'SetLayeredWindowAttributes', 'Invalid window handle.')
I have tried with the c++ code below and I am getting the same results, i.e. I can get the selected color to be transparent in Paint and other applications, but not in Edge.
#include <iostream>
#include <Windows.h>
int main(int argc, char *argv[])
{
HWND hwnd;
COLORREF color;
if (argc != 3)
{
printf("Usage: %s <HWND> <color>",argv[0]);
}
else
{
hwnd = (HWND)strtoul(argv[1],NULL,16);
color = (COLORREF)strtoul(argv[2],NULL,16); // Note: order is reverse than usual. Use 0xBBGGRR
printf("Setting window %x to %x", (unsigned int)hwnd, (unsigned int)color);
if (!SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_LAYERED))
printf("SetWindowLong error: %d\n", GetLastError());
if (!SetLayeredWindowAttributes(hwnd,color,50,LWA_COLORKEY))
printf("SetLayeredWindowsAttributes error: %d\n", GetLastError());
}
}
I found out that LWA_COLORKEY and LWA_ALPHA can be ORed, in which case both the alpha and colorkey arguments of SetLayeredWindowAttributes can be used simultaneously. I can, therefore, see on the same Edge window that the overall alpha effect is visible, but that the transparency for the key color is not visible.
I follow your steps, create a black image using MS Paint and run the following code for MS Paint and Edge, both work for me. Make sure you use the right target window handle and add error check for ensuring functions execute successfully.
HWND hwnd = (HWND)0x00070948; // The window handle I found via Spy++ for testing purpose.
if (!SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_LAYERED))
printf("SetWindowLong error: %d\n", GetLastError());
if (!SetLayeredWindowAttributes(hwnd, RGB(0, 0, 0), 50, LWA_COLORKEY))
printf("SetLayeredWindowAttributes error: %d\n", GetLastError());
The black image open with MS Paint:
The black image open with Edge and turns to transparent after executing SetLayeredWindowAttributes. From transparent area you can see my desktop (blue).
I finally found a solution, which may or may not have undesirable performance side effects, but I suspect that it is the only way to make it work with the code in my question.
The problem was that Edge was using hardware acceleration by default, and the use of a GPU for rendering either makes it impossible to use my code to cause transparency, or at least makes it too difficult for me to figure out at this time.
Posts such as SetLayeredWindowAttributes to make a window transparent is only working part of the time clued me in to look at hardware acceleration.
Thanks to Rita for at least proving that it could be done, which kept me looking for why it wasn't working on my systems.
I found the way to turn off hardware acceleration in Edge at TenForums.
I would still be interested to know if there are other ways of achieving the same effect, while possibly keeping the hardware acceleration. Comment away!

Drawing docking window frames on Vista and up

I need to port some old code which implements docking windows in plain C to Vista and up. When dragging docking windows around, the old code draws the docking window's outline directly to the desktop DC by doing something like this:
hdc = GetDCEx(NULL, NULL, DCX_WINDOW|DCX_CACHE|DCX_LOCKWINDOWUPDATE);
...
PatBlt(hdc, left, top, width, height, PATINVERT);
...
ReleaseDC(NULL, hdc);
This still works on Vista and up but it is very, very slow - probably, because the DWM has to copy the desktop's frame buffer into a temporary mutable pixel buffer, do the drawing, and copy it back to the now immutable frame buffer. This is very slow.
Thus, I was wondering how such code should be ported to Vista and up. AFAIK, drawing directly to the desktop DC is no longer recommended with the introduction of the DWM in Vista so I was wondering how I should draw my docking windows' outline when the user is dragging them? What's the best way to do this?
I was thinking of abusing cursor images (or drag images or whatever Win32 has on offer here) to do what I want but I'm not sure whether this is really the best way to go. Any ideas?
Thanks!

How do I extend nonclient area into a window thinner than 16 pixels?

I'm working on a project that requires me to draw a thin line horizontally across the screen and drag it vertically. To add a bit of flair I'm trying to add the DWM glass effect to the window so that it will match window boders in Windows 7 and 8 (though I know I won't get transparency in Windows 8).
I've got the line drawn by creating a child window with the non client area extended into the client area using the sheet of glass trick with DwmExtendFrameIntoClientArea like this:
MARGINS margins = { -1, -1, -1, -1 };
DwmEnableComposition(DWM_EC_ENABLECOMPOSITION);
DwmExtendFrameIntoClientArea(DIV_HWND, &margins);
And I create the window like this:
HWND DIV_HWND = CreateWindow(DIV_NAME,
NULL,
WS_VISIBLE,
0, 0, mon_info.rcWork.right, mon_info.rcWork.top + 3,
top->hwnd, NULL,
hInstance, NULL);
And the window class like this:
const wchar_t DIV_NAME[] = L"DIV";
WNDCLASS DIV = {};
DIV.lpfnWndProc = DIV_PROC;
DIV.hInstance = hInstance;
DIV.lpszClassName = DIV_NAME;
DIV.hCursor = LoadCursor(NULL, IDC_SIZEALL);
RegisterClass(&DIV);
I've gotten the window down to a width of 16 pixels with all the nice borders and drop shadow effects I expect to see around a windows border by handling the WM_GETMINMAXINFO message and returning 2 as the ptMinTrackSize.x and y but I can't seem to get it smaller than that.
I can get a "glassy" window by using DwmEnableBlurBehindWindow on a region I set with SetWindowRgn but the color is that of a background window even when it is in focus. Additionally I miss the slight drop shadow and border.
It seems like I might be hitting the size restriction due to border sizes since each border is 8 pixels wide.
Any help is greatly appreciated!
I've found my answer so I'll leave it here on the off chance someone else needs it. All I had to do was call DwmSetWindowAttribute(hwmd, DWMWA_NCRENDERING_POLICY, DWMNCRP_ENABLED, sizeof(int)); to ensure the DWM rendering policy was not based on the window style. This allowed me to use a WS_POPUP window to get the size I wanted with a nicely glass filled interior.
There are a few bugs here:
There is a strange shadow along the top and bottom if the Y dimension of the window is smaller than 16px.
On Vista\7 the rounded window edges give the window a strange oval-like shape.

SetWindowPos and multiple monitors with different resolutions

I have two monitors that are running at different resolutions. Left monitor is 1920x1200. Right monitor (the primary monitor) is 1920x1080.
I want to use SetWindowPos to make a window take up the full vertical height of the left hand monitor.
Here's what I do:
x = GetSystemMetrics(SM_XVIRTUALSCREEN);
hMonitor = monitorFromPoint(x, 0, MONITOR_DEFAULTTONEAREST);
MONITORINFO moninfo;
moninfo.cbSize = sizeof(MONITORINFO);
GetMonitorInfo(hMonitor, moninfo);
height = moninfo.rcWork.bottom - moninfo.rcWork.top;
SetWindowPos(hwnd, 0, moninfo.rcWork.left, moninfo.rcWord.top, width, height, SWP_NOZORDER | SWP_NOACTIVATE);
I have confirmed that height is computing to 1200 (expected b/c that is the target monitor's vertical resolution).
However, after the call to SetWindowPos, the window rectangle doesn't fill the entire height of the screen (it is actually 1080 high).
I even tried this in VBA just for giggles:
Public Sub testSWP()
Dim hwnd As Long
hwnd = &H1D2F2C
SetWindowPos &H1D2F2C, 0, -1900, 0, 150, 1200, SWP_NOZORDER Or SWP_NOACTIVATE
Dim r As RECT
GetWindowRect hwnd, r
' at this point, r.bottom = 1080
End Sub
This is well and good (GetWindowRect documentation says coordinates will be in Client space, and I'm assuming that win32 is translating between the resolution of my primary and secondary monitor.
I'm getting ready to inflate the vertical dimension by the ratio of the heights of the target and primary monitor. I'm pretty sure this is going to work, but it seems like a lot of hoops to have to jump through - am I maybe just not aware of a better way of determining the screen dimensions in 'client coordinates'?
The issue isn't with coordinate transformation. It is that windows isn't allowing SetWindowPos to adjust the window so it is larger than the screen. Of course, it is basing this on the primary monitor size.
See: Can a window be resized past the screen size/offscreen?
Do you want a normal window (with titlebar etc) or do you want a fullscreen window (like youtube fullscreen video playback or games).
I think you want the latter i.e. make a fullscreen window which covers the entire screen. For that, in the call to CreateWindow, pass WS_POPUP as the window style (see dwStyle param). This will create the window without the titlebar and it will cover the entire screen.
Also, I don't think the way you're getting the left monitor is correct. You should be using EnumMonitors to iterate through all the monitors, get the left most monitor and then use GetMonitorInfo to retrieve the monitor's rects if you want to make this a generic application.

How can I extract text information from an owner-drawn window, given a HWND?

I'm writing a simple automated test application for Win32. It runs as a separate process and accesses the target application via the Windows API. I can read window hierarchies, find labels and textboxes, and click buttons by sending/posting messages etc. All fine.
Unfortunately many controls in the target application are composed of nothing more than an owner-drawn control/window. (For example, we use the BCG menus and controlbars). Finding the correct part of the control to send a 'click' to is problematic.
Is there any way, given a HWND, to extract GDI drawing commands? I'd like to know each piece of text drawn to that control, and its coordinates.
Failing that, is there any way to capture a single control/window (again by HWND) into a bitmap? Worse-case scenario, I could OCR it.
You can use TextGRAB SDK to do this. Unfortunately it is shareware and costs $30.
To capture a window as a bitmap:
RECT rc;
GetClientRect(hWnd, &rc);
int cx = rc.right-rc.left;
int cy = rc.bottom-rc.top;
HDC winDC = ::GetDC(hWnd);
HDC tempDC = ::CreateCompatibleDC(winDC);
HBITMAP newBMP = ::CreateCompatibleBitmap(winDC, cx, cy);
HBITMAP oldBmp = (HBITMAP)::SelectObject(tempDC, newBMP);
BitBlt(tempDC,0,0,cx,cy, winDC,0,0,SRCCOPY|CAPTUREBLT);
// now you have the window content in the newBMP bitmap, do with it as you please here
::SelectObject(tempDC, oldBmp);
::DeleteObject(newBMP);
::DeleteDC(tempDC);
::ReleaseDC(hWnd, winDC);

Resources