I've been struggling with this issue for several days and haven't found a solution despite consulting numerous documentation sources.
I'm attempting to invoke DevicePicker.show() to display the DevicePicker at my mouse cursor position. When Windows display scaling is set to 100% (DPI = USER_DEFAULT_SCREEN_DPI), it works perfectly and appears near the cursor. However, with any other scaling setting (DPI != USER_DEFAULT_SCREEN_DPI), there's a consistent offset between the displayed position and the coordinates I provide.
Following the guidance at https://learn.microsoft.com/en-us/windows/win32/learnwin32/dpi-and-device-independent-pixels, I'm converting the mouse cursor position through DPI scaling before passing it to DevicePicker.show():
void ShowPicker(HWND hwnd, Rect rect) {
DevicePicker devicePicker;
wprintf(L"Picker: X=%f Y=%f Width=%f Height=%f\n", rect.X, rect.Y, rect.Width, rect.Height);
//devicePicker.as<IInitializeWithWindow>()->Initialize(hwnd);
devicePicker.Show(rect);
}
void ShowPosition(HWND hwnd) {
POINT pt = {};
GetCursorPos(&pt);
// Show picker at cursor position
// DevicePicker expects screen coordinates in physical pixels
float scale = static_cast<float>(GetDpiForWindow(hwnd)) / USER_DEFAULT_SCREEN_DPI;
wprintf(L"scale = %f\n", scale);
Rect pickerRect;
pickerRect.X = static_cast<float>(pt.x) / scale;
pickerRect.Y = static_cast<float>(pt.y) / scale;
pickerRect.Width = 0.0;
pickerRect.Height = 0.0;
ShowPicker(hwnd, pickerRect);
}
At 100% Windows scaling (scale=1), this works correctly. However, at 200% scaling (scale=2), there's already a significant deviation from the expected position.
Here is full code:
#include "framework.h"
#include "device_picker.h"
#include <cstdio>
#include <stdexcept>
#include <shobjidl_core.h>
// NuGet CppWinRT 2.0.250303.1
#include <winrt/base.h>
#include <winrt/Windows.Devices.Enumeration.h>
#include <winrt/Windows.Media.Audio.h>
using namespace winrt;
using namespace winrt::Windows::Devices::Enumeration;
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::Media::Audio;
void RegisterClass(HINSTANCE hInstance, PCWSTR class_name);
HWND CreateMainWindow(HINSTANCE hInstance, PCWSTR class_name);
void MessageLoop();
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// Create console for debug output
AllocConsole();
FILE* file;
freopen_s(&file, "CONOUT$", "w", stdout);
freopen_s(&file, "CONOUT$", "w", stderr);
wprintf(L"Console initialized.\n");
// Initialize random seed
srand(static_cast<unsigned int>(time(nullptr)));
// Set DPI awareness
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
// Initialize WinRT
init_apartment();
LPCWSTR windowClass = L"demo_window_class";
try
{
RegisterClass(hInstance, windowClass);
HWND hwnd = CreateMainWindow(hInstance, windowClass);
MessageLoop();
}
catch (const std::exception& ex)
{
MessageBoxA(nullptr, ex.what(), "Error", MB_ICONERROR);
return 1;
}
return 0;
}
void RegisterClass(HINSTANCE hInstance, PCWSTR class_name) {
WNDCLASSEXW wc;
memset(&wc, 0, sizeof(wc));
wc.cbSize = sizeof(WNDCLASSEXW);
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
wc.hInstance = hInstance;
wc.lpszClassName = class_name;
wc.style = CS_HREDRAW | CS_VREDRAW;
//wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); // White background
wc.lpfnWndProc = WndProc;
ATOM atom = RegisterClassExW(&wc);
if (atom == 0) throw std::runtime_error("Failed to register window class");
}
HWND CreateMainWindow(HINSTANCE hInstance, PCWSTR class_name) {
PCWSTR window_title = L"Device Picker Sample";
int screen_width= GetSystemMetrics(SM_CXSCREEN);
int screen_height = GetSystemMetrics(SM_CYSCREEN);
int x = 0;
int y = 0;
int w = screen_width;
int h = screen_height;
HWND hwnd = CreateWindowExW(
0,
class_name,
window_title,
WS_POPUP | WS_VISIBLE,
x, y, w, h,
nullptr,
nullptr,
hInstance,
nullptr
);
if (hwnd == nullptr) throw std::runtime_error("Failed to create main window");
ShowWindow(hwnd, SW_SHOW);
UpdateWindow(hwnd);
return hwnd;
}
void MessageLoop() {
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
void ShowPicker(HWND hwnd, Rect rect) {
DevicePicker devicePicker;
wprintf(L"Picker: X=%f Y=%f Width=%f Height=%f\n", rect.X, rect.Y, rect.Width, rect.Height);
//devicePicker.as<IInitializeWithWindow>()->Initialize(hwnd);
devicePicker.Show(rect);
}
void PaintRect(HWND hwnd, RECT rect) {
wprintf(L"Painting: left=%d top=%d right=%d bottom=%d\n", rect.left, rect.top, rect.right, rect.bottom);
HDC hdc = GetDC(hwnd);
if (hdc == nullptr) {
wprintf(L"GetDC failed\n");
return;
}
// Random color (RGB only, alpha 0)
COLORREF color = RGB(rand() % 256, rand() % 256, rand() % 256);
HBRUSH hBrush = CreateSolidBrush(color);
FillRect(hdc, &rect, hBrush);
DeleteObject(hBrush);
ReleaseDC(hwnd, hdc);
}
void ShowPosition(HWND hwnd) {
// This function shows both cursor position and picker position for comparison
const int WIDTH = 100;
const int HEIGHT = 100;
POINT pt = {};
if (!GetCursorPos(&pt))
{
wprintf(L"GetCursorPos failed\n");
return;
}
POINT clientPt = pt;
if (!ScreenToClient(hwnd, &clientPt))
{
wprintf(L"ScreenToClient failed\n");
return;
}
wprintf(L"ScreenCursor: X=%d Y=%d\n", pt.x, pt.y);
wprintf(L"ClientCursor: X=%d Y=%d\n", clientPt.x, clientPt.y);
// Show picker at cursor position
// DevicePicker expects screen coordinates in physical pixels
float scale = static_cast<float>(GetDpiForWindow(hwnd)) / USER_DEFAULT_SCREEN_DPI;
wprintf(L"scale = %f\n", scale);
Rect pickerRect;
pickerRect.X = static_cast<float>(pt.x) / scale;
pickerRect.Y = static_cast<float>(pt.y) / scale;
pickerRect.Width = 0.0;
pickerRect.Height = 0.0;
ShowPicker(hwnd, pickerRect);
// Draw a rectangle around the pointer, centered on the pointer
RECT paintRect;
paintRect.left = clientPt.x - (WIDTH / 2);
paintRect.top = clientPt.y - (HEIGHT / 2);
paintRect.right = clientPt.x + (WIDTH / 2);
paintRect.bottom = clientPt.y + (HEIGHT / 2);
PaintRect(hwnd, paintRect);
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
LRESULT res = 0;
switch (message)
{
case WM_LBUTTONDOWN:
{
wprintf( L"WM_LBUTTONDOWN: wparam(0x%llX) lparam(0x%llX)\n",
static_cast<unsigned long long>(wParam),
static_cast<unsigned long long>(lParam));
// Click left mouse button to show device picker
ShowPosition(hwnd);
break;
}
case WM_RBUTTONDOWN:
{
// Right mouse button to quit application
PostQuitMessage(0);
break;
}
case WM_KEYDOWN:
{
wprintf(L"WM_KEYDOWN: wparam(0x%llX) lparam(0x%llX)\n",
static_cast<unsigned long long>(wParam),
static_cast<unsigned long long>(lParam));
// Handle ESC key to quit application
if (wParam == VK_ESCAPE)
{
PostQuitMessage(0);
}
break;
}
default:
res = DefWindowProcW(hwnd, message, wParam, lParam);
break;
}
return res;
}