カテゴリー
Software Tech

[C#備忘録]ComboBox (DropDownList)の表示幅の自動調整 (C#) | AmpiTa Project

 ドロップダウンリストは、特に指定しない限り配置した時点の幅でリスト表示されるので、下図のように文字が切れてしまうこともあります。

 以前の筆者は、この文字幅に収まる文字数に工夫して収めていましたが、多言語化を進める中ではそうもいかなくなりました。
 と申しますのも、日本語なら略語も思いつきますが、英語や中国語では無理、自動翻訳で候補された中で選ばなければならないので姑息的手法は通用しなくなり、新たな方法を検討するに至りました。




文字数をカウントして掛ける

 標題が結論でもあるのですが、文字数を数えて、表示用の幅を決定するプログラムを加えました。

 元々、コンボボックスはフォームが開くとき(shown)でまとめてセットアップしていたので、そのセットアップに数行追記してこの文字幅対応のリストを実装しました。




コードの追記は4行

 元々は下記のコードがありました。

 『Items.Add』は表示項目の設定です。
 『SelectedIndex』でゼロを指定しているのは、リストの一番目を表示欄に表示させておくように指示しています。他の箇所ではこの数字が1であったり8であったりしています。
 スタイルは『ComboBoxStyle.DropDownList』なので、ドロップダウンリストです。テキストの自由入力には対応しない、一覧から選ぶだけのリストです。

cmbFunction.Items.Add("差分受信");
cmbFunction.Items.Add("全件受信");
cmbFunction.Items.Add("入力代行(個別手入力・一括流込");
cmbFunction.Items.Add("一覧出力(CSV・HTML)");
cmbFunction.Items.Add("LINE・eMail情報展開");
cmbFunction.SelectedIndex = 0;
cmbFunction.DropDownStyle = ComboBoxStyle.DropDownList;

 ここに追記した4行とは、下記の通りです。

 このコンボボックスのデザイン上のフォントサイズは『14』です。

cmbFunction.Items.Add("差分受信");
cmbFunction.Items.Add("全件受信");
cmbFunction.Items.Add("入力代行(個別手入力・一括流込");
cmbFunction.Items.Add("一覧出力(CSV・HTML)");
cmbFunction.Items.Add("LINE・eMail情報展開");
cmbFunction.SelectedIndex = 0;
cmbFunction.DropDownStyle = ComboBoxStyle.DropDownList;

int int_Max = 0;
for (int intRound = 0; intRound < cmbFunction.Items.Count; intRound++)
{int_Max = Math.Max(int_Max,cmbFunction.Items[intRound].ToString().Length); }
cmbFunction.DropDownWidth = (int_Max + 1) * 18;

 1行目の『int_Max』は、リストのアイテムの最大文字数です。

 2行目では『for』を使って、コンボボックスに納められているすべてのItemを0番から順に巡りなさいと指示しています。

 3行目がforで巡って来たItemの文字数を取得する命令です。当該リストの表示内容を『ToString()』で取得、さらにそこに『Length』を付けることで文字数を取得することができます。もし大きな数字であれば取得して『int_Max』に格納します。
 最大値の取得には『Math.Max』を使っています。変数に格納されている数値(今回の初期値は0)と比較して大きいか小さいかを判断し、コンボボックスのリストの中で最大文字数であるものを取得します。

 4行目では『DropDownWidth』に、ドロップダウンリストとして表示した際の表示幅(width)を指定しています。最大文字数(int_Max)に『1』を足してから『18』を掛けるとちょうど良かったです。

 先述のとおりフォントサイズは『14』なので文字数×フォントサイズでいけるかと思いましたが、違いました。




掛け率は9ptで11倍が基準?

 フォントサイズに対する掛け率の方程式は見つけることができていません。

 下図はフォントサイズが14ptと18ptの場合です。
 14ptの場合は先述のとおり『18』倍がちょうど良かったです。
 18ptの場合は『22』倍がちょうど良かったです。

Font=18pt, scale=x22
Font=14pt, scale=x18

 フォントサイズが4pt増えて、掛け率が『4』増える法則が成り立つかと思うと、そうでもありません。

 36ptなら18ptよりも18増えているので、掛け率も18足せば良いかと思うと、そうでもありません。
 下図の通りフォントサイズ36ptで掛け率『40』(22+18=40)では上手くいきません。
 掛け率『42』でギリギリ、『44』でちょうど良い感じです。この数値はフォントサイズ18ptのときの2倍なので、フォントサイズが2倍になれば掛け率も2倍ということかもしれません。

Font=36pt, scale=x40
Font=36pt, scale=x42
Font=36pt, scale=x44

 本当にそうなるのか試してみると、なりそうです。

 18ptの3倍である54ptのとき、掛け率は3倍の『66』でちょうど良いです。

 18ptの4倍である72ptのとき、掛け率は4倍の『88』でちょうど良いです。

Font=54pt, scale=x66
Font=72pt, scale=x88

 スケールダウンして12pt、すなわち18ptの3分の2にすると、計算上では掛け率は14.666と割り切れないのですが、試しに『14』倍にしてみるとギリギリすぎて、『16』倍にすると余るので、やはり『15』倍が適正値です。

Font=12pt, scale=x14
Font=12pt, scale=x16

 もっと小さくなるとどうなるか、というところですが、今回の場合では標準サイズ以下に全文字が収まってしまうので、これ以上はどんな計算をしても、元のサイズでリスト表示されてしまいます。

Font=10pt, scale=x12
Font=8pt, scale=x8

 今回は実験なので、元のコンボボックスを小さくして確認しました。

 下図はフォントサイズが9pt、掛け率が11です。法則は成り立った感じがします。

Font=9pt, scale=x11

 フォントサイズ9ptで掛け率11だと奇数で計算しづらそうなので、偶数でどうかというと、上手くいきませんでした。

 例えばフォントサイズ8ptで掛け率10ですと数字はキレイですが、下図の通り切れてしまいます。

Font=8pt, scale=x10


 9ptあたり11倍という法則を計算式にしてみました。

 コンボボックスのフォントサイズを取得するとfloat型で返されるため『Math.Ceiling』で整数にすることにします。

 また、法則を使うためにはフォントサイズを『9』で割り、『11』を掛ける必要があるので、やはり小数点になるためすべての計算式をまとめて『Math.Ceiling』の中に納めます。

 それを『(int)』でint型に変換して計算すると収まります。

cmbFunction.Items.Add("差分受信");
cmbFunction.Items.Add("全件受信");
cmbFunction.Items.Add("入力代行(個別手入力・一括流込");
cmbFunction.Items.Add("一覧出力(CSV・HTML)");
cmbFunction.Items.Add("LINE・eMail情報展開");
cmbFunction.SelectedIndex = 0;
cmbFunction.DropDownStyle = ComboBoxStyle.DropDownList;
int int_Scale = (int)Math.Ceiling(cmbFunction.Font.Size / 9 * 11);
int int_Max = 0;
for (int intRound = 0; intRound < cmbFunction.Items.Count; intRound++)
{int_Max = Math.Max(int_Max,cmbFunction.Items[intRound].ToString().Length); }
cmbFunction.DropDownWidth = (int_Max + 1) * int_Scale;



スマート化

 そもそも、文字数に1を足して計算するという不合理があるので、改めてすべてを見直しました。

 色々と数字を組み合わせたりしましたが、この文字数に『1』を足すのは合理的であることがわかりました。計算式がシンプルになるのは『文字数+1』で『int_Max』を形成する方法です。

 割り算と掛け算を同時に行うならば、適当な数字に丸めた方が良いのではないかということで、こちらはまとまりました。

 『11÷9』を数値化すると 1.222222 なので 1.2 や 1.25 が良いのかなと思いましたが、結論としては『1.33』に落ち着きました。1.5 など色々と試しましたが、フォントサイズが6ptから60ptまで、すべてにおいて全文字が表示され、かつ、余白が最少化される数値が 1.33 でした。

cmbFunction.Items.Add("差分受信");
cmbFunction.Items.Add("全件受信");
cmbFunction.Items.Add("入力代行(個別手入力・一括流込");
cmbFunction.Items.Add("一覧出力(CSV・HTML)");
cmbFunction.Items.Add("LINE・eMail情報展開");
cmbFunction.SelectedIndex = 0;
cmbFunction.DropDownStyle = ComboBoxStyle.DropDownList;
int int_Max = 0;
for (int intRound = 0; intRound < cmbFunction.Items.Count; intRound++)
{int_Max = Math.Max(int_Max,cmbFunction.Items[intRound].ToString().Length); }
cmbFunction.DropDownWidth =  (int)Math.Ceiling(cmbFunction.Font.Size  *1.33* (int_Max + 1) );

 上記のとおり、最後の1行で『Math.Ceiling』で囲われた中でfloat型の『Font.Size』に『1.33』を掛け、そこに『文字数+1』を掛けることで得られた数字を、『(int)』で整数化して『DropDownWidth』としました。




多言語化には不適切

 ここまでの方法で日本語対応としては十分でしたが、英語では半角が使われるため『Length』で取得する文字数での掛け率が不整合になると考えられます。
 日本語(全角)の10文字と、英数(半角)の10文字では、幅が2倍差あります。

 したがって、文字バイト数を取得して表示幅を調整する方法を検討しました。




落としどころは0.77

 コードに『System.Text.Encoding』を追記しテキストを『shift_jis』としてバイト数を取得します。

 コンボボックスのアイテムをそれぞれ『GetByteCount』でチェックしていき『int_Max』に納めていきます。
 この事例ですと『差分受信』は8、『入力代行(個別手入力・一括流込)』は32が返されます。

 フォントサイズに文字列バイト数を掛け、そこに定数として『0.77』を掛けるとちょうど良かったです。

cmbFunction.Items.Add("差分受信");
cmbFunction.Items.Add("全件受信");
cmbFunction.Items.Add("入力代行(個別手入力・一括流込)");
cmbFunction.Items.Add("一覧出力(CSV・HTML)");
cmbFunction.Items.Add("LINE・eMail情報展開");
cmbFunction.SelectedIndex = 0;
cmbFunction.DropDownStyle = ComboBoxStyle.DropDownList;
int int_Max = 0;
System.Text.Encoding byte_Shift_JIS = System.Text.Encoding.GetEncoding("shift_jis");
for (int intRound = 0; intRound < cmbFunction.Items.Count; intRound++)
{ GetByteCount= Math.Max(int_Max, byte_Shift_JIS.GetByteCount(cmbFunction.Items[intRound].ToString())); }
cmbFunction.DropDownWidth =  (int)Math.Ceiling(cmbFunction.Font.Size  * int_Max * 0.77);

 実験ではフォントサイズ8ptで掛け率を0.75から0.01ずつ上げていきました。すると0.76~0.78あたりがギリギリで良さそうであったため、このあたりの数字で6ptから60ptまで振って確認したところ、余白ができてしまう場合の量が適当だなと思えたのが 0.77 であったため、今回は 0.77 をプログラムに組み込みました。

Font=8pt, scale=x0.75
Font=8pt, scale=x0.76

Font=8pt, scale=x0.77
Font=8pt, scale=x0.78

Font=8pt, scale=x0.79
Font=8pt, scale=x0.80

 文字数を変動させた場合の様子についても確認しました。

 日本語(全角)、英語(半角)のそれぞれの文字数を調整して無駄に余白が大きくならないか確認しました。

 下図はフォントサイズ14ptですが、文字数が多く成れば全体の表示幅は広くなっていますが、余白はだいたい同じくらいです。

Font=14pt, scale=x0.77, 20 letters
Font=14pt, scale=x0.77, 22 letters
Font=14pt, scale=x0.77, 24 letters

 ということで、コンボボックス(ドロップダウンリスト)のセットアップが終わったら下記のコードを実行することで表示幅は適正化できるだろうと考え、AmpiTa Projectのプログラムには組み込んでいくことにしました。

int int_Max = 0;
System.Text.Encoding byte_Shift_JIS = System.Text.Encoding.GetEncoding("shift_jis");
for (int intRound = 0; intRound < cmbFunction.Items.Count; intRound++)
{ GetByteCount= Math.Max(int_Max, byte_Shift_JIS.GetByteCount(cmbFunction.Items[intRound].ToString())); }
cmbFunction.DropDownWidth =  (int)Math.Ceiling(cmbFunction.Font.Size  * int_Max * 0.77);




処理は一瞬

 何百行もあるとどうなるかわかりませんが、ドロップダウンリストにそれほどの行数を必要とするプログラムを組むことがないので、筆者のところでは上記コードで十分でした。

 処理時間については1秒未満なので、これの実装前後で変化に気づくユーザー様も居ないくらいの短い時間だと思います。




おわりに

 筆者はバリバリのプログラマーではないので、姑息的手段を模索して改善に取り組んでいます。

 あまりに姑息的なので、あとでプログラムを見直していても単純明快、そういうことをしたかったのかと気付けるような内容です。

 おそらく今回のコードも省力化の方法があり、そもそもリスト幅の調整にはもっと良い方法が存在すると思います。リスト件数が100を超えることがあれば見直したいと思います。