GetWindowRect returnează o dimensiune care include marginile „invizibile” (Programare, Winapi, Vb6, Windows 10, Dwm)

Deanna a intrebat.

Lucrez la o aplicație care poziționează ferestrele pe ecran în stil grilă. Când rulează acest lucru pe Windows 10, există un decalaj imens între ferestre. O investigație suplimentară arată că GetWindowRect returnează valori neașteptate, inclusiv o margine invizibilă, dar nu reușesc să-l fac să returneze valorile reale cu marginea vizibilă.

1) Acest fir sugerează că acest lucru este proiectat și că îl puteți „rezolva” prin crearea unei legături cu winver=6. Mediul meu nu permite acest lucru, dar am încercat să schimb PE MajorOperatingSystemVersion și MajorSubsystemVersion la 6 fără niciun efect

2) Același fir sugerează, de asemenea, utilizarea DwmGetWindowAttribute cu DWMWA_EXTENDED_FRAME_BOUNDS pentru a obține coordonatele reale de la DWM, ceea ce funcționează, dar înseamnă că trebuie schimbat peste tot unde se obțin coordonatele ferestrei. De asemenea, nu permite setarea valorii, ceea ce ne lasă să inversăm procesul pentru a putea seta dimensiunea ferestrei.

3) Această întrebare sugerează că este vorba de lipsa conștientizării DPI în cadrul procesului. Nici setarea indicatorului de conștientizare a DPI în manifest, nici apelul SetProcessDpiAwareness a avut vreun rezultat.

4) Dintr-un capriciu, am încercat, de asemenea, să adaug stegulețele de compatibilitate Windows Vista, 7, 8, 8.1 și 10, precum și manifestul temelor Windows, fără nicio schimbare.

Această fereastră este mutată la 0x0, 1280×1024, se presupune că pentru a umple întregul ecran, iar la interogarea înapoi a coordonatelor, obținem aceleași valori. fereastra este însă de fapt cu 14 pixeli mai îngustă, pentru a ține cont de marginea de pe versiunile mai vechi de Windows.

Cum pot convinge Windows să mă lase să lucrez cu coordonatele reale ale ferestrei?

Comentarii

  • Pentru o fereastră maximizată sau o fereastră mică, care sunt coordonatele pe care le așteptați și coordonatele pe care le obțineți? –  > Por Barmak Shemirani.
  • @barmak Aș vrea ca atunci când o setez la 0x0, fereastra să fie în stânga sus, în loc de 7×0 cum apare de fapt. Vedeți captura de ecran. –  > Por Deanna.
  • Este vorba de VB6 sau VB.NET? –  > Por IInspectable.
  • @IInspectable Codul meu de test este VB6, așa cum a fost etichetat, dar problema afectează și API-ul Win32. –  > Por Deanna.
  • Această întrebare spune din nou că schimbarea subsistemului care se schimbă la 6.0 ar trebui să funcționeze. Va trebui să testez din nou mâine, deoarece schimb doar antetul PE. –  > Por Deanna.
4 răspunsuri
Barmak Shemirani

Windows 10 are margini subțiri invizibile în stânga, dreapta și jos, este folosit pentru a prinde mouse-ul pentru redimensionare. Granițele ar putea arăta astfel: 7,0,7,7 (stânga, sus, dreapta, jos)

Când apelați SetWindowPos pentru a pune fereastra la aceste coordonate:
0, 0, 1280, 1024

Fereastra va alege aceste coordonate exacte și GetWindowRect va returna aceleași coordonate. Dar, din punct de vedere vizual, fereastra pare să fie aici:
7, 0, 1273, 1017

Puteți să păcăliți fereastra și să-i spuneți să meargă aici:
-7, 0, 1287, 1031

Pentru a face acest lucru, obținem grosimea marginilor din Windows 10:

RECT rect, frame;
GetWindowRect(hwnd, &rect);
DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &frame, sizeof(RECT));

//rect should be `0, 0, 1280, 1024`
//frame should be `7, 0, 1273, 1017`

RECT border;
border.left = frame.left - rect.left;
border.top = frame.top - rect.top;
border.right = rect.right - frame.right;
border.bottom = rect.bottom - frame.bottom;

//border should be `7, 0, 7, 7`

Apoi, decalați dreptunghiul astfel:

rect.left -= border.left;
rect.top -= border.top;
rect.right += border.left + border.right;
rect.bottom += border.top + border.bottom;

//new rect should be `-7, 0, 1287, 1031`

Dacă nu cumva există o soluție mai simplă!

Comentarii

  • Acest lucru devine din ce în ce mai bun… Această metodă funcționează doar DUPĂ ce fereastra a fost afișată 🙁 Până atunci, funcția DWM returnează același lucru ca și GetWindowRect. –  > Por Deanna.
  • Oh, nu m-am gândit la asta. O să șterg acest răspuns. Am observat alte probleme cu el. Rețineți că SetWindowPos și GetWindowRect funcționează corect. Ați cerut borduri și sistemul vă oferă borduri. Singura problemă este că marginile sunt invizibile în Windows 10, astfel încât pare că fereastra este în locul greșit. Visual Studio IDE are propriile sale ferestre de instrumente, atunci când fereastra de instrumente este andocată, aceasta nu utilizează margini sau margini personalizate NC_PAINT; iar atunci când fereastra sa de instrumente este flotantă, aceasta utilizează margini implicite. Cred că doriți ceva similar? –  > Por Barmak Shemirani.
  • Aș dori ca ferestrele să mă lase să lucrez cu dreptunghiul real, vizibil al ferestrei 🙂 Acest răspuns este util, așa că aș prefera să nu fie șters totuși, deoarece este singura soluție de până acum… –  > Por Deanna.
  • Rețineți că DwmGetWindowAttribute() returnează coordonate fizice, dar GetWindowRect() returnează coordonate logice. Astfel, lățimea marginii va fi greșită pentru o aplicație care nu este conștientă de DPI pe un ecran scalat la orice altceva decât 100%. –  > Por Ian Goldby.
  • Ca răspuns la comentariul lui @IanGoldby – este o idee bună, dar, după părerea mea, este un sfat mult mai bun să vă asigurați întotdeauna că aplicația dvs. rulează întotdeauna ca fiind conștientă de DPI dacă efectuați apeluri de sistem pentru coordonate. În acest caz, apelurile DWM și User32 vor fi de acord și ambele vor returna coordonate fizice. Există multe funcții API Windows care sunt inconsecvente în acest fel dacă nu specificați DPI Awareness –  > Por caesay.
mikew

Cum pot convinge Windows să mă lase să lucrez cu coordonatele reale ale ferestrei?

Deja lucrați cu coordonatele reale. Windows10 a ales pur și simplu să ascundă marginile de ochii dumneavoastră. Dar, cu toate acestea, ele sunt încă acolo. Dacă treceți cu mouse-ul pe lângă marginile ferestrei, cursorul se va schimba în cursorul de redimensionare, ceea ce înseamnă că acesta se află încă deasupra ferestrei.

Dacă doriți ca ochii dvs. să se potrivească cu ceea ce vă spune Windows, puteți încerca să expuneți aceste margini, astfel încât să fie din nou vizibile, utilizând tema Aero Lite:

http://winaero.com/blog/enable-the-hidden-aero-lite-theme-in-windows-10/

Comentarii

  • Aceasta este concluzia la care am ajuns. Schimbarea temei însă nu este o opțiune, deoarece este vorba de o aplicație disponibilă în comerț. Forțarea unei teme asupra utilizatorilor finali este în general considerată o practică proastă 🙂 –  > Por Deanna.
  • Sunt de acord că schimbarea temei este o idee proastă și un lucru pe care eu nu îl cer utilizatorilor mei. Dar ei se plâng în continuare de lacune 🙁 – –  > Por mikew.
Mason

AdjustWindowRectEx (sau pe Windows 10 și mai târziu AdjustWindowRectExForDpi) ar putea fi de folos. Aceste funcții vor converti un dreptunghi al clientului într-o dimensiune a ferestrei.

Presupun că nu doriți totuși să suprapuneți marginile, așa că probabil că aceasta nu este o soluție completă – dar poate fi o parte a soluției și poate fi utilă pentru alte persoane care se întâlnesc cu această întrebare.

Iată un scurt fragment din baza mea de coduri în care am folosit cu succes acestea pentru a seta dimensiunea ferestrei pentru a obține dimensiunea dorită de client, scuzați macrourile de tratare a erorilor:

DWORD window_style = (DWORD)GetWindowLong(global_context->window, GWL_STYLE);
CHECK_CODE(window_style);
CHECK(window_style != WS_OVERLAPPED); // Required by AdjustWindowRectEx

DWORD window_style_ex = (DWORD)GetWindowLong(global_context->window, GWL_EXSTYLE);
CHECK_CODE(window_style_ex);

// XXX: Use DPI aware version?
RECT requested_size = {};
requested_size.right = width;
requested_size.bottom = height;
AdjustWindowRectEx(
    &requested_size,
    window_style,
    false, // XXX: Why always false here?
    window_style_ex
);

UINT set_window_pos_flags = SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER;
CHECK_CODE(SetWindowPos(
    global_context->window,
    nullptr,
    0,
    0,
    requested_size.right - requested_size.left,
    requested_size.bottom - requested_size.top,
    set_window_pos_flags
));

Există încă două ambiguități în cazul de utilizare de mai sus:

  • Fereastra mea face are un meniu, dar trebuie să trec false pentru parametrul de meniu, altfel obțin o dimensiune greșită. Voi actualiza acest răspuns cu o explicație dacă îmi dau seama de ce se întâmplă acest lucru!
  • Nu am citit încă despre modul în care Windows gestionează conștientizarea DPI, așa că nu sunt sigur când doriți să utilizați această funcție față de cea care nu este conștientă de DPI.

xjdrew

Puteți răspunde la WM_NCCALCSIZE mesaj, modificați WndProc‘s default behaviour pentru a elimina marginea invizibilă.

Ca acest document și acest document explică, atunci când wParam > 0, La cerere wParam.Rgrc[0] conține noile coordonate ale ferestrei, iar când procedura se întoarce, Response wParam.Rgrc[0] conține coordonatele noului dreptunghi al clientului.

Exemplul de cod golang:

case win.WM_NCCALCSIZE:
    log.Println("----------------- WM_NCCALCSIZE:", wParam, lParam)

    if wParam > 0 {
        params := (*win.NCCALCSIZE_PARAMS)(unsafe.Pointer(lParam))
        params.Rgrc[0].Top = params.Rgrc[2].Top
        params.Rgrc[0].Left = params.Rgrc[0].Left + 1
        params.Rgrc[0].Bottom = params.Rgrc[0].Bottom - 1
        params.Rgrc[0].Right = params.Rgrc[0].Right - 1
        return 0x0300
    }