web-dev-qa-db-ja.com

.NETWinFormsアプリでフォントDPIを制御する方法

中小企業向けのアプリを作成しました。オフィスの一部の従業員はフォームを正しく見ることができません。その理由は、DPI設定が96dpi以上に設定されているためです。誰かがこれを制御する方法を知っていますか?

Winformsアプリの経験があるすべての人にとって、DPIがアプリケーションの外観に影響を与えないようにフォームレイアウトをどのように制御しますか?

32

ユーザーのUIフォントの選択(SystemFonts.IconTitleFont)を尊重せず、フォームを1つのフォントサイズ(Tahoma 8pt、Microsoft Sans Serif 8.25ptなど)のみにハードコーディングすると仮定すると、フォームのAutoScaleModeから_ScaleMode.Dpi_。

これにより、フォームのサイズがスケーリングされ、その子が制御するほとんどは、Form.Scale()を呼び出すことにより、係数_CurrentDpiSetting / 96_によって制御され、保護されたものが呼び出されます。 ScaleControl()メソッドはそれ自体とすべての子コントロールで再帰的に実行されます。 ScaleControlは、新しい倍率の必要に応じて、コントロールの位置、サイズ、フォントなどを増やします。

警告:すべてのコントロールが適切にスケーリングされるわけではありません。たとえば、リストビューの列は、フォントが大きくなっても広くなりません。これを処理するには、必要に応じて手動で追加のスケーリングを実行する必要があります。これを行うには、保護されたScaleControl()メソッドをオーバーライドし、リストビュー列を手動でスケーリングします。

_public class MyForm : Form
{
   protected override void ScaleControl(SizeF factor, BoundsSpecified specified)
   {
      base.ScaleControl(factor, specified);
      Toolkit.ScaleListViewColumns(listView1, factor);
   }
}

public class Toolkit  
{
   /// <summary>
   /// Scale the columns of a listview by the Width scale factor specified in factor
   /// </summary>
   /// <param name="listview"></param>
   /// <param name="factor"></param>
   /// <example>/*
   /// protected override void ScaleControl(SizeF factor, BoundsSpecified specified)
   /// {
   ///    base.ScaleControl(factor, specified);
   ///    
   ///    //ListView columns are not automatically scaled with the ListView, so we
   ///    //must do it manually
   ///    Toolkit.ScaleListViewColumns(lvPermissions, factor);
   /// }
   ///</example>
   public static void ScaleListViewColumns(ListView listview, SizeF factor)
   {
      foreach (ColumnHeader column in listview.Columns)
      {
          column.Width = (int)Math.Round(column.Width * factor.Width);
      }
   }
}
_

コントロールを使用しているだけであれば、これはすべてうまくいきます。ただし、ハードコードされたピクセルサイズを使用する場合は、フォームの現在の倍率でピクセルの幅と長さをスケーリングする必要があります。ハードコードされたピクセルサイズを持つ可能性のある状況のいくつかの例:

  • 高さ25pxの長方形を描く
  • フォームの場所(11,56)に画像を描画する
  • アイコンを48x48にストレッチ描画
  • microsoft Sans Serif8.25ptを使用してテキストを描画する
  • アイコンの32x32形式を取得し、PictureBoxに詰め込みます

この場合、ハードコードされた値を「現在のスケーリング係数」でスケーリングする必要があります。残念ながら、「現在の」スケール係数は提供されていないため、自分で記録する必要があります。解決策は、最初はスケーリング係数が1.0であり、ScaleControl()が呼び出されるたびに、実行中のスケール係数を新しい係数で変更すると想定することです。

_public class MyForm : Form
{
   private SizeF currentScaleFactor = new SizeF(1f, 1f);

   protected override void ScaleControl(SizeF factor, BoundsSpecified specified)
   {
      base.ScaleControl(factor, specified);

      //Record the running scale factor used
      this.currentScaleFactor = new SizeF(
         this.currentScaleFactor.Width * factor.Width,
         this.currentScaleFactor.Height * factor.Height);

      Toolkit.ScaleListViewColumns(listView1, factor);
   }
}
_

最初のスケーリング係数は_1.0_です。次にフォームが_1.25_でスケーリングされると、スケーリング係数は次のようになります。

_1.00 * 1.25 = 1.25    //scaling current factor by 125%
_

次にフォームが_0.95_でスケーリングされると、新しいスケーリング係数は次のようになります。

_1.25 * 0.95 = 1.1875  //scaling current factor by 95%
_

(単一の浮動小数点値ではなく)SizeFが使用される理由は、スケーリング量がx方向とy方向で異なる可能性があるためです。フォームが_ScaleMode.Font_に設定されている場合、フォームは新しいフォントサイズに拡大縮小されます。フォントのアスペクト比は異なる場合があります(例:Segoe UITahomaよりも背の高いフォントです)。つまり、x値とy値を個別にスケーリングする必要があります。

したがって、位置_(11,56)_にコントロールを配置する場合は、位置コードを次のように変更する必要があります。

_Point pt = new Point(11, 56);
control1.Location = pt;
_

_Point pt = new Point(
      (int)Math.Round(11.0*this.scaleFactor.Width),
      (int)Math.Round(56.0*this.scaleFactor.Height));
control1.Location = pt;
_

フォントサイズを選択する場合も同様です。

_Font f = new Font("Segoe UI", 8, GraphicsUnit.Point);
_

次のようになる必要があります:

_Font f = new Font("Segoe UI", 8.0*this.scaleFactor.Width, GraphicsUnit.Point);
_

また、32x32アイコンをビットマップに抽出すると、次のように変更されます。

_Image i = new Icon(someIcon, new Size(32, 32)).ToBitmap();
_

_Image i = new Icon(someIcon, new Size(
     (int)Math.Round(32.0*this.scaleFactor.Width), 
     (int)Math.Round(32.0*this.scaleFactor.Height))).ToBitmap();
_

等.

非標準のDPIディスプレイのサポートは すべての開発者が支払うべき税金 です。しかし、誰も望んでいないという事実が理由です Microsoftは、グラフィックカードが高dpiを適切に処理するとは言わないアプリケーションを拡張する機能を放棄し、Vistaに追加しました

63
Ian Boyd

グローバル検索/置換を介してAutoScaleModeをすべての場所(つまり、すべてのUserControls)に継承するように設定してから、メインフォームでAutoScaleModeをDpiに設定します。

また、このタイプの状況では、レイアウトコンテナがアンカーよりもうまく機能することもわかりました。

16
Nick

やや抜本的だとは思いますが、アプリをWPFで書き直すことを検討してください。 WPFアプリケーションは、すべてのDPI設定で同じ外観になります。

2
Guildenstern70