Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Det här innehållet riktar sig till utvecklare som vill uppdatera skrivbordsprogram för att hantera ändringar i visningsskalningsfaktorn (punkter per tum eller DPI) dynamiskt, så att deras program kan vara skarpa på alla bildskärmar som de återges på.
Om du till att börja med skapar en ny Windows-app från grunden rekommenderar vi starkt att du skapar en UWP(Universal Windows Platform) program. UWP-program skalas automatiskt – och dynamiskt – för varje visning som de körs på.
Skrivbordsprogram som använder äldre Windows-programmeringstekniker (rå Win32-programmering, Windows Forms, Windows Presentation Framework (WPF) kan inte hantera DPI-skalning automatiskt utan ytterligare utvecklararbete. Utan sådant arbete visas program som suddiga eller felaktigt stora i många vanliga användningsscenarier. Det här dokumentet innehåller kontext och information om vad som ingår i uppdateringen av ett skrivbordsprogram för att återges korrekt.
Visa skalningsfaktor & DPI
I takt med att visningstekniken har fortskridit har tillverkare av bildskärmspaneler packat in ett ökande antal pixlar i varje enhet med fysiskt utrymme på sina paneler. Detta har resulterat i att punkterna per tum (DPI) för moderna bildskärmspaneler är mycket högre än de tidigare har varit. Tidigare hade de flesta skärmar 96 bildpunkter per linjär tum fysiskt utrymme (96 DPI); under 2017 är skärmar med nästan 300 DPI eller högre lättillgängliga.
De flesta äldre gränssnittsramverk för skrivbord har inbyggda antaganden om att visnings-DPI:et inte ändras under processens livslängd. Det här antagandet gäller inte längre, och visnings-DPI:er ändras ofta flera gånger under en programprocesss livslängd. Några vanliga scenarier där visningsskalningsfaktorn/DPI-ändringarna är:
- Installationer med flera bildskärmar där varje skärm har en annan skalningsfaktor och programmet flyttas från en skärm till en annan (till exempel en 4K och en 1080p-skärm)
- Dockning och avdockning av en hög DPI-bärbar dator med en extern skärm med låg DPI (eller vice versa)
- Ansluta via Fjärrskrivbord från en hög DPI-bärbar dator/surfplatta till en låg DPI-enhet (eller vice versa)
- Ändra inställningar för visningsskalningsfaktor medan program körs
I dessa scenarier ritar UWP-program om sig själva för den nya DPI:n automatiskt. Som standard, och utan ytterligare utvecklararbete, gör inte skrivbordsprogram det. Skrivbordsprogram som inte gör detta extra arbete för att svara på DPI-ändringar kan verka suddiga eller felaktigt storleksanpassade för användaren.
DPI-medvetenhetsläge
Skrivbordsprogram måste tala om för Windows om de stöder DPI-skalning. Som standard anser systemet att skrivbordsprogram som DPI inte känner till och bitmappen sträcker ut sina fönster. Genom att ange något av följande tillgängliga DPI-lägen kan program uttryckligen berätta för Windows hur de vill hantera DPI-skalning:
DPI omedveten
DPI-ovetande program renderas med ett fast DPI-värde på 96 (100%). När dessa program körs på en skärm med en visningsskala som är större än 96 DPI, kommer Windows att sträcka programbitmappen till den förväntade fysiska storleken. Detta resulterar i att programmet blir suddigt.
System-DPI-medvetenhet
Skrivbordsprogram som är system-DPI-medvetna tar vanligtvis emot DPI för den primära anslutna övervakaren när användaren loggar in. Under initieringen beskriver de sitt användargränssnitt på rätt sätt (storlekskontroller, val av teckenstorlekar, inläsning av tillgångar osv.) med hjälp av det system-DPI-värdet. Därför är system-DPI-medvetna program inte DPI-skalade (bitmappssträckta) av Windows på visningsåtergivning vid den enda DPI:en. När programmet flyttas till en skärm med en annan skalningsfaktor, eller om visningsskalningsfaktorn annars ändras, kommer Windows att bitmappsskala programmets fönster, vilket gör att de ser suddiga ut. I praktiken renderas system-DPI-medvetna skrivbordsprogram endast skarpt med en enda skalningsfaktor, vilket blir suddigt när DPI:et ändras.
Per-Monitor och Per-Monitor (V2) DPI-medvetenhet
Vi rekommenderar att skrivbordsprogram uppdateras för att använda DPI-medvetenhetsläget per övervakning, så att de omedelbart kan återges korrekt när DPI:et ändras. När ett program rapporterar till Windows som det vill köra i det här läget kommer Windows inte att bitmappa programmet när DPI:et ändras, utan skickar i stället WM_DPICHANGED till programfönstret. Det är sedan det fullständiga ansvaret för programmet att hantera storleksändringen för den nya DPI:n. De flesta gränssnittsramverk som används av skrivbordsprogram (vanliga Windows-kontroller (comctl32), Windows Forms, Windows Presentation Framework osv.) stöder inte automatisk DPI-skalning, vilket kräver att utvecklare ändrar storlek på och flyttar innehållet i sina fönster själva.
Det finns två versioner av Per-Monitor medvetenhet om att ett program kan registrera sig som: version 1 och version 2 (PMv2). Registrering av en process som körs i PMv2-medvetenhetsläget resulterar i:
- Programmet meddelas när DPI:et ändras (både de högsta och underordnade HWND:erna)
- Programmet ser de råa bildpunkterna för varje visning
- Programmet blir aldrig bitmappsskalat av Windows
- Automatiskt icke-klientområde (fönsterrubrik, rullningslister osv.) DPI-skalning efter Windows
- Win32-dialogrutor (från CreateDialog) automatiskt DPI skalas av Windows
- Temaritade bitmappstillgångar i vanliga kontroller (kryssrutor, knappbakgrunder osv.) återges automatiskt med lämplig DPI-skalningsfaktor
När de körs i Per-Monitor v2 Awareness-läge meddelas program när deras DPI har ändrats. Om ett program inte ändrar storlek för den nya DPI:n visas programgränssnittet för litet eller för stort (beroende på skillnaden i tidigare och nya DPI-värden).
Not
Per-Monitor V1-medvetenhet (PMv1) är mycket begränsad. Vi rekommenderar att program använder PMv2.
I följande tabell visas hur program återges i olika scenarier:
DPI-medvetenhetsläge | Windows-version introducerad | Programmets vy över DPI | Beteende vid DPI-ändring |
---|---|---|---|
Ovetande | Ej tillämpligt | Alla skärmar är 96 DPI | Bitmappssträckning (suddig) |
System | Vista | Alla skärmar har samma DPI (DPI för den primära visningen när den aktuella användarsessionen startades) | Bitmappssträckning (suddig) |
Per-Monitor | 8.1 | DPI:et för den visning som programfönstret främst finns på |
|
Per-Monitor V2 | Windows 10 Creators Update (1703) | DPI:et för den visning som programfönstret främst finns på |
automatisk DPI-skalning av:
|
DPI-medvetenhet per övervakare (V1)
Per-Monitor V1 DPI-medvetenhetsläge (PMv1) introducerades med Windows 8.1. Det här DPI-medvetenhetsläget är mycket begränsat och erbjuder endast de funktioner som anges nedan. Vi rekommenderar att skrivbordsprogram använder Per-Monitor v2-medvetenhetsläge som stöds i Windows 10 1703 eller senare.
Det initiala stödet för per övervakningsmedvetenhet erbjöd endast program följande:
- HWND:n på toppnivå meddelas om en DPI-ändring och ger en ny föreslagen storlek
- Windows kommer inte att bitmappa ut programmets användargränssnitt
- Programmet ser alla skärmar i fysiska bildpunkter (se virtualisering)
I Windows 10 1607 eller senare kan PMv1-program också anropa EnableNonClientDpiScaling under WM_NCCREATE för att begära att Windows skalar fönstrets icke-klientområde korrekt.
Stöd för DPI-skalning per övervakare efter UI Framework/Teknik
Tabellen nedan visar nivån för stöd för DPI-medvetenhet per övervakare som erbjuds av olika Windows UI-ramverk från och med Windows 10 1703:
Ramverk/teknik | Stöd | OS-version | DPI-skalning som hanteras av | Ytterligare läsning |
---|---|---|---|---|
Universal Windows Platform (UWP) | Full | 1607 | Ramverk för användargränssnitt | UWP(Universal Windows Platform) |
Raw Win32/Common Controls V6 (comctl32.dll) |
|
1703 | Tillämpning | GitHub-exempel |
Windows-formulär | Begränsad automatisk DPI-skalning per övervakare för vissa kontroller | 1703 | Ramverk för användargränssnitt | hög DPI-support i Windows Forms |
Windows Presentation Framework (WPF) | Inbyggda WPF-program kommer att DPI skala WPF som finns i andra ramverk och andra ramverk som finns i WPF skalas inte automatiskt | 1607 | Ramverk för användargränssnitt | GitHub-exempel |
GDI | Ingen | Ej tillämpligt | Tillämpning | Se GDI-High-DPI skalning |
GDI+ | Ingen | Ej tillämpligt | Tillämpning | Se GDI-High-DPI skalning |
MFC | Ingen | Ej tillämpligt | Tillämpning | Ej tillämpligt |
Uppdatera befintliga program
För att kunna uppdatera ett befintligt skrivbordsprogram för att hantera DPI-skalning på rätt sätt måste det uppdateras så att de viktiga delarna i användargränssnittet åtminstone uppdateras för att svara på DPI-ändringar.
De flesta skrivbordsprogram körs i systemläge för DPI-medvetenhet. System-DPI-medvetna program skalas vanligtvis till DPI för den primära skärmen (den visning som systemfältet fanns på när Windows-sessionen startades). När DPI:et ändras kommer Windows att bitmappa ut användargränssnittet för dessa program, vilket ofta resulterar i att de blir suddiga. När du uppdaterar ett system-DPI-medvetet program för att bli per-monitor-DPI medveten, måste koden som hanterar användargränssnittslayouten uppdateras så att den utförs inte bara under programinitiering, utan även när ett DPI-ändringsmeddelande (WM_DPICHANGED i fallet med Win32) tas emot. Detta innebär vanligtvis att man går tillbaka till alla antaganden i koden om att användargränssnittet bara behöver skalas en gång.
När det gäller Win32-programmering har många Win32-API:er inte heller någon DPI eller visningskontext, så de returnerar bara värden i förhållande till system-DPI:et. Det kan vara användbart att koppla genom koden för att leta efter några av dessa API:er och ersätta dem med DPI-medvetna varianter. Några av de vanliga API:er som har DPI-medvetna varianter är:
Enkel DPI-version | Per-Monitor version |
---|---|
GetSystemMetrics | GetSystemMetricsForDpi |
AdjustWindowRectEx | AdjustWindowRectExForDpi |
SystemParametersInfo | SystemParametersInfoForDpi |
GetDpiForMonitor | GetDpiForWindow |
Det är också en bra idé att söka efter hårdkodade storlekar i din kodbas som förutsätter en konstant DPI och ersätter dem med kod som korrekt står för DPI-skalning. Nedan visas ett exempel som innehåller alla dessa förslag:
Exempel:
Exemplet nedan visar ett förenklat Win32-fall där du skapar en underordnad HWND. Anropet till CreateWindow förutsätter att programmet körs på 96 DPI (USER_DEFAULT_SCREEN_DPI
konstant) och varken knappens storlek eller position kommer att vara korrekt vid högre DPIs:
case WM_CREATE:
{
// Add a button
HWND hWndChild = CreateWindow(L"BUTTON", L"Click Me",
WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
50,
50,
100,
50,
hWnd, (HMENU)NULL, NULL, NULL);
}
Den uppdaterade koden nedan visar:
- DPI:et för fönsterskapande kod som skalar positionen och storleken på den underordnade HWND:en för DPI:t för dess överordnade fönster
- Svara på DPI-ändring genom att flytta och ändra storlek på den underordnade HWND:en
- Hårdkodade storlekar har tagits bort och ersatts med kod som svarar på DPI-ändringar
#define INITIALX_96DPI 50
#define INITIALY_96DPI 50
#define INITIALWIDTH_96DPI 100
#define INITIALHEIGHT_96DPI 50
// DPI scale the position and size of the button control
void UpdateButtonLayoutForDpi(HWND hWnd)
{
int iDpi = GetDpiForWindow(hWnd);
int dpiScaledX = MulDiv(INITIALX_96DPI, iDpi, USER_DEFAULT_SCREEN_DPI);
int dpiScaledY = MulDiv(INITIALY_96DPI, iDpi, USER_DEFAULT_SCREEN_DPI);
int dpiScaledWidth = MulDiv(INITIALWIDTH_96DPI, iDpi, USER_DEFAULT_SCREEN_DPI);
int dpiScaledHeight = MulDiv(INITIALHEIGHT_96DPI, iDpi, USER_DEFAULT_SCREEN_DPI);
SetWindowPos(hWnd, hWnd, dpiScaledX, dpiScaledY, dpiScaledWidth, dpiScaledHeight, SWP_NOZORDER | SWP_NOACTIVATE);
}
...
case WM_CREATE:
{
// Add a button
HWND hWndChild = CreateWindow(L"BUTTON", L"Click Me",
WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
0,
0,
0,
0,
hWnd, (HMENU)NULL, NULL, NULL);
if (hWndChild != NULL)
{
UpdateButtonLayoutForDpi(hWndChild);
}
}
break;
case WM_DPICHANGED:
{
// Find the button and resize it
HWND hWndButton = FindWindowEx(hWnd, NULL, NULL, NULL);
if (hWndButton != NULL)
{
UpdateButtonLayoutForDpi(hWndButton);
}
}
break;
När du uppdaterar ett System DPI-medvetet program är några vanliga steg att följa:
- Markera processen som DPI-medveten per övervakare (V2) med hjälp av ett programmanifest (eller annan metod, beroende på vilka gränssnittsramverk som används).
- Gör UI-layoutlogik återanvändbar och flytta den från programinitieringskoden så att den kan återanvändas när en DPI-ändring inträffar (WM_DPICHANGED när det gäller Windows-programmering (Win32).
- Ogiltigförklara all kod som förutsätter att DPI-känsliga data (DPI/teckensnitt/storlekar/etc.) aldrig behöver uppdateras. Det är mycket vanligt att cachelagrade teckenstorlekar och DPI-värden vid processinitiering. När du uppdaterar ett program för att bli DPI-medveten per övervakare måste DPI-känsliga data omvärderas när en ny DPI påträffas.
- När en DPI-ändring inträffar läser du in (eller rastrerar om) eventuella bitmappstillgångar för den nya DPI:n eller, om du vill, bitmappen sträcker ut de inlästa tillgångarna till rätt storlek.
- Grep för API:er som inte är Per-Monitor DPI-medvetna och ersätter dem med Per-Monitor DPI-medvetna API:er (om tillämpligt). Exempel: ersätt GetSystemMetrics med GetSystemMetricsForDpi.
- Testa programmet på ett multi-display/multi-DPI-system.
- För alla fönster på den översta nivån i ditt program som du inte kan uppdatera till korrekt DPI-skalning använder du DPI-skalning i blandat läge (beskrivs nedan) för att tillåta bitmappssträckande av dessa toppnivåfönster av systemet.
Mixed-Mode DPI-skalning (Sub-Process DPI-skalning)
När du uppdaterar ett program för att stödja DPI-medvetenhet per övervakare kan det ibland bli opraktiskt eller omöjligt att uppdatera varje fönster i programmet på en gång. Detta kan helt enkelt bero på den tid och det arbete som krävs för att uppdatera och testa allt användargränssnitt, eller på att du inte äger all UI-kod som du behöver köra (om ditt program kanske läser in användargränssnittet från tredje part). I dessa situationer erbjuder Windows ett sätt att komma in i en värld av medvetenhet per övervakare genom att låta dig köra några av dina programfönster (endast på toppnivå) i sitt ursprungliga DPI-medvetenhetsläge medan du fokuserar din tid och energi på att uppdatera de viktigaste delarna av användargränssnittet.
Nedan visas en bild av hur detta kan se ut: du uppdaterar huvudprogrammets användargränssnitt ("Huvudfönster" i bilden) så att det körs med DPI-medvetenhet per övervakare medan du kör andra fönster i deras befintliga läge ("Sekundärt fönster").
Före Windows 10 Anniversary Update (1607) var DPI-medvetenhetsläget för en process en processomfattande egenskap. Från och med Windows 10 Anniversary Update kan den här egenskapen nu anges per fönster på den översta nivån. (Underordnade fönster måste fortsätta att matcha skalningsstorleken för deras överordnade.) Ett fönster på den översta nivån definieras som ett fönster utan överordnad. Detta är vanligtvis ett "vanligt" fönster med knappar för att minimera, maximera och stänga. Det scenario som DPI-medvetenhet under processen är avsett för är att få det sekundära användargränssnittet skalat efter Windows (bitmappssträckt) medan du fokuserar din tid och dina resurser på att uppdatera det primära användargränssnittet.
Om du vill aktivera DPI-medvetenhet under processen anropar du SetThreadDpiAwarenessContext före och efter alla fönsterskapande anrop. Fönstret som skapas associeras med den DPI-medvetenhet som du anger via SetThreadDpiAwarenessContext. Använd det andra anropet för att återställa den aktuella trådens DPI-medvetenhet.
När du använder DPI-skalning under processen kan du förlita dig på att Windows utför en del av DPI-skalningen för ditt program, men det kan öka programmets komplexitet. Det är viktigt att du förstår nackdelarna med den här metoden och vilken typ av komplexitet den medför. Mer information om DPI-medvetenhet under processen finns i Mixed-Mode DPI-skalning och DPI-medvetna API:er.
Testa dina ändringar
När du har uppdaterat ditt program för att bli DPI-medveten per övervakare är det viktigt att verifiera att programmet svarar korrekt på DPI-ändringar i en mixed-DPI-miljö. Några detaljer att testa är:
- Flytta programfönster fram och tillbaka mellan visning av olika DPI-värden
- Starta programmet på visning av olika DPI-värden
- Ändra skalningsfaktorn för övervakaren medan programmet körs
- Ändra den visning som du använder som primär skärm, logga ut från Windowsoch sedan testa programmet igen när du har loggat in igen. Detta är särskilt användbart för att hitta kod som använder hårdkodade storlekar/dimensioner.
Vanliga fallgropar (Win32)
Inte använda den föreslagna rektangel som anges i WM_DPICHANGED
När Windows skickar ett WM_DPICHANGED meddelande i programfönstret innehåller det här meddelandet en föreslagen rektangel som du bör använda för att ändra storlek på fönstret. Det är viktigt att ditt program använder den här rektangeln för att ändra storlek på sig själv, eftersom det gör följande:
- Kontrollera att musmarkören förblir i samma relativa position i fönstret när du drar mellan skärmarna
- Förhindra att programfönstret hamnar i en rekursiv dpi-ändringscykel där en DPI-ändring utlöser en efterföljande DPI-ändring, vilket utlöser ännu en DPI-ändring.
Om du har programspecifika krav som hindrar dig från att använda den föreslagna rektangel som Windows tillhandahåller i WM_DPICHANGED-meddelandet kan du läsa WM_GETDPISCALEDSIZE. Det här meddelandet kan användas för att ge Windows en önskad storlek som du vill använda när DPI-ändringen har inträffat, samtidigt som du undviker de problem som beskrivs ovan.
Brist på dokumentation om virtualisering
När en HWND eller process körs som antingen DPI omedveten eller system-DPI-medveten, kan det vara bitmapp som sträcks ut av Windows. När detta inträffar skalar och konverterar Windows DPI-känslig information från vissa API:er till koordinatutrymmet för den anropande tråden. Om en DPI-omedveten tråd till exempel frågar skärmstorleken när den körs på en hög DPI-skärm virtualiserar Windows svaret som ges till programmet som om skärmen fanns i 96 DPI-enheter. Om en system-DPI-medveten tråd interagerar med en skärm på en annan DPI än den som användes när den aktuella användarens session startades, kommer Windows att DPI-skala vissa API-anrop till det koordinatutrymme som HWND skulle använda om den kördes med sin ursprungliga DPI-skalningsfaktor.
När du uppdaterar ditt skrivbordsprogram till DPI-skalning på rätt sätt kan det vara svårt att veta vilka API-anrop som kan returnera virtualiserade värden baserat på trådkontexten. Den här informationen är för närvarande inte tillräckligt dokumenterad av Microsoft. Tänk på att om du anropar något system-API från en DPI-omedveten eller system-DPI-medveten trådkontext kan returvärdet virtualiseras. Kontrollera därför att tråden körs i den DPI-kontext som du förväntar dig när du interagerar med skärmen eller enskilda fönster. När du tillfälligt ändrar en tråds DPI-kontext med SetThreadDpiAwarenessContextska du återställa den gamla kontexten när du är klar för att undvika fel beteende någon annanstans i programmet.
Många Windows-API:er har ingen DPI-kontext
Många äldre Windows-API:er innehåller inte en DPI- eller HWND-kontext som en del av deras gränssnitt. Därför måste utvecklare ofta utföra ytterligare arbete för att hantera skalning av DPI-känslig information, till exempel storlekar, punkter eller ikoner. Utvecklare som använder LoadIcon måste till exempel antingen stretchinlästa ikoner för bitmappar eller använda alternativa API:er för att läsa in ikoner med rätt storlek för lämplig DPI, till exempel LoadImage.
Tvingad återställning av processomfattande DPI-medvetenhet
I allmänhet går det inte att ändra DPI-medvetenhetsläget för processen efter initieringen av processen. Windows kan dock med två skäl ändra DPI-medvetenhetsläget för din process om du försöker bryta kravet på att alla HWND:er i ett fönsterträd har samma DPI-medvetenhetsläge. I alla versioner av Windows, från och med Windows 10 1703, går det inte att ha olika HWND:er i ett HWND-träd som körs i olika DPI-lägen. Om du försöker skapa en underordnad-överordnad relation som bryter mot den här regeln kan DPI-medvetenheten om hela processen återställas. Detta kan utlösas av:
- Ett CreateWindow-anrop där det skickade i det överordnade fönstret har ett annat DPI-medvetenhetsläge än den anropande tråden.
- Ett SetParent-anrop där de två fönstren är associerade med olika DPI-medvetenhetslägen.
Tabellen nedan visar vad som händer om du försöker bryta mot den här regeln:
Operation | Windows 8.1 | Windows 10 (1607 och tidigare) | Windows 10 (1703 och senare) |
---|---|---|---|
SkapaWindow (In-Proc) | Ej tillämpligt | Child ärver (blandat läge) | Child ärver (blandat läge) |
SkapaWindow (korsföring) | Tvingad återställning (av anroparens process) | Child ärver (blandat läge) | Tvingad återställning (av anroparens process) |
SetParent (In-Proc) | Ej tillämpligt | Tvingad återställning (av den aktuella processen) | misslyckas (ERROR_INVALID_STATE) |
SetParent (korsföring) | Tvingad återställning (underordnad fönsterprocess) | Tvingad återställning (underordnad fönsterprocess) | Tvingad återställning (underordnad fönsterprocess) |