Alto desenvolvimento de aplicativos da área de trabalho de DPI no Windows
Esse conteúdo é direcionado para desenvolvedores que estão procurando atualizar aplicativos da área de trabalho para lidar com alterações de fator de escala de exibição (pontos por polegada ou DPI) dinamicamente, permitindo que seus aplicativos sejam nítidos em qualquer exibição em que sejam renderizados.
Para começar, se você estiver criando um novo aplicativo do Windows do zero, é altamente recomendável criar um aplicativo UWP (Plataforma Universal do Windows). Os aplicativos UWP são dimensionados automaticamente e dinamicamente para cada exibição em que estão sendo executados.
Aplicativos de área de trabalho usando tecnologias de programação mais antigas do Windows (programação bruta do Win32, Windows Forms, WPF (Windows Presentation Framework), etc.) não é possível lidar automaticamente com o dimensionamento de DPI sem trabalho adicional do desenvolvedor. Sem esse trabalho, os aplicativos aparecerão desfocados ou de tamanho incorreto em muitos cenários de uso comuns. Este documento fornece contexto e informações sobre o que está envolvido na atualização de um aplicativo da área de trabalho para renderizar corretamente.
Exibir o Fator de Escala & DPI
À medida que a tecnologia de exibição progrediu, os fabricantes de painéis de exibição empacotaram um número crescente de pixels em cada unidade de espaço físico em seus painéis. Isso fez com que os pontos por polegada (DPI) dos painéis de exibição modernos fossem muito mais altos do que historicamente. No passado, a maioria dos displays tinha 96 pixels por polegada linear de espaço físico (96 DPI); em 2017, as exibições com quase 300 DPI ou superior estão prontamente disponíveis.
A maioria das estruturas de interface do usuário da área de trabalho herdada tem suposições internas de que a DPI de exibição não mudará durante o tempo de vida do processo. Essa suposição não é mais verdadeira, com DPIs de exibição geralmente mudando várias vezes ao longo do tempo de vida de um processo de aplicativo. Alguns cenários comuns em que o fator de escala de exibição/as alterações de DPI são:
- Configurações de vários monitores em que cada exibição tem um fator de escala diferente e o aplicativo é movido de uma exibição para outra (como uma exibição de 4K e 1080p)
- Encaixando e desencaixando um laptop DPI alto com uma exibição externa de baixo DPI (ou vice-versa)
- Conectando-se via Área de Trabalho Remota de um laptop/tablet DPI alto a um dispositivo de baixa DPI (ou vice-versa)
- Fazer com que as configurações de fator de escala de exibição sejam alteradas enquanto os aplicativos estão em execução
Nesses cenários, os aplicativos UWP são redesenhados automaticamente para o novo DPI. Por padrão e sem trabalho adicional do desenvolvedor, os aplicativos da área de trabalho não funcionam. Aplicativos de área de trabalho que não fazem esse trabalho extra para responder às alterações de DPI podem parecer desfocados ou de tamanho incorreto para o usuário.
Modo de Reconhecimento de DPI
Os aplicativos da área de trabalho devem informar ao Windows se dão suporte ao dimensionamento de DPI. Por padrão, o sistema considera os aplicativos da área de trabalho DPI sem conhecimento e o bitmap alonga suas janelas. Ao definir um dos seguintes modos de reconhecimento de DPI disponíveis, os aplicativos podem dizer explicitamente ao Windows como desejam lidar com o dimensionamento de DPI:
DPI sem conhecimento
Aplicativos sem conhecimento de DPI são renderizados com um valor de DPI fixo de 96 (100%). Sempre que esses aplicativos forem executados em uma tela com uma escala de exibição maior que 96 DPI, o Windows estenderá o bitmap do aplicativo para o tamanho físico esperado. Isso faz com que o aplicativo pareça desfocado.
Reconhecimento de DPI do Sistema
Os aplicativos de área de trabalho que estão cientes do DPI do sistema normalmente recebem o DPI do monitor conectado primário a partir do momento da entrada do usuário. Durante a inicialização, eles dispõem a interface do usuário adequadamente (controles de dimensionamento, escolher tamanhos de fonte, carregar ativos etc.) usando esse valor de DPI do Sistema. Dessa forma, os aplicativos com reconhecimento de DPI do sistema não são dimensionados por DPI (bitmap estendido) pelo Windows em exibições de renderização nesse único DPI. Quando o aplicativo é movido para uma exibição com um fator de escala diferente ou se o fator de escala de exibição for alterado, o Windows reduzirá a escala de bits das janelas do aplicativo, fazendo com que elas pareçam desfocadas. Efetivamente, os aplicativos de área de trabalho com reconhecimento de DPI do sistema só são renderizados de forma nítida em um único fator de escala de exibição, ficando desfocado sempre que o DPI for alterado.
Reconhecimento de DPI Per-Monitor e Per-Monitor (V2)
É recomendável que os aplicativos da área de trabalho sejam atualizados para usar o modo de reconhecimento de DPI por monitor, permitindo que eles sejam renderizados imediatamente corretamente sempre que o DPI for alterado. Quando um aplicativo relata ao Windows que deseja ser executado nesse modo, o Windows não fará o bitmap alongar o aplicativo quando o DPI for alterado, em vez de enviar WM_DPICHANGED para a janela do aplicativo. Em seguida, é responsabilidade total do aplicativo lidar com o redimensionamento para o novo DPI. A maioria das estruturas de interface do usuário usadas por aplicativos da área de trabalho (controles comuns do Windows (comctl32), Windows Forms, Windows Presentation Framework etc.) não dão suporte ao dimensionamento automático de DPI, exigindo que os desenvolvedores redimensionem e reposicionem o conteúdo de suas próprias janelas.
Há duas versões de Per-Monitor reconhecimento de que um aplicativo pode se registrar como: versão 1 e versão 2 (PMv2). Registrar um processo como em execução no modo de reconhecimento PMv2 resulta em:
- O aplicativo que está sendo notificado quando o DPI é alterado (os HWNDs de nível superior e filho)
- O aplicativo que vê os pixels brutos de cada exibição
- O aplicativo nunca está sendo dimensionado por bitmap pelo Windows
- Área não cliente automática (legenda da janela, barras de rolagem etc.) Dimensionamento de DPI pelo Windows
- Caixas de diálogo Win32 (de CreateDialog) são dimensionadas automaticamente pelo Windows
- Ativos de bitmap desenhados por tema em controles comuns (caixas de seleção, planos de fundo de botão etc.) sendo renderizados automaticamente no fator de escala de DPI apropriado
Ao executar em Per-Monitor modo de Reconhecimento v2, os aplicativos são notificados quando sua DPI é alterada. Se um aplicativo não se redimensionar para o novo DPI, a interface do usuário do aplicativo aparecerá muito pequena ou muito grande (dependendo da diferença nos valores de DPI anterior e novo).
Nota
Per-Monitor reconhecimento de V1 (PMv1) é muito limitada. É recomendável que os aplicativos usem PMv2.
A tabela a seguir mostra como os aplicativos serão renderizados em cenários diferentes:
Reconhecimento de DPI por Monitor (V1)
Per-Monitor modo de reconhecimento de DPI V1 (PMv1) foi introduzido com o Windows 8.1. Esse modo de reconhecimento de DPI é muito limitado e oferece apenas a funcionalidade listada abaixo. É recomendável que os aplicativos da área de trabalho usem Per-Monitor modo de reconhecimento v2, com suporte no Windows 10 1703 ou superior.
O suporte inicial para reconhecimento por monitor só oferecia aos aplicativos o seguinte:
- HWNDs de nível superior são notificados de uma alteração de DPI e fornecidos um novo tamanho sugerido
- O Windows não fará o bitmap alongar a interface do usuário do aplicativo
- O aplicativo vê todas as exibições em pixels físicos (consulte virtualização)
No Windows 10 1607 ou superior, os aplicativos PMv1 também podem chamar EnableNonClientDpiScaling durante WM_NCCREATE para solicitar que o Windows dimensione corretamente a área não cliente da janela.
Suporte ao dimensionamento de DPI por monitor por UI Framework/Tecnologia
A tabela abaixo mostra o nível de suporte de reconhecimento de DPI por monitor oferecido por várias estruturas de interface do usuário do Windows a partir do Windows 10 1703:
Framework/Technology | Apoio | Versão do sistema operacional | Dimensionamento de DPI manipulado por | Leitura adicional |
---|---|---|---|---|
UWP (Plataforma Universal do Windows) | Cheio | 1607 | Estrutura de interface do usuário | plataforma Universal do Windows (UWP) |
Controles Brutos win32/comuns V6 (comctl32.dll) |
|
1703 | Aplicação | de exemplo do GitHub |
Windows Forms | Dimensionamento de DPI automático por monitor limitado para alguns controles | 1703 | Estrutura de interface do usuário | alto suporte a DPI no Windows Forms |
Estrutura de Apresentação do Windows (WPF) | Os aplicativos nativos do WPF dimensionarão o WPF hospedado em outras estruturas e outras estruturas hospedadas no WPF não serão dimensionadas automaticamente | 1607 | Estrutura de interface do usuário | de exemplo do GitHub |
GDI | Nenhum | N/A | Aplicação | Consulte de dimensionamento de High-DPI GDI |
GDI+ | Nenhum | N/A | Aplicação | Consulte de dimensionamento de High-DPI GDI |
MFC | Nenhum | N/A | Aplicação | N/A |
Atualizando aplicativos existentes
Para atualizar um aplicativo de área de trabalho existente para lidar com o dimensionamento de DPI corretamente, ele precisa ser atualizado de modo que, no mínimo, as partes importantes de sua interface do usuário sejam atualizadas para responder às alterações de DPI.
A maioria dos aplicativos da área de trabalho é executada no modo de reconhecimento de DPI do sistema. Os aplicativos com reconhecimento de DPI do sistema normalmente são dimensionados para o DPI da exibição primária (a exibição em que a bandeja do sistema estava localizada no momento em que a sessão do Windows foi iniciada). Quando a DPI for alterada, o Windows fará o bitmap estica a interface do usuário desses aplicativos, o que geralmente resulta na desfocada. Ao atualizar um aplicativo com reconhecimento de DPI do Sistema para se tornar ciente do DPI por monitor, o código que manipula o layout da interface do usuário precisa ser atualizado de modo que ele seja executado não apenas durante a inicialização do aplicativo, mas também sempre que uma notificação de alteração de DPI (WM_DPICHANGED no caso do Win32) for recebida. Isso normalmente envolve revisitar quaisquer suposições no código de que a interface do usuário só precisa ser dimensionada uma vez.
Além disso, no caso da programação win32, muitas APIs Win32 não têm nenhum contexto de DPI ou exibição, portanto, elas só retornarão valores relativos ao DPI do Sistema. Pode ser útil usar o código para procurar algumas dessas APIs e substituí-las por variantes com reconhecimento de DPI. Algumas das APIs comuns que têm variantes com reconhecimento de DPI são:
Versão de DPI única | Per-Monitor versão |
---|---|
GetSystemMetrics | GetSystemMetricsForDpi |
AdjustWindowRectEx | AdjustWindowRectExForDpi |
SystemParametersInfo | SystemParametersInfoForDpi |
GetDpiForMonitor | GetDpiForWindow |
Também é uma boa ideia pesquisar tamanhos embutidos em código em sua base de código que pressupõem uma DPI constante, substituindo-os por um código que seja responsável corretamente pelo dimensionamento de DPI. Veja abaixo um exemplo que incorpora todas essas sugestões:
Exemplo:
O exemplo a seguir mostra um caso Win32 simplificado de criação de um HWND filho. A chamada para CreateWindow pressupõe que o aplicativo está em execução em 96 DPI (USER_DEFAULT_SCREEN_DPI
constante) e nem o tamanho nem a posição do botão estarão corretos em DPIs mais altos:
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);
}
O código atualizado abaixo mostra:
- O código de criação de janela DPI dimensionando a posição e o tamanho do HWND filho para o DPI de sua janela pai
- Respondendo à alteração do DPI reposicionando e redimensionando o HWND filho
- Tamanhos embutidos em código removidos e substituídos por código que responde a alterações de DPI
#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;
Ao atualizar um aplicativo com reconhecimento de DPI do sistema, algumas etapas comuns a seguir são:
- Marque o processo como V2 (reconhecimento de DPI por monitor) usando um manifesto do aplicativo (ou outro método, dependendo das estruturas de interface do usuário usadas).
- Torne a lógica de layout da interface do usuário reutilizável e mova-a para fora do código de inicialização do aplicativo, de modo que possa ser reutilizado quando ocorrer uma alteração de DPI (WM_DPICHANGED no caso da programação do Windows (Win32).
- Invalide qualquer código que pressupõe que os dados confidenciais de DPI (DPI/fontes/tamanhos/etc.) nunca precisam ser atualizados. É uma prática muito comum armazenar em cache tamanhos de fonte e valores de DPI na inicialização do processo. Ao atualizar um aplicativo para se tornar ciente do DPI por monitor, os dados confidenciais de DPI devem ser reavaliados sempre que uma nova DPI for encontrada.
- Quando ocorrer uma alteração de DPI, recarregue (ou re-rasterize) todos os ativos de bitmap para o novo DPI ou, opcionalmente, o bitmap estique os ativos carregados no momento para o tamanho correto.
- Grep para APIs que não estão Per-Monitor reconhecimento de DPI e as substituem por Per-Monitor APIs com reconhecimento de DPI (quando aplicável). Exemplo: substitua GetSystemMetrics por GetSystemMetricsForDpi.
- Teste seu aplicativo em um sistema multiDPI/exibição múltipla.
- Para qualquer janela de nível superior em seu aplicativo que você não consiga atualizar para a escala de DPI corretamente, use o dimensionamento de DPI de modo misto (descrito abaixo) para permitir o alongamento de bitmap dessas janelas de nível superior pelo sistema.
dimensionamento de DPI Mixed-Mode ( dimensionamento de DPISub-Process)
Ao atualizar um aplicativo para dar suporte à conscientização de DPI por monitor, às vezes ele pode se tornar impraticável ou impossível atualizar todas as janelas do aplicativo de uma só vez. Isso pode ser simplesmente devido ao tempo e ao esforço necessários para atualizar e testar toda a interface do usuário ou porque você não possui todo o código da interface do usuário que precisa executar (se o aplicativo talvez carregue a interface do usuário de terceiros). Nessas situações, o Windows oferece uma maneira de facilitar o mundo da conscientização por monitor, permitindo que você execute algumas de suas janelas de aplicativos (somente de nível superior) no modo de reconhecimento de DPI original enquanto você concentra seu tempo e energia atualizando as partes mais importantes da interface do usuário.
Veja abaixo uma ilustração da aparência disso: atualize a interface do usuário do aplicativo principal ("Janela Principal" na ilustração) para ser executada com reconhecimento de DPI por monitor enquanto executa outras janelas no modo existente ("Janela Secundária").
Antes da Atualização de Aniversário do Windows 10 (1607), o modo de reconhecimento de DPI de um processo era uma propriedade em todo o processo. A partir da Atualização de Aniversário do Windows 10, essa propriedade agora pode ser definida por janela de de nível superior. (Janelas filho devem continuar a corresponder ao tamanho de dimensionamento de seus pais.) Uma janela de nível superior é definida como uma janela sem pai. Normalmente, essa é uma janela "regular" com botões minimizar, maximizar e fechar. O cenário para o qual o reconhecimento de DPI de subprocesso se destina é ter a interface do usuário secundária dimensionada pelo Windows (bitmap estendido) enquanto você concentra seu tempo e recursos na atualização da interface do usuário primária.
Para habilitar o reconhecimento de DPI de subprocesso, chame SetThreadDpiAwarenessContext antes e depois de qualquer chamada de criação de janela. A janela criada será associada à consciência de DPI que você definiu por meio de SetThreadDpiAwarenessContext. Use a segunda chamada para restaurar o reconhecimento de DPI do thread atual.
Ao usar o dimensionamento de DPI de subprocesso, você pode contar com o Windows para fazer parte do dimensionamento de DPI para seu aplicativo, isso pode aumentar a complexidade do aplicativo. É importante que você entenda as desvantagens dessa abordagem e da natureza das complexidades que ela apresenta. Para obter mais informações sobre o reconhecimento de DPI de subprocesso, consulte Mixed-Mode dimensionamento de DPI e APIs com reconhecimento de DPI.
Testando suas alterações
Depois de atualizar seu aplicativo para se tornar ciente do DPI por monitor, é importante validar se o aplicativo responde corretamente às alterações de DPI em um ambiente de DPI misto. Alguns detalhes a serem testados incluem:
- Movendo janelas de aplicativos para frente e para trás entre exibições de diferentes valores de DPI
- Iniciando seu aplicativo em exibições de diferentes valores de DPI
- Alterando o fator de escala do monitor enquanto o aplicativo está em execução
- Alterando a exibição que você usa como exibição primária, sair do Windowse, em seguida, testar novamente seu aplicativo depois de entrar novamente. Isso é particularmente útil na localização de código que usa tamanhos/dimensões codificados em código.
Armadilhas Comuns (Win32)
Não usar o retângulo sugerido fornecido no WM_DPICHANGED
Quando o Windows envia uma mensagem WM_DPICHANGED à janela do aplicativo, essa mensagem inclui um retângulo sugerido que você deve usar para redimensionar sua janela. É fundamental que seu aplicativo use esse retângulo para se redimensionar, como isso fará:
- Verifique se o cursor do mouse permanecerá na mesma posição relativa na Janela ao arrastar entre exibições
- Impedir que a janela do aplicativo entre em um ciclo de alteração de dpi recursivo em que uma alteração de DPI dispara uma alteração de DPI subsequente, que dispara mais uma alteração de DPI.
Se você tiver requisitos específicos do aplicativo que impedem o uso do retângulo sugerido que o Windows fornece na mensagem WM_DPICHANGED, consulte WM_GETDPISCALEDSIZE. Essa mensagem pode ser usada para dar ao Windows um tamanho desejado que você gostaria de usar depois que a alteração de DPI ocorreu, evitando ainda os problemas descritos acima.
Falta de documentação sobre de virtualização
Quando um HWND ou processo está em execução como DPI sem conhecimento ou com reconhecimento de DPI do sistema, ele pode ser estendido por bitmap pelo Windows. Quando isso acontece, o Windows dimensiona e converte informações confidenciais de DPI de algumas APIs para o espaço de coordenadas do thread de chamada. Por exemplo, se um thread sem conhecimento de DPI consultar o tamanho da tela durante a execução em uma exibição de alta DPI, o Windows virtualizará a resposta fornecida ao aplicativo como se a tela estivesse em 96 unidades de DPI. Como alternativa, quando um thread com reconhecimento de DPI do Sistema estiver interagindo com uma exibição em um DPI diferente do que estava em uso quando a sessão do usuário atual foi iniciada, o Windows dimensionará algumas chamadas de API para o espaço de coordenadas que o HWND estaria usando se estivesse em execução em seu fator de escala de DPI original.
Quando você atualiza o aplicativo da área de trabalho para a escala de DPI corretamente, pode ser difícil saber quais chamadas à API podem retornar valores virtualizados com base no contexto do thread; no momento, essas informações não estão documentadas suficientemente pela Microsoft. Lembre-se de que, se você chamar qualquer API do sistema de um contexto de thread sem conhecimento de DPI ou com reconhecimento de DPI do sistema, o valor retornado poderá ser virtualizado. Dessa forma, verifique se o thread está em execução no contexto de DPI que você espera ao interagir com a tela ou janelas individuais. Ao alterar temporariamente o contexto de DPI de um thread usando SetThreadDpiAwarenessContext, certifique-se de restaurar o contexto antigo quando terminar de evitar causar comportamento incorreto em outro lugar do aplicativo.
Muitas APIs do Windows não têm um contexto de DPI
Muitas APIs herdadas do Windows não incluem um contexto DPI ou HWND como parte de sua interface. Como resultado, os desenvolvedores geralmente precisam fazer um trabalho adicional para lidar com o dimensionamento de qualquer informação confidencial de DPI, como tamanhos, pontos ou ícones. Por exemplo, os desenvolvedores que usam LoadIcon devem usar ícones de stretch loaded de bitmap ou usar APIs alternativas para carregar ícones de tamanho correto para o DPI apropriado, como LoadImage.
redefinição forçada de de reconhecimento de DPI em todo o processo
Em geral, o modo de reconhecimento de DPI do processo não pode ser alterado após a inicialização do processo. No entanto, o Windows poderá alterar à força o modo de reconhecimento de DPI do seu processo se você tentar quebrar o requisito de que todos os HWNDs em uma árvore de janela tenham o mesmo modo de reconhecimento de DPI. Em todas as versões do Windows, a partir do Windows 10 1703, não é possível ter HWNDs diferentes em uma execução de árvore HWND em diferentes modos de reconhecimento de DPI. Se você tentar criar uma relação filho-pai que interrompa essa regra, o reconhecimento de DPI de todo o processo poderá ser redefinido. Isso pode ser disparado por:
- Uma chamada CreateWindow em que a janela pai passada é de um modo de reconhecimento de DPI diferente do thread de chamada.
- Uma chamada SetParent em que as duas janelas estão associadas a diferentes modos de reconhecimento de DPI.
A tabela a seguir mostra o que acontece se você tentar violar essa regra:
Operação | Windows 8.1 | Windows 10 (1607 e anterior) | Windows 10 (1703 e posterior) |
---|---|---|---|
CreateWindow (In-Proc) | N/A | Filho herda (modo misto) | Filho herda (modo misto) |
CreateWindow (Cross-Proc) | de redefinição forçada (do processo do chamador) | Filho herda (modo misto) | de redefinição forçada (do processo do chamador) |
SetParent (In-Proc) | N/A | de redefinição forçada (do processo atual) | fail (ERROR_INVALID_STATE) |
SetParent (Cross-Proc) | de redefinição forçada (do processo da janela filho) | de redefinição forçada (do processo da janela filho) | de redefinição forçada (do processo da janela filho) |
Tópicos relacionados
de Referência de API de Alta DPI
Mixed-Mode APIs com reconhecimento de DPI e dimensionamento de DPI.