web-dev-qa-db-ja.com

システムフォントとdpi設定に自動スケーリングするWinFormsコードを記述する方法

Intro:「WinFormsはDPI /フォント設定にうまく自動スケーリングしないので、WPFに切り替えてください」というコメントがたくさんあります。ただし、これは.NET 1.1に基づいていると思います。彼らは実際に.NET 2.0で自動スケーリングを実装するかなり良い仕事をしたようです。少なくともこれまでの調査とテストに基づいています。しかし、もしあなた方の何人かがより良く知っているなら、私たちはあなたから聞いてみたいです。 (WPFに切り替えるべきだと主張することを気にしないでください...今は選択肢ではありません。)

質問:

  • WinFormsで何が適切に自動スケーリングされないため、避けるべきですか?

  • WinFormsコードを自動スケーリングするように記述する場合、プログラマはどの設計ガイドラインに従う必要がありますか?

これまでに特定した設計ガイドライン:

コミュニティWikiの回答 を参照してください。

それらのいずれかが間違っているか、不適切ですか?他に採用すべきガイドラインはありますか?回避する必要がある他のパターンはありますか?これに関する他のガイダンスをいただければ幸いです。

130
Brian Kennedy

適切にスケーリングをサポートしないコントロール:

  • AutoSize = FalseおよびLabelを含むFontが継承されます。コントロールにFontを明示的に設定して、[プロパティ]ウィンドウに太字で表示されるようにします。
  • ListView列の幅は拡大縮小されません。代わりに、フォームのScaleControlをオーバーライドします。 この回答 を参照してください
  • SplitContainerPanel1MinSizePanel2MinSizeおよびSplitterDistanceプロパティ
  • MultiLine = TrueおよびTextBoxを含むFontが継承されます。コントロールにFontを明示的に設定して、[プロパティ]ウィンドウに太字で表示されるようにします。
  • ToolStripButtonの画像。フォームのコンストラクターで:

    • ToolStrip.AutoSize = Falseを設定
    • ToolStrip.ImageScalingSizeおよびCreateGraphics.DpiXに従って.DpiYを設定します
    • 必要に応じてToolStrip.AutoSize = Trueを設定します。

    AutoSizeTrueに残すことができる場合もありますが、これらの手順を実行しないとサイズ変更に失敗する場合があります。 。NET Framework 4.5.2 およびEnableWindowsFormsHighDpiAutoResizingを使用すると、その変更なしで動作します。

  • TreeViewの画像。 ImageList.ImageSizeおよびCreateGraphics.DpiXに従って.DpiYを設定します。 StateImageList の場合、 。NET Framework 4.5.1 およびEnableWindowsFormsHighDpiAutoResizingを使用すると、変更なしで機能します。
  • Formのサイズ。固定サイズFormを作成後に手動でスケーリングします。

設計ガイドライン:

  • すべてのContainerControlsを同じAutoScaleMode = Fontに設定する必要があります。 (フォントはDPIの変更とシステムフォントサイズ設定の変更の両方を処理します。DPIはシステムフォントサイズ設定の変更ではなく、DPIの変更のみを処理します。)

  • 96dpiを想定して、すべてのContainerControlsもAutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);で設定する必要があります(次の箇条書きを参照)。これは、デザイナーを開いたDPIに基づいてデザイナーによって自動的に追加されますが、最も古いデザイナーファイルの多くにはありませんでした。おそらく、Visual Studio .NET(VS 2005より前のバージョン)が適切に追加していない可能性があります。

  • デザイナーの仕事はすべて96dpiで行います(120dpiに切り替えられるかもしれませんが、インターネットの知恵は96dpiに固執すると言います;実験はそこにあります;設計上、それはAutoScaleDimensions行を変更するだけなので問題ではありませんデザイナーが挿入する)。高解像度ディスプレイの仮想96dpiで実行するようにVisual Studioを設定するには、その.exeファイルを見つけて右クリックしてプロパティを編集し、互換性で「高DPIスケーリング動作をオーバーライドします。スケーリングの実行:システム」を選択します。

  • コンテナレベルでフォントを設定しないでください...リーフコントロールでのみ設定してください。 (コンテナのフォントを設定すると、そのコンテナの自動スケーリングがオフになっているようです。)

  • UserControlに固定されたアンカーRightまたはBottomを使用しないでください。その位置は自動スケーリングされません。代わりに、パネルまたは他のコンテナをユーザーコントロールにドロップし、他のコントロールをそのパネルに固定します。 UserControlでパネルにDock RightまたはDock Bottomを使用させます。

  • ResumeLayoutの最後にあるInitializeComponentが呼び出されると、Controlsリスト内のコントロールのみが自動スケーリングされます...コントロールを動的に追加する場合、追加する前にそのコントロールでSuspendLayout();AutoScaleDimensions = new SizeF(6F, 13F);AutoScaleMode = AutoScaleMode.Font;ResumeLayout();を実行する必要があります。また、DockモードまたはFlowLayoutPanelTableLayoutPanelなどのレイアウトマネージャーを使用していない場合も調整する必要があります。

  • ContainerControlから派生した基本クラスは、AutoScaleModeInheritに設定したままにする必要があります(クラスContainerControlで設定されたデフォルト値。ただし、デザイナーが設定したデフォルト値ではありません)。それを他の何かに設定し、派生クラスがそれをFontに設定しようとすると(必要に応じて)、それをFontに設定すると、デザイナーのAutoScaleDimensionsの設定がクリアされ、実際に自動スケーリングがオフになります! (このガイドラインと以前のガイドラインを組み合わせると、デザイナーで基本クラスをインスタンス化できないことを意味します。すべてのクラスを基本クラスまたはリーフクラスとして設計する必要があります!)

  • DesignerでForm.MaxSizeを静的に使用しないでください。 FormのMinSizeおよびMaxSizeは、他のすべてと同じようにスケーリングしません。したがって、すべての作業を96dpiで行う場合、DPIが高い場合、MinSizeが問題を引き起こすことはありませんが、期待したほど制限的ではないかもしれませんが、MaxSizeはサイズのスケーリングを制限し、問題を引き起こす可能性があります。 MinSize == Size == MaxSizeが必要な場合は、Designerで実行しないでください...コンストラクターまたはOnLoadオーバーライドで実行してください... MinSizeMaxSizeの両方を適切にスケーリングされたサイズに設定してください。

  • 特定のPanelまたはContainerのすべてのコントロールは、アンカーまたはドッキングを使用する必要があります。それらを混ぜると、そのPanelによって行われる自動スケーリングは、しばしば微妙な奇妙な方法で誤動作します。

107
kjbartel

私の経験は、現在のトップ投票の回答とはかなり異なっています。 .NETフレームワークのコードをステップ実行し、参照ソースコードを熟読することで、自動スケーリングが機能するためのすべての場所が整っていると結論付けました。これは真実であることが判明しました。

適切にリフロー/自動サイズ調整されたレイアウトを作成すると、Visual Studioで使用される既定の設定(つまり、親フォームではAutoSizeMode = Font、その他すべてでは継承)を使用して、ほぼすべてが自動的に適切に機能します。

唯一の落とし穴は、デザイナーでフォームにFontプロパティを設定した場合です。生成されたコードは、割り当てをアルファベット順にソートします。つまり、AutoScaleDimensionsbeforeFontに割り当てられます。残念ながら、これはWinFormsの自動スケーリングロジックを完全に破壊します。

ただし、修正は簡単です。デザイナでFontプロパティをまったく設定しない(フォームコンストラクタで設定する)か、これらの割り当てを手動で並べ替えます(ただし、デザイナでフォームを編集するたびにこれを行う必要があります)。ほら、最小限の手間でほぼ完璧で完全に自動スケーリング。フォームのサイズも正しくスケーリングされます。


ここで、既知の問題に遭遇したときにリストします。

  • ネストされたTableLayoutPanel制御マージンを誤って計算します 。マージンとパディングを完全に回避する、またはネストされたテーブルレイアウトパネルを回避する以外に、回避策はありません。
23
Roman Starkov

.Net Framework 4.7のアプリケーションをターゲットにし、Windows 10 v1703(Creators Update Build 15063)で実行します。 Windows 10(v1703)での.NET 4.7では、MSはDPIを大幅に改善しました

.NET Framework 4.7以降、Windowsフォームには、一般的な高DPIおよび動的DPIシナリオの機能強化が含まれています。これらには以下が含まれます。

  • MonthCalendarコントロールやCheckedListBoxコントロールなど、多数のWindowsフォームコントロールのスケーリングとレイアウトの改善。

  • シングルパススケーリング。 .NET Framework 4.6以前のバージョンでは、スケーリングは複数のパスを介して実行されたため、一部のコントロールは必要以上にスケーリングされていました。

  • Windowsフォームアプリケーションの起動後にユーザーがDPIまたはスケールファクターを変更する動的DPIシナリオのサポート。

これをサポートするには、アプリケーションにアプリケーションマニフェストを追加し、アプリがWindows 10をサポートしていることを通知します。

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

次に、app.configを追加して、アプリごとにモニター対応を宣言します。 これはapp.configで行われ、以前のようなマニフェストでは行われません!

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

この PerMonitorV2 は、Windows 10 Creators Update以降の新機能です。

DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2

Per Monitor v2とも呼ばれます。元のモニターごとのDPI認識モードに対する進歩。これにより、アプリケーションは、トップレベルウィンドウごとに新しいDPI関連のスケーリング動作にアクセスできます。

  • 子ウィンドウDPI変更通知-Per Monitor v2コンテキストでは、発生するDPI変更がウィンドウツリー全体に通知されます。

  • 非クライアント領域のスケーリング-すべてのウィンドウには、非クライアント領域がDPIに敏感な方法で自動的に描画されます。 EnableNonClientDpiScalingの呼び出しは不要です。

  • SWin32メニューのスケーリング-Per Monitor v2コンテキストで作成されたすべてのNTUSERメニューは、モニターごとにスケーリングされます。

  • ダイアログのスケーリング-Per Monitor v2コンテキストで作成されたWin32ダイアログは、DPIの変更に自動的に応答します。

  • comctl32コントロールのスケーリングの改善-さまざまなcomctl32コントロールにより、Per Monitor v2コンテキストでのDPIスケーリング動作が改善されました。

  • テーマの動作の改善-Per Monitor v2ウィンドウのコンテキストで開かれたUxThemeハンドルは、そのウィンドウに関連付けられたDPIの観点から動作します。

これで、3つの新しいイベントをサブスクライブして、DPIの変更に関する通知を受け取ることができます。

  • Control.DpiChangedAfterParent、これは、コントロールの親コントロールまたはフォームのDPI変更イベントが発生した後、コントロールのDPI設定がプログラムで変更されたときに発生します。

  • Control.DpiChangedBeforeParent。これは、親コントロールまたはフォームのDPI変更イベントが発生する前に、コントロールのDPI設定がプログラムによって変更されたときに発生します。

  • Form.DpiChanged。フォームが現在表示されているディスプレイデバイスでDPI設定が変更されると起動されます。

DPIの処理/スケーリングに関する3つのヘルパーメソッドもあります。

  • Control.LogicalToDeviceUnits。値を論理ピクセルからデバイスピクセルに変換します。

  • Control.ScaleBitmapLogicalToDevice。ビットマップイメージをデバイスの論理DPIにスケーリングします。

  • Control.DeviceDpi。現在のデバイスのDPIを返します。

それでも問題が解決しない場合は、 app.configエントリを介したDPI改善のオプトアウト を使用できます。

ソースコードにアクセスできない場合は、Windowsエクスプローラーでアプリケーションプロパティに移動し、互換性に移動してSystem (Enhanced)を選択できます

enter image description here

GDIスケーリングを有効にして、DPI処理も改善します。

GDIベースのアプリケーションの場合、WindowsはモニターごとにDPIをスケーリングできるようになりました。これは、これらのアプリケーションが魔法のようにモニターごとのDPIに対応することを意味します。

これらすべての手順を実行すると、WinFormsアプリケーションのDPIエクスペリエンスが向上します。ただし、.net 4.7のアプリを対象とし、少なくともWindows 10 Build 15063(Creators Update)が必要であることを忘れないでください。次のWindows 10 Update 1709では、さらに改善される可能性があります。

17
magicandre1981

仕事で書いたガイド:

WPFは「デバイスに依存しないユニット」で動作します。つまり、すべてのコントロールが高dpi画面に完全に対応します。 WinFormsでは、さらに注意が必要です。

WinFormsはピクセル単位で動作します。テキストはシステムdpiに従ってスケーリングされますが、スケーリングされていないコントロールによってトリミングされることがよくあります。このような問題を回避するには、明示的なサイズ設定と配置を避ける必要があります。次のルールに従ってください:

  1. どこででも(ラベル、ボタン、パネル)AutoSizeプロパティをTrueに設定します。
  2. レイアウトには、バニラパネルではなく、FlowLayoutPanel(WPF StackPanelに類似)およびTableLayoutPanel(WPFグリッドに類似)をレイアウトに使用します。
  3. 高dpiのマシンで開発している場合、Visual Studioデザイナーはフラストレーションを感じることがあります。 AutoSize = Trueを設定すると、コントロールのサイズが画面に合わせて変更されます。コントロールにAutoSizeMode = GrowOnlyがある場合、通常のdpiのユーザーに対してはこのサイズのままになります。予想よりも大きくなります。これを修正するには、通常のdpiのコンピューターでデザイナーを開き、右クリックしてリセットします。
12
Colonel Panic

WinFormsでニースを高DPIでプレイするのは非常に難しいことがわかりました。そこで、フォームの動作をオーバーライドするVB.NETメソッドを作成しました。

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
8
user3950597

最近、この問題に遭遇しました。特に、エディターが高dpiシステムで開かれたときのVisual Studioの再スケーリングとの組み合わせで。 keepAutoScaleMode = Fontが最善であることがわかりましたが、Forms Fontをデフォルトのフォントに設定しますが、ピクセル単位でサイズを指定、ポイントではなく、すなわち:Font = MS Sans; 11px。コードでは、I thenフォントをデフォルトにリセットします:Font = SystemFonts.DefaultFontで問題ありません。

ちょうど私の2セント。 「AutoScaleMode = Fontを維持」、および「デザイナーのピクセル単位でフォントサイズを設定」私はインターネットで見つけませんでした。

ブログに詳細があります: http://www.sgrottel.de/?p=1581&lang=en

5
Knowleech

アンカーがうまく機能しないことに加えて、さらに一歩進んで、正確な配置(別名、Locationプロパティを使用)はフォントスケーリングではあまりうまく機能しないと言います。 2つの異なるプロジェクトでこの問題に対処する必要がありました。どちらの場合も、すべてのWinFormsコントロールの配置を、TableLayoutPanelとFlowLayoutPanelを使用するように変換する必要がありました。 TableLayoutPanel内でDock(通常はFillに設定)プロパティを使用すると非常にうまく機能し、システムフォントDPIでうまくスケーリングされます。

4
Brannon