windows - AccessibleObjectFromPoint og per-monitor DPI

Indlæg af Hanne Mølgaard Plasc

Problem



Jeg bruger tilgængelighed med funktionen AccessibleObjectFromPoint, og jeg synes, at den fungerer korrekt på et DPI-miljø. Desværre kan jeg ikke få det til at fungere. Jeg prøvede mange ting, og situationen for nu er:



  • Min app er markeret som per-monitor-DPI-bevidst i manifestet. (True/PM)

  • Jeg bruger GetCursorPos og derefter AccessibleObjectFromPoint.



Hvordan kan problemet reproduceres:



  • Har to skærme, en med 100\% DPI, den anden med 125\%.

  • Kør Chrome på 125\% monitoren.

  • Brug AccessibleObjectFromPoint på et af fanebladene, det vandt ikke.



Det virker med nogle apps (DPI-bevidst, det ligner udforsker), men arbejder ikke med andre. Jeg har forsøgt flere relevante funktioner som GetPhysicalCursorPos og PhysicalToLogicalPointForPerMonitorDPI, men intet virker.


Det er værd at bemærke, at Microsoft s inspect.exe fungerer som forventet.

Bedste reference


Jeg har kæmpet med dette nøjagtigt samme problem i flere uger og kan nu fortælle mine resultater. Desværre kan jeg ikke give dig mere end et antydning af kode, fordi projektet jeg arbejder på, er proprietært.


Problemet startede på Windows 8.1. Problemet eksisterede ikke på Windows 7 eller Vista, fordi AccessibleObjectFromPoint altid brugte rå fysiske koordinater, som dokumenteret her: https://msdn.microsoft.com/en-us/library/windows/desktop/dd317984(v=vs.85 ) .aspx.

'Microsoft Active Accessibility bruger ikke logiske koordinater. Følgende metoder og funktioner returnerer enten fysiske koordinater eller tager dem som parametre. 'Dette har ikke været sandt siden Windows 8.1. [9]


AccessibleObjectFromPoint bruger nu en fejlagtig beregning, som ikke altid kan finde det korrekte vindue af grunde, der svarer til mit spørgsmål her: Høj DPI-skalering, musekroge og WindowFromPoint.
Mine resultater fører mig til en konklusion: API'en er brudt. Dette betyder ikke, at det ikke er muligt selv.


Mulige løsninger, jeg har delvist testet, synes at fungere efter.
Forudsætninger er at du


1/. Gør din proces pr. Monitor DPI opmærksom, IKKE BRUG AF MANIFESTEN (mere om det senere).


2/. Bestem hWnd for vinduet, du vil forespørge (WindowFromPoint () varianter)


3/. Bestem skærmens DPI for den forespurgte hWnd


4/. Bestem DPI for din proces


5/. Bestem DPI for den forespurgte hWnd


6/. Bestem skærmens oprindelse og offset for den forespurgte hWnd (MonitorFromWindow () og GetMonitorInfo ())


Derefter afhænger af din platform


Windows 10.0.14393 +


Skriv en funktion, der finder den IAccessible (AccessibleObjectFromWindow ()) fra øverste niveau vindue, og derefter rekursivt kalder IAccessible :: accHitTest, indtil du når de nederste iAccessible og måske ChildID data. Returner det som om du ville kalde AccessibleObjectFromPoint.


For at kalde det succesfuldt skal du skala (x, y) koordinaterne ind i skalaen for den forespurgte hWnd, ved hjælp af DPI'erne og koordinaterne hentet i listen ovenfor. Pas på systemer, hvor skærme ikke er af samme størrelse, eller hvis skærme er delvist udlignet, eller over og under.


Og nu for den vigtige del for 10.0.14393 - Indstil din tråd til samme DPI\_AWARENESS\_CONTEXT af hWnd du spørger. Ring nu til din nye funktion. Nu vend din tråd til at overvåge DPI opmærksom, og voila, det virker, selvom vinduet ikke er maksimeret. Derfor må du ikke bruge manifestet.


Hvis du er på Windows 8.1 til 10.0.10586 , har du en hårdere opgave.
I stedet for at kalde accHitTest, som ovenfor, skal du rekursivt kalde AccessibleChildren og gentage kaldet IAccessible :: accLocation for at afgøre, om dit testpunkt er inden for hvert barn. Dette er vanskelig og begynder at blive virkelig rodet, når du kommer til f.eks. kombinationsbokse i produkter som Office, som kun er system DPI opmærksom.


Det er alt, hvad jeg kan give dig for nu.


For at gøre det med succes på multi-platform (min skal arbejde fra Vista til Windows-Current) er det eneste rigtig sikre bet at skrive en wrapper DLL i C ++, der kan bestemme ved kørsel, hvilket OS det er på og ændre kodebanen i overensstemmelse hermed. Grunden til, at du vil gøre det i C ++, er at undgå at overføre IAccessible objekter på tværs af .Net/unmanaged Marshalling-grænsen. Du kan ringe til IUnknown :: Release på objekter, du ikke behøver at returnere n den ustyrede side. Du kan gøre alt i. Net, men det vil være langsomt.


P. S. Pas også på, at der kommer Chrome tilbage til uendelige træer, hvor forældre er børn af deres forældre, er det nødvendigt med nogle snity checks. Chrome returnerer heller ikke accRole korrekt, og vil give dig HTML-tags i stedet for VT\_I4.


Held og lykke