Como escrever o código WinForms que é dimensionado automaticamente para as configurações de fonte e dpi do sistema?

143

Introdução: Existem muitos comentários por aí que dizem "O WinForms não faz a escala automática das configurações de DPI / fonte; mude para o WPF". No entanto, acho que é baseado no .NET 1.1; parece que eles realmente fizeram um bom trabalho ao implementar o dimensionamento automático no .NET 2.0. Pelo menos com base em nossas pesquisas e testes até agora. No entanto, se alguns de vocês souberem melhor, adoraríamos ouvi-lo. (Por favor, não se preocupe em argumentar que devemos mudar para o WPF ... essa não é uma opção no momento.)

Questões:

  • O que no WinForms NÃO é dimensionado automaticamente corretamente e, portanto, deve ser evitado?

  • Quais diretrizes de design os programadores devem seguir ao escrever o código WinForms para que ele seja dimensionado automaticamente?

Diretrizes de design que identificamos até agora:

Veja a resposta do wiki da comunidade abaixo.

Alguns deles estão incorretos ou inadequados? Quaisquer outras diretrizes que devemos adotar? Existem outros padrões que precisam ser evitados? Qualquer outra orientação sobre isso seria muito apreciada.

Brian Kennedy
fonte

Respostas:

127

Controles que não oferecem suporte à escala corretamente:

  • Labelcom AutoSize = Falsee Fontherdado. Definido explicitamenteFonto controle para que ele apareça em negrito na janela Propriedades.
  • ListViewas larguras das colunas não são dimensionadas. Substitua os formulários ScaleControlpara fazê-lo. Veja esta resposta
  • SplitContainer's Panel1MinSize, Panel2MinSizee SplitterDistancepropriedades
  • TextBoxcom MultiLine = Truee Fontherdado. Defina explicitamente Fonto controle para que ele apareça em negrito na janela Propriedades.
  • ToolStripButtonimagem de. No construtor do formulário:

    • Conjunto ToolStrip.AutoSize = False
    • Defina de ToolStrip.ImageScalingSizeacordo com CreateGraphics.DpiXe.DpiY
    • Defina ToolStrip.AutoSize = Truese necessário.

    Às vezes, AutoSizepode ser deixado em, Truemas às vezes falha ao redimensionar sem essas etapas. Funciona sem que isso mude com o .NET Framework 4.5.2 e EnableWindowsFormsHighDpiAutoResizing.

  • TreeViewimagens de. Defina de ImageList.ImageSizeacordo com CreateGraphics.DpiXe .DpiY. Para StateImageList, funciona sem que isso mude com o .NET Framework 4.5.1 e EnableWindowsFormsHighDpiAutoResizing.
  • Formtamanho. Dimensionar tamanho fixo Formmanualmente após a criação.

Diretrizes de design:

  • Todos os ContainerControls devem ser definidos para o mesmo AutoScaleMode = Font. (A fonte manipulará as alterações de DPI e as alterações na configuração do tamanho da fonte do sistema; o DPI manipulará apenas as alterações de DPI, não as alterações na configuração do tamanho da fonte do sistema.)

  • Todos os ContainerControls também devem ser definidos com o mesmo AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);, assumindo 96 dpi (veja o próximo marcador) e a fonte padrão do MS Sans Serif (veja o marcador dois abaixo). Isso é adicionado automaticamente pelo designer com base no DPI no qual você abre o designer ... mas estava ausente em muitos dos nossos arquivos de designer mais antigos. Talvez o Visual Studio .NET (a versão anterior ao VS 2005) não estivesse adicionando isso corretamente.

  • Faça todo o seu trabalho de designer em 96dpi (poderemos mudar para 120dpi; mas a sabedoria na internet diz que adere a 96dpi; a experimentação está em ordem; por design, isso não deve importar, pois apenas altera a AutoScaleDimensionslinha que o designer insere). Para definir o Visual Studio para ser executado em 96dpi virtual em uma tela de alta resolução, localize seu arquivo .exe, clique com o botão direito do mouse para editar as propriedades e, em Compatibilidade, selecione "Substituir o comportamento de dimensionamento de alta DPI. Dimensionamento realizado por: Sistema".

  • Certifique-se de nunca definir a fonte no nível do contêiner ... apenas nos controles de folha OU no construtor do formulário mais básico, se desejar uma fonte padrão para todo o aplicativo que não seja o MS Sans Serif. (A configuração da fonte em um contêiner parece desativar o dimensionamento automático desse contêiner porque vem em ordem alfabética após as configurações das configurações AutoScaleMode e AutoScaleDimensions.) NOTA: se você alterar a fonte no construtor do formulário mais básico, isso causará suas AutoScaleDimensions para calcular de maneira diferente de 6x13; em particular, se você mudar para a interface do usuário do Segoe (a fonte padrão do Win 10), será 7x15 ... precisará tocar em todos os formulários do Designer para que possa recalcular todas as dimensões desse arquivo .designer, incluindo o AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);.

  • NÃO use Anchor Rightou Bottomancorado a um UserControl ... seu posicionamento não será dimensionado automaticamente; em vez disso, solte um painel ou outro contêiner no seu UserControl e ancore seus outros controles nesse painel; ter o uso Painel Doca Right, Bottomou Fillem seu UserControl.

  • Somente os controles nas listas Controles, quando ResumeLayoutno final da InitializeComponentchamada, serão dimensionados automaticamente ... se você adicionar controles dinamicamente, precisará SuspendLayout(); AutoScaleDimensions = new SizeF(6F, 13F); AutoScaleMode = AutoScaleMode.Font; ResumeLayout();desse controle antes de adicioná-lo. E seu posicionamento também precisará ser ajustado se você não estiver usando os modos Dock ou um Gerenciador de layout como FlowLayoutPanelou TableLayoutPanel.

  • As classes base derivadas de ContainerControldevem deixar AutoScaleModedefinido como Inherit(o valor padrão definido na classe ContainerControl; mas NÃO o padrão definido pelo designer). Se você configurá-lo para qualquer outra coisa e, em seguida, sua classe derivada tentar defini-la como Font (como deveria), o ato de definir isso para Fontlimpar a configuração do designer AutoScaleDimensions, resultando na desativação do auto-scaling! (Essa diretriz combinada com a anterior significa que você nunca pode instanciar classes base em um designer ... todas as classes precisam ser projetadas como classes base ou como folhas!)

  • Evite usar Form.MaxSizeestaticamente / no Designer. MinSizee MaxSizeno formulário não escalam tanto quanto todo o resto. Portanto, se você fizer todo o seu trabalho em 96 dpi, quando estiver em um DPI mais alto MinSize, não causará problemas, mas poderá não ser tão restritivo quanto o esperado, mas MaxSizepoderá limitar a escala do tamanho, o que pode causar problemas. Se você quiser MinSize == Size == MaxSize, não faça isso no Designer ... faça isso no seu construtor ou OnLoadsubstitua ... defina ambos MinSizee MaxSizepara o tamanho em escala adequada.

  • Todos os controles de um determinado Panelou Containerdevem usar ancoragem ou ancoragem. Se você as misturar, o dimensionamento automático feito por isso Panelgeralmente se comportará mal de maneiras bizarras e sutis.

  • Ao fazer o auto-dimensionamento, ele tentará dimensionar o formulário geral ... no entanto, se nesse processo for executado no limite superior do tamanho da tela, esse é um limite rígido que poderá estragar (clipe) a escala. Portanto, verifique se todos os formulários no Designer a 100% / 96dpi têm tamanho não superior a 1024x720 (o que corresponde a 150% em uma tela de 1080p ou 300%, que é o valor recomendado pelo Windows em uma tela de 4K). Mas você precisa subtrair a barra de título / legenda gigante do Win10 ... mais parecida com o tamanho máximo de 1000x680 ... que no designer será como 994x642 ClientSize. (Portanto, você pode fazer uma FindAll References no ClientSize para encontrar violadores.)

kjbartel
fonte
NumericUpDowntambém não dimensiona Marginadequadamente. Parece que a margem é redimensionada duas vezes. Se eu escalar de volta uma vez, parece bom.
ygoe 16/06
AutoScaleMode = Fontnão funciona bem para usuários que usam uma fonte muito grande e com Ubuntu. Nós preferimos #AutoScaleMode = DPI
KindDragon 27/10
> TextBox com MultiLine = True e Font herdado. Enlouquecer o dia todo - essa foi a solução! Muito obrigado! A propósito, a mesma correção também é a correção para os controles ListBox. : D
neminem
Para mim, caixas de listagem com fonte herdada não são bem dimensionadas. Eles fazem depois de definir explicitamente. (.NET 4.7)
PulseJet
27

Minha experiência foi bastante diferente da atual resposta votada. Ao percorrer o código da estrutura .NET e ler atentamente o código fonte de referência, concluí que tudo está pronto para o dimensionamento automático funcionar, e havia apenas um problema sutil em algum lugar que atrapalhava. Isso acabou sendo verdade.

Se você criar um layout com refluxo / tamanho automático, quase tudo funcionará exatamente como deveria, automaticamente, com as configurações padrão usadas pelo Visual Studio (a saber, AutoSizeMode = Font no formulário pai e Herdar em todo o resto).

A única opção é se você definiu a propriedade Font no formulário no designer. O código gerado classificará as atribuições em ordem alfabética, o que significa que AutoScaleDimensionsserá atribuído antes Font . Infelizmente, isso quebra completamente a lógica de dimensionamento automático do WinForms.

A correção é simples. Não defina a Fontpropriedade no designer (defina-a no construtor de formulários) ou reordene manualmente essas atribuições (mas você deverá continuar fazendo isso sempre que editar o formulário no designer). Voila, escala quase perfeita e totalmente automática com o mínimo de problemas. Até os tamanhos dos formulários são redimensionados corretamente.


Vou listar problemas conhecidos aqui quando os encontrar:

Roman Starkov
fonte
1
Não definindo Fontno designer: Um pensamento vem à mente: vá em frente e defina a fonte no designer, para que você possa projetar com a fonte desejada. ENTÃO no construtor, após o layout, leu a propriedade da fonte e defina o mesmo valor novamente? Ou talvez apenas peça que o layout seja feito novamente? [Advertência: não tive motivos para testar essa abordagem.] Ou, de acordo com a resposta da Knowleech , no designer especifique em pixels (para que o designer do Visual Studio não faça nova escala no monitor de alto DPI), e no código leia esse valor, converta de pixels para pontos (para obter o dimensionamento correto).
Home
1
Cada bit do nosso código tem as dimensões de escala automática definidas antes do modo de escala automática e tudo é perfeitamente dimensionado. Parece que o pedido não importa na maioria dos casos.
23418 Josh
Eu procurei no meu código por instâncias em que AutoScaleDimensionsnão estava definido new SizeF(6F, 13F)como o recomendado na resposta superior. Descobriu-se que, em todas as instâncias, a propriedade Font do formulário havia sido definida (não padrão). Parece que quando AutoScaleMode = Font, em seguida, AutoScaleDimensionsé calculada com base na propriedade de fonte do formulário. Além disso, a configuração de dimensionamento no painel de controle do Windows parece afetar AutoScaleDimensions.
Walter Stabosz 28/07/19
24

Alveje seu aplicativo para .Net Framework 4.7 e execute-o no Windows 10 v1703 (Build Creators Update 15063). Com o .Net 4.7 no Windows 10 (v1703), a MS fez muitas melhorias de DPI .

Começando com o .NET Framework 4.7, o Windows Forms inclui aprimoramentos para cenários comuns de alto DPI e dinâmico de DPI. Esses incluem:

  • Aprimoramentos na escala e no layout de vários controles do Windows Forms, como o controle MonthCalendar e o controle CheckedListBox.

  • Escala de passagem única. No .NET Framework 4.6 e versões anteriores, o dimensionamento era realizado por meio de várias passagens, o que fazia com que alguns controles fossem dimensionados mais do que o necessário.

  • Suporte para cenários de DPI dinâmicos nos quais o usuário altera o DPI ou o fator de escala após o lançamento de um aplicativo Windows Forms.

Para suportá-lo, adicione um manifesto de aplicativo ao seu aplicativo e sinalize que ele oferece suporte ao Windows 10:

<compatibility xmlns="urn:schemas-microsoft.comn:compatibility.v1">
    <application>
        <!-- Windows 10 compatibility -->
        <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
    </application>
</compatibility>

Em seguida, adicione app.confige declare o aplicativo por monitor consciente. Agora isso é feito no app.config e NÃO no manifesto como antes!

<System.Windows.Forms.ApplicationConfigurationSection>
   <add key="DpiAwareness" value="PerMonitorV2" />
</System.Windows.Forms.ApplicationConfigurationSection> 

Este PerMonitorV2 é novo desde a Atualização do Windows 10 Creators:

DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2

Também conhecido como Per Monitor v2. Um avanço sobre o modo de reconhecimento de DPI por monitor original, que permite que os aplicativos acessem novos comportamentos de dimensionamento relacionados a DPI em uma janela por nível superior.

  • Notificações de alteração de DPI da janela filho - Nos contextos do Monitor v2, toda a árvore da janela é notificada de qualquer alteração de DPI que ocorra.

  • Dimensionamento da área não cliente - Todas as janelas terão automaticamente sua área não cliente desenhada de maneira sensível ao DPI. As chamadas para EnableNonClientDpiScaling são desnecessárias.

  • S caling de menus Win32 - menus Todos NTUSER criados em contextos por monitor v2 será escalar de forma per-monitor.

  • Dimensionamento da caixa de diálogo - as caixas de diálogo Win32 criadas nos contextos Per Monitor v2 responderão automaticamente às alterações de DPI.

  • Escala aprimorada dos controles comctl32 - Vários controles comctl32 aprimoraram o comportamento da escala de DPI nos contextos Per Monitor v2.

  • Comportamento de temas aprimorado - os identificadores do UxTheme abertos no contexto de uma janela Per Monitor v2 funcionarão em termos do DPI associado a essa janela.

Agora você pode se inscrever em 3 novos eventos para ser notificado sobre alterações de DPI:

  • Control.DpiChangedAfterParent , que é acionado Ocorre quando a configuração de DPI para um controle é alterada programaticamente após a ocorrência de um evento de alteração de DPI para seu controle ou formulário pai.

  • Control.DpiChangedBeforeParent , que é acionado quando a configuração de DPI para um controle é alterada programaticamente antes que ocorra um evento de alteração de DPI para seu controle ou formulário pai.

  • Form.DpiChanged , que é acionado quando a configuração de DPI muda no dispositivo de exibição em que o formulário está sendo exibido no momento.

Você também tem três métodos auxiliares sobre manipulação / dimensionamento de DPI:

  • Control.LogicalToDeviceUnits , que converte um valor de pixels lógicos em pixels do dispositivo.

  • Control.ScaleBitmapLogicalToDevice , que dimensiona uma imagem de bitmap para o DPI lógico de um dispositivo.

  • Control.DeviceDpi , que retorna o DPI para o dispositivo atual.

Se você ainda encontrar problemas, poderá desativar as melhorias de DPI através das entradas app.config .

Se você não tiver acesso ao código-fonte, poderá acessar as propriedades do aplicativo no Windows Explorer, acessar a compatibilidade e selecionar System (Enhanced)

insira a descrição da imagem aqui

que ativa a escala de GDI para melhorar também o tratamento de DPI:

Para aplicativos baseados no GDI, o Windows agora pode escaloná-los por DPI, por monitor. Isso significa que esses aplicativos, magicamente, se tornarão compatíveis com DPI por monitor.

Execute todas essas etapas e obtenha uma melhor experiência de DPI para aplicativos WinForms. Mas lembre-se de que você precisa direcionar seu aplicativo para .net 4.7 e precisa pelo menos do Windows 10 Build 15063 (Atualização de criadores). Na próxima atualização do Windows 10 1709, poderemos obter mais melhorias.

magicandre1981
fonte
12

Um guia que escrevi no trabalho:

O WPF funciona em 'unidades independentes de dispositivo', o que significa que todos os controles são dimensionados perfeitamente para telas de alta resolução. No WinForms, é preciso mais cuidado.

WinForms funciona em pixels. O texto será redimensionado de acordo com o dpi do sistema, mas geralmente será cortado por um controle não redimensionado. Para evitar esses problemas, você deve evitar o dimensionamento e o posicionamento explícitos. Siga estas regras:

  1. Onde quer que você o encontre (rótulos, botões, painéis), defina a propriedade AutoSize como True.
  2. Para o layout, use FlowLayoutPanel (no WPF StackPanel) e TableLayoutPanel (no WPF Grid) para o layout, em vez do painel baunilha.
  3. Se você estiver desenvolvendo em uma máquina de alta resolução, o designer do Visual Studio pode ser uma frustração. Quando você define AutoSize = True, ele redimensiona o controle para sua tela. Se o controle tiver AutoSizeMode = GrowOnly, ele permanecerá desse tamanho para pessoas em dpi normal, ou seja. ser maior que o esperado. Para corrigir isso, abra o designer em um computador com dpi normal e clique com o botão direito do mouse em redefinir.
Coronel Panic
fonte
3
para caixas de diálogo que podem ser redimensionadas, o AutoSize em tudo seria um pesadelo, não quero que meus botões fiquem maiores e menores à medida que aumenta manualmente o tamanho das caixas de diálogo durante a execução do programa.
23418 Josh
10

Achei muito difícil fazer com que o WinForms funcionasse bem com alto DPI. Então, eu escrevi um método VB.NET para substituir o comportamento do formulário:

Public Shared Sub ScaleForm(WindowsForm As System.Windows.Forms.Form)
    Using g As System.Drawing.Graphics = WindowsForm.CreateGraphics
        Dim sngScaleFactor As Single = 1
        Dim sngFontFactor As Single = 1
        If g.DpiX > 96 Then
            sngScaleFactor = g.DpiX / 96
            'sngFontFactor = 96 / g.DpiY
        End If
        If WindowsForm.AutoScaleDimensions = WindowsForm.CurrentAutoScaleDimensions Then
            'ucWindowsFormHost.ScaleControl(WindowsForm, sngFontFactor)
            WindowsForm.Scale(sngScaleFactor)
        End If
    End Using
End Sub
jrh
fonte
6

Recentemente, deparei-me com esse problema, especialmente em combinação com o redimensionamento do Visual Studio quando o editor é aberto no sistema de alta resolução. Eu achei melhor para manter AutoScaleMode = Font , mas para definir as formas de fonte para a fonte padrão, mas especificar o tamanho em pixels , não ponto, ou seja: Font = MS Sans; 11px. No código, que , em seguida, redefinir a fonte com o padrão: Font = SystemFonts.DefaultFonte tudo está bem.

Apenas meus dois centavos. Eu pensei em compartilhar, porque "manter AutoScaleMode = Font" e "Definir tamanho da fonte em pixel para o Designer" foi algo que não encontrei na internet.

Tenho mais alguns detalhes no meu blog: http://www.sgrottel.de/?p=1581&lang=pt

Knowleech
fonte
4

Além das âncoras não funcionarem muito bem: eu daria um passo adiante e diria que o posicionamento exato (aka, usando a propriedade Location) não funciona muito bem com o dimensionamento da fonte. Eu tive que resolver esse problema em dois projetos diferentes. Nos dois, tivemos que converter o posicionamento de todos os controles WinForms para usar o TableLayoutPanel e FlowLayoutPanel. O uso da propriedade Dock (geralmente definida como Fill) dentro do TableLayoutPanel funciona muito bem e se adapta bem à DPI do tipo de letra do sistema.

Brannon
fonte