DevicePicker fails to display at the correct coordinates when DPI != 96

dalin ye 20 Reputation points
2025-11-24T18:28:50.8866667+00:00

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;
}
Developer technologies | C++
Developer technologies | C++
A high-level, general-purpose programming language, created as an extension of the C programming language, that has object-oriented, generic, and functional features in addition to facilities for low-level memory manipulation.
0 comments No comments
{count} votes

Answer accepted by question author
  1. Darran Rowe 2,531 Reputation points
    2025-11-25T01:19:26.7166667+00:00

    Are you sure that you really need to do any scaling?

    One of the biggest quirks of the Windows API is that the coordinates that you obtain are the raw values. Windows doesn't try to do anything to them. What's more, the WinRT documentation doesn't state if the rectangle used by DevcePicer are screen coordinates or not.


2 additional answers

Sort by: Most helpful
  1. Q&A Assist
    2025-11-24T18:29:03.6333333+00:00
    AI generated content. This question contains an answer created with AI from Q&A Assist. Learn more

    It seems that the issue you're facing with the DevicePicker not displaying at the correct coordinates when the DPI is not set to 96 may be related to how you're calculating the position based on the DPI scaling. When using different DPI settings, the coordinates you provide should be adjusted accordingly to ensure that the DevicePicker appears at the expected location.

    In your code, you are correctly obtaining the DPI for the window and calculating the scale factor. However, when you adjust the cursor position by dividing it by the scale, ensure that you are using the correct DPI value that corresponds to the display where the cursor is located. This is crucial because the DPI can vary between displays in a multi-monitor setup.

    Additionally, when you set the DPI awareness context using SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);, it allows your application to be aware of the DPI settings for each monitor. Make sure that the GetDpiForWindow(hwnd) function is returning the correct DPI for the window that is currently active.

    You might also want to check if the DevicePicker requires the coordinates to be in physical pixels or logical pixels. If it expects physical pixels, your current approach should work, but if it expects logical pixels, you would need to adjust your calculations accordingly.

    If the problem persists, consider logging the values of pt.x, pt.y, and the calculated pickerRect.X and pickerRect.Y to see if they match your expectations. This can help identify where the discrepancy is occurring.

    0 comments No comments

  2. Adiba Khan 1,440 Reputation points Microsoft External Staff
    2025-11-25T07:36:52.7+00:00

    Thanks for reaching out. This issue is a classic example of a coordinate system mismatch in High-DPI applications, particularly when hosting Windows Runtime (WinRT) components like DevicePicker within a modern C++/Win32 framework  using Per-Monitor DPI Awareness V2 (PMv2).

    Resolution: Correcting DevicePicker Positioning in High DPI environments

    The root cause of the offset is a double scaling error combined with a failure to correctly translate the coordinate origin from the virtual screen to the local client area.

    1. Root cause analysis Your application correctly sets itself to the modern Per-Monitor High-DPI Awareness V2 (DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2). This is the required foundation for accurate scaling. The positioning failure occurs in the ShowPosition function:
      1. Coordinate Exception: The WinRT DevicePicker.Show(Rect) method, accessed via InitializeWithWindow, expects the input Rect coordinates (pickerRect.X, pickerRect.Y) to be in Logical Pixels (DIPs) and relative to the host window’s client area.
      2. API misuse: your code attempts to translate the screen position in two steps:
        1. It correctly calls GetCursorPos(&pt) to retrieve the cursor location in Physical Screen Coordinates (raw pixels relative to the virtual pixels to logical pixels/DIPs)
        2. it correctly calls ScreenToClient (hwnd, &clientPt), which automatically performs the required two tasks: origin translation (screen to client area) and DPI scaling (physical pixels to logical pixels/DIPs).
        3. The error: crucially your subsequent code ignores the correct result in clientPt and instead performs a redundant manual division using the unscaled physical screen coordinates.
      3. This operation produces a scaled coordinate, but one that is still relative to the distant Virtual Screen Origin, not the local client are origin. The resulting offset is the scaled distance of your window’s top-left corner from the virtual screen origin, which is why the picker appears consistently displaced when scaling is enabled.
    2. Corrected C++ implementation The solution is to eliminate the manual scaling calculation and rely entirely on the coordinates returned by the ScreenToClient API function, as it already provides the necessary logical client coordinates (DIPs). Please replace the coordinate calculation logic within your ShowPosition function with the following corrected implementation:
         void ShowPosition(HWND hwnd)
         {
         	POINT pt = {};
         	if (!GetCursorPos(&pt)) return;
         	// Use ScreenToClient to perform both origin translation
         	//and physical-to-logical (DIP) scaling simultaneously.
         	
         	POINT clientPt =pt;
         	if ( !ScreenToClient (hwnd, &clientPt))
         	{
         		//Add robust error handling here if necessary
         		return;
         	}
         	//Initialize the DdevicePicker Rect
         	Rect pickerTect;
         
         	//Correction: Use the resulting clientPt (which is already in DIPs relative to the client area)
         	pickerRect.X = static_cast<float>(clientPt.x);
         	pickerRect.Y = static_cast<float>(clientPt.y);
         	// Set width and height to 0.0f for point positioning (as intended)
         	pickerRect.Windth = 0.0f;
         	PickerRect.height = 0.0f;
         	//Show Device Picker 
         	DevicePicker devicePicker;
         
         devicePicker.As<IInializeWithWindow>()-> Initialize(hwnd);
         devicePicker.Show(pickerRect);
         }
         
      

    Summary of Fix

    By casting clientPt.x and clientPt.y directly to float and assigning them to pickerRect.X and pickerRect.Y, you are utilizing the coordinates that the Windows platform is designed to provide under the Per-Monitor DPI Awareness V2 contract. This ensures the DevicePicker receives coordinates that are correctly scaled and referenced relative to the client area of your host window, resolving the positional offset bug across all DPI settings.

     Please let us know if you require any further assistance we’re happy to help. If you found this information useful, kindly mark this as "Accept Answer".


Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.