カテゴリー
Software Tech

[C#備忘録]高DPIへの対応(目標未達) | AmpiTa Project

 AmpiTa(多用途安否確認システム)の画面表示にズレが生じていることがわかり、その原因を追究しました。

 テキストボックスやボタンの位置が思わぬ場所へ行ってしまうため、これを直さないと使い物にならないことは自明です。

 原因と対策について備忘録を残しますが、まだ完全に制圧できたわけではありません。これから様々な専門家の先生方のご意見を賜り、解決策をみつけたいと思います。




症状

 下図はAmpiTaのメイン画面の左上の方です。差分取得と書かれた機能ドロップダウンメニューの下にメッセージが表示されています。


 この体裁が崩れると、下図のようにタブコントロールが上にせり上がって来て、他のコントロールと重なってしまいます。


 上図の例であれば見づらいだけで、機能としては使えるのですが、もっとズレて致命的なエラーとなっているケースも発見されたので表示設定を見直すことになりました。


 正常であると下図のように上段にドロップダウンメニューやボタン、その下に一覧表などが表示されるようになっています。上図では重なってしまっているので処理に困ります。




座標指示はLocation

 表示位置がずれることが課題でしたが、そもそもコントロールの表示位置はどのように決定するかというと『Location』の値で決まります。

 2つの数字はXとY、画面やフォームなど基準となる場所において、コントロールの左端と上端をどの位置にするのかInt形式で指示します。


 デザイン画面でコントロールをマウスで動かして指示する方法、プロパティの値を入力する方法、コード上で指示する方法があります。

tabControl1.Location = new Point (0,49)




指示は確かであったか!?

 表示位置は指定するだけではなく、取得することもできます。

 Console.WriteLine("左からの位置" + "\t" + tabControl1.Location.X)
 Console.WriteLine("上からの位置" + "\t" + tabControl1.Location.Y)

 上記コードで確認してみたところ、設定値と同じ値を返してきました。

 開発環境では問題かなったが、古いパソコンではどうであるか確認するために新たなコードを追加し、10年以上前に調達した画面サイズの小さいノートパソコンで試してみました。


 左端の座標は0のままでしたが、上端からの座標は 73.5 になっていました。元の設定が 49 でしたのでちょうど 1.5 倍です。




150%

 Windowsのシステム設定の中には『ディスプレイ』という項目があります。

 その名の通り画面表示を設定することができます。

 画面表示は様々な制御を加えられていますが、特に設定をしていない場合は『推奨』という設定になっています。

 ノートパソコンであれば、買った時にカタログに書いてあった解像度が適用されていると思います。

 最近ですと『4K/UHD』という3840×2160の機種もありますが、『WQHD』(2560×1440)や『WQXGA』(2560×1600)というパソコンは店頭でも見かけるのではないかと思います。

 少し前ですと『2K/FHD(Full-HD)』(1920×1080)を売りにしている機種もよく見かけました。その前になると『SXGA』(1280×1024)や『WXGA』(1280×800または1280×768)という機種も多かったと思います。

 Microsoft WordやExcelなどで『2K/FHD(Full-HD)』(1920×1080)で表示される文字サイズに慣れている人が同じ画面サイズ、例えば15インチのノートパソコンに『4K/UHD』(3840×2160)の100%表示文字を表示させると、単純に物理的な文字サイズは半分になってしまいますので『見えづらい』と感じる人が多いと思います。

 そこで用意されているのが『拡大/縮小』機能です。




ディスプレイの実際

 筆者のディスプレイ設定を確認してみると、下図のように3つのディスプレイが検出されました。


 開発に用いているディスプレイは4Kタイプです。Windowsが『推奨』としているのが 3840×2160 なので、そのままの設定にしています。

 倍率は 150% が推奨になっていますので、こちらもそのまま使っています。

拡大/縮小(倍率)
解像度

 デュアルディスプレイで作業しているので、サブ画面があります。そちらは費用負担をケチったので4Kではありません。

拡大/縮小(倍率)
解像度




倍率の検出

 今のところ、システム設定の倍率の欄を直接検出する方法は見つかっていません。


 そこで組み込んだプログラムは、現在の表示画面のサイズと、設定上のサイズの取得です。この2つを取得することで、両者間の差から拡大/縮小がわかるだろうと考えました。




倍率の計算

 下記の『dbl_DPI_R_W』と『dbl_DPI_R_H』は実際に表示されているディスプレイのサイズを取得する変数です。

 『SystemParameters.PrimaryScreenWidth』で幅、『SystemParameters.PrimaryScreenHeight』で高さを取得します。

 もう一組の『dbl_DPI_S_W』と『dbl_DPI_S_H』は設定上のディスプレイサイズです。C#で組んだプログラムではこちらの値を取得して処理する方法が標準のため、前述の実サイズとの乖離があると表示ズレが生じます。

double dbl_DPI_R_W = System.Windows.SystemParameters.PrimaryScreenWidth;
double dbl_DPI_R_H = System.Windows.SystemParameters.PrimaryScreenHeight;
double dbl_DPI_S_W = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width;
double dbl_DPI_S_H = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height;
double dbl_DPI_Scale = dbl_DPI_S_H / dbl_DPI_R_H;

 この2組の値の比を計算します。

 比率だけの計算であれば幅だけ、高さだけ、とちらか一方だけで用は足ります。

 今回はログをとるために幅と高さの両方を取得しています。




SystemParametersを使うために

 『PrimaryScreen.Bounds』は『System.Windows.Forms』の一部分ですので、多くの場合、すでに設定されているかなと思います。未設定の場合は『System.Windows.Forms』を宣言します。

using System.Windows.Forms;

 宣言する前に.NET Frameworkの『System.Windows.Forms』を使用する設定になっていなければなりません。

 Visual Studioのソリューションエクスプローラーの『参照』から対象を探してチェックマークを入れて OK します。



 『SystemParameters』を使うためには『PresentationFramework』が必要になります。

 先程と同様にソリューションエクスプローラーの『参照』から対象を探してチェックマークを入れて OK します。




位置調整の実装

 今回ズレてしまっていたタブコントロールは下記のコードで調整することにより適正な位置に合わせることができました。

dbl_DPI_R_W = System.Windows.SystemParameters.PrimaryScreenWidth;
dbl_DPI_S_W = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width;
dbl_DPI_Scale = dbl_DPI_S_H / dbl_DPI_R_H;

int_Left = Convert.ToInt32(560 * dbl_DPI_Scale);
int_Top = Convert.ToInt32(15 * dbl_DPI_Scale);

int_This_Width = this.Width;
int_This_Height = this.Height;

int_Width = int_This_Width - int_Left - Convert.ToInt32(20 * dbl_DPI_Scale); 
int_Height = int_This_Height - int_Top - Convert.ToInt32(48* dbl_DPI_Scale);

tabControl1.Anchor = AnchorStyles.None;
tabControl1.Width = int_Width;
tabControl1.Height = int_Height;
tabControl1.Location = new System.Drawing.Point(int_Left, int_Top);
tabControl1.Anchor = 
    AnchorStyles.Top | 
    AnchorStyles.Bottom | 
    AnchorStyles.Left | 
    AnchorStyles.Right;

 最初の『dbl_DPI_R_W』で現在表示中のディスプレイサイズを取得します。

dbl_DPI_R_W = System.Windows.SystemParameters.PrimaryScreenWidth;

 次の『dbl_DPI_S_W』でパソコンのシステム設定で設定されているディスプレイサイズを取得します。

dbl_DPI_S_W = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width;

 得られた値から、拡大率を算出し変数『dbl_DPI_Scale』に代入します。

dbl_DPI_Scale = dbl_DPI_S_W / dbl_DPI_R_W;

 配置(Location)を決定します。

 Visual Studioでのデザイン時の座標に、拡大率を掛けて得た値を、『Convert.ToInt32』でint関数に変換して『int_Left』『int_Top』に代入します。

 拡大率が100%であれば、デザインしたときのままになります。

 まだ Location は設定しません。

int_Left = Convert.ToInt32(560 * dbl_DPI_Scale);
int_Top = Convert.ToInt32(15 * dbl_DPI_Scale);

 次の処理のために、フォームのサイズを取得しておきます。

int_This_Width = this.Width;
int_This_Height = this.Height;

 コントロールの幅と高さを決定するための計算を行います。

 フォームの大きさから算出します。フォームの全幅(this.Width)から、コントロールのX軸座標(int_Left)を引きます。これで左側のスペースを捨て、その位置から右側のフォームの余白でコントロールを作る、という処理になります。

 さらに、コントロールの右側部分の余白も差し引きます。

 わかりづらいので補足します。
 デザイン時のフォーム幅(this.Width)が1,000、コントロール幅が800、配置のX座標が50であったとします。すなわち、フォームの左端から50の位置を起点に、X=850の座標までの間の幅800をこのコントロールが使う事になり、X=850~X=1,000までの幅150はこのコントロールに関係ないということになります。
 この150がコントロールの右側になります。

 下記コードですと『20』が右側に残された余白部分になります。

 その『20』に、拡大率を掛けます。

 同様に、高さについても適用します。

int_Width = int_This_Width - int_Left - Convert.ToInt32(20 * dbl_DPI_Scale); 
int_Height = int_This_Height - int_Top - Convert.ToInt32(48* dbl_DPI_Scale);

 最後に取得した値を適用していきます。

 まず、アンカーを解除します。

tabControl1.Anchor = AnchorStyles.None;

 次に、幅(Width)と高さ(Height)を指定します。

 サイズを決定してから配置(座標)を指定した方が失敗しないような気がしますが、確たることはわかりません。

tabControl1.Width = int_Width;
tabControl1.Height = int_Height;

 次に配置をX座標、Y座標で指示します。

tabControl1.Location = new System.Drawing.Point(int_Left, int_Top);

 最後にアンカーを元に戻します。

 今回は上下左右ともにアンカーを打っています。

tabControl1.Anchor = 
    AnchorStyles.Top | 
    AnchorStyles.Bottom | 
    AnchorStyles.Left | 
    AnchorStyles.Right;

 このコードで、ある程度は問題なく動いてくれると思います。




見た目には問題ない

 冒頭にもお示しした画像ですが、体裁崩れがきれいに整いました。


 下図のようにディスプレイの拡大率を調整している場合、体裁崩れが起きていましたが、キレイに揃うようになりました。




スクロールバー

 フォームの内側にあるコントロールが表示できない場合に、スクロールバーが現れて見えるようにできます。

this.AutoScroll = true;

 フォームの『AutoScroll』を『true』にすることで、必要なときに現れるようになります。

 ただし、ディスプレイの拡大/縮小が100%以外のときに、上手く機能しない場合が確認されています。

 これの直し方はまだわかっていません。




おわりに

 今回はユーザーの利用環境によって変動するディスプレイ設定への対応について備忘録を作成しました。

 まだ完全ではありませんが、とりあえず最低限の動作には支障がなさそうです。




関連記事