C# 画面サイズに合わせて自由にControlsをリサイズ

#複雑なWindowsフォームを自在にリサイズしたい！
画面サイズに合わせてフォーム全体では無く、LabelやPanel、CheckBoxやRadioButtonなどのControl毎に自在に拡大縮小ができたらいいなと思ったので、オープンソースのClassを使ってカスタマイズしてみました。意外と汎用性が高くコードも経験の浅い自分でも分かりやすかったので、備忘録としてメモしておきます。

# Resize ALL Controls at Runtime
調べてみると同じ目的の実装方法は他にもいくつかありましたが、今回は下記のオープンソースのクラスをインポートして使ってみました。経験の浅い自分でもコードが分かりやすく、また導入も簡単でカスタマイズしやすいという点が良いと思います。
[C# Resize ALL Controls at Runtime](https://www.codeproject.com/Tips/749619/Csharp-Resize-ALL-Controls-at-Runtime)
環境がVisual Studioであれば同フォルダ下に置いてプロジェクトにClassを追加します。おそらく必要なファイルはclsResize.csだけの方が多いと思います。
#導入
あとはリンクの導入方法を参考に進めていきます。まず以下のnamespaceは追加しておいてねーって書いてますがだいたい追加してると思います。

```C#:waktattoru.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms; 
```

次に対象のフォームクラスのコンストラクタに以下のようにイベントを追加します。

```C#:sample.cs
namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        clsResize _form_resize;

        public Form1()
        {
            InitializeComponent();

           _form_resize = new clsResize(this); //I put this after the initialize event to be sure that all controls are initialized properly

           this.Load += new EventHandler(_Load); //This will be called after the initialization // form_load
           this.Resize += new EventHandler(_Resize); //form_resize
        }
```

コンストラクタに追加した各イベントメソッドを追加します。

```C#:sample.cs
private void _Load(object sender, EventArgs e)
  {
       _form_resize._get_initial_size();
  }

private void _Resize(object sender, EventArgs e)
   {
      _form_resize._resize();
   }
```

_LoadイベントはForm_Loadイベント時のフォームのデフォルトサイズを取得し、_Resizeイベントはフォームのサイズを操作し、デフォルトサイズから変更があったときにトリガがかかり自動でControlsをリサイズしてくれます。

#自分仕様にカスタム
現状簡単なフォームのリサイズであれば、これだけの実装で問題ないと思います。
ですが、デフォルト設定のままだと各コントロールごとにフォントサイズを設定していた場合、全てフォーム全体で設定しているフォントサイズになってしまいリサイズイベントが起きると各コントロールのテキストレイアウトがくずれてしまうかと思います。
例)

- フォームのフォントサイズが10px
- label2のフォントサイズ8px
- label1のフォントサイズ12px

の場合フォームのフォントサイズの10pxが基準となり、その基準から拡大縮小したサイズと比率計算してリサイズ処理をするので意図したサイズ比率でリサイズ処理が行われない可能性があります。

```C#:clsResize.cs
public clsResize(Form _form_)
    {
        form = _form_; //the calling form
        _formSize = _form_.ClientSize; //Save initial form size
        _fontsize = _form_.Font.Size; //Font size 
　　　　//↑これだと呼び出し時のフォーム全体で設定した値が比率計算の基準となる
    }
```

なので各コントロールごとに設定したフォントサイズの値を取得できたら、それぞれのサイズでリサイズ処理が行われ、全体としてのレイアウトが崩れずにリサイズできるのではと考えました。具体的な実装方法として思いついたの方法は以下の通りです。

- リサイズ対象のフォームで使用している全てのControlsを取得
- 取得したコントロールの名前とフォントサイズをそれぞれDictionaryに追加
- リサイズ処理の中で計算に使用している`_fontsize`をリサイズするcontrolを名前でフォントサイズを取得し、上書きする。

```C#:clsResize.cs
public clsResize(Form _form_)
    {
        form = _form_; //the calling form
        _formSize = _form_.ClientSize; //Save initial form size
        _fontsize = _form_.Font.Size; //Font size

        var _controls = _get_all_controls(form);//call the enumerator
        FontTable = new Dictionary<string, float>();
        ControlTable = new Dictionary<string, System.Drawing.Rectangle>();
        foreach (Control control in _controls) //Loop through the controls
        {
            FontTable.Add(control.Name, control.Font.Size);
            ControlTable.Add(control.Name, control.Bounds);
        }
    }

Dictionary<string, float> FontTable;
Dictionary<string, System.Drawing.Rectangle> ControlTable;
.....

 public void _resize() //Set the resize
    {
        double _form_ratio_width = (double)form.ClientSize.Width /(double)_formSize.Width; //ratio could be greater or less than 1
        double _form_ratio_height = (double)form.ClientSize.Height / (double)_formSize.Height; // this one too
        var _controls = _get_all_controls(form); //reenumerate the control collection
        int _pos = -1;//do not change this value unless you know what you are doing

        foreach (Control control in _controls)
        {
            this._fontsize = FontTable[control.Name]; //<-取得したコントロールのフォントサイズ値で上書きするためにこれを追加
            // do some math calc
            _pos += 1;//increment by 1;
            System.Drawing.Size _controlSize = new System.Drawing.Size((int)(_arr_control_storage[_pos].Width * _form_ratio_width),
                (int)(_arr_control_storage[_pos].Height * _form_ratio_height)); //use for sizing
......

```
フォントサイズと同様に、取得したControlsからcontrol.BoundsをControlTable Dictionaryに追加していけばフォントサイズだけでなく、親コントロールに対する子コントロールの表示位置もそれぞれの値ごとに意図した比率でリサイズ処理できるようになります。

#まとめ
改善点としては、全コントロールを取得し、それぞれの値でリサイズしていく訳なので使用しているコントロールの数によっては動きが重くなる点です。各フォントサイズを取得する為に、毎回全コントロールを取得し、Dictionaryに追加しているので一度コントロールの値を取得してどこかで保持してリサイズイベントが発火したコントロールだけ取得する。みたいな処理ができないか考えています。ですが、比較的簡単にこれらの機能を追加できるのは駆け出しエンジニアにとっては非常にメリットだと思います。
最後に、間違いや良い実装方法などありましたらお気軽にコメント頂けますと幸いです。