Windowsフォームで下線だけ表示するTextBoxを作る

# 環境
- Windows10 Pro (1903) 64bit
- Visual Studio 2019
- .NET Framework 4.5

# 作った経緯
仕事でWindowsフォームよく使うけど、UI周りの知識というか理屈というか原理というかそのあたりが弱いなと思ったから。
お勉強がてら自分で改造してみれば何か見えてくるかと思ったから。
~~あと台風が来ててお出かけできなかったから。~~

# 出来たもの
![出来たTextBox](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/470282/a627685b-9786-b4c1-81d0-eddbb8bc62cf.png)
直線部分がTextBoxになっています。

![「肉」](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/470282/5a1ebdfd-0d5d-ac33-3479-579c0f42085e.png)
TextBoxなのでキーボード入力もできます。

# アプローチ
## TextBoxを継承してOnPaintを拾う : ダメだった
TextBoxを継承して、OnPaint拾って直線を描画する方法でやってみる。

```c#:UnderLineTextBox.cs
    public class UnderLineTextBox : System.Windows.Forms.TextBox
    {
        public UnderLineTextBox()
        {
            //ボーダーを全部消す
            this.BorderStyle = BorderStyle.None;
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);

            //下線を描画する
            e.Graphics.DrawLine(new Pen(Color.Black), 0, this.Height - 1, this.Width, this.Height - 1);
        }
    }
```
結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/470282/50fe3bb6-9b3c-7b45-ffac-fc74513b9350.png)
罫線が描画されませんでした。なんで…

## OnPaintを拾うためにUserPaintを有効にする : 労力がヤバイのでやりたくない
「Windowsフォーム textbox onpaint 発生しない」で検索すると、どうやら「SetStyle」メソッドとやらで「UserPaint」を有効にしないとそもそもOnPaintが呼び出されないらしい。

```c#:UnderLineTextBox.cs
    public class UnderLineTextBox : System.Windows.Forms.TextBox
    {
        public UnderLineTextBox()
        {
            //UserPaintをTrueに設定する。
            this.SetStyle(ControlStyles.UserPaint, true);       //ここを追加

            //ボーダーを全部消す
            this.BorderStyle = BorderStyle.None;
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);

            //下線を描画する
            e.Graphics.DrawLine(new Pen(Color.Black), 0, this.Height - 1, this.Width, this.Height - 1);
        }
    }
```
結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/470282/39cd10d4-06f9-228e-877b-4dfcabbaf9e0.png)
一見よさそうだが…

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/470282/56e7ffda-2fe5-e45c-741e-fac173f095dd.png)
！？
文字入れたら予想外のバグり方で草。
どうやら**UserPaintを有効化すると、既存のTextBoxで行われている描画処理も自前で実装しないといけない**ようです。
こうなるとUserPaintはダメだ。どうするか…

## WndProcで記述する : もう少し詰めれば行けそう
**天の声「ウィンドウプロシージャってあるやん？」**

それな！
というわけでウィンドウプロシージャでウィンドウ描画時に線を引く感じでやってみます。
ちなみにMessageの番号など覚えていませんので[Google先生に訊きます](https://www.google.com/search?&q=windows+%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8+%E6%8F%8F%E7%94%BB)。

```c#:UnderLineTextBox.cs
    public class UnderLineTextBox : System.Windows.Forms.TextBox
    {
        public UnderLineTextBox()
        {
            //UserPaintをTrueに設定する。
            //this.SetStyle(ControlStyles.UserPaint, true);       //ここはもう不要

            //ボーダーを全部消す
            this.BorderStyle = BorderStyle.None;
        }

        protected override void WndProc(ref Message m)
        {
            base.WndProc(ref m);

            if (m.Msg == 15)    //WM_PAINT = 15
            {
                using (Graphics g = this.CreateGraphics())
                {
                    //下だけボーダー表示
                    g.DrawLine(new Pen(Color.Black), 0, this.Height - 1, this.Width, this.Height - 1);
                }
            }
        }

        //ここも不要
        //protected override void OnPaint(PaintEventArgs e)
        //{
        //    base.OnPaint(e);

        //    //下線を描画する
        //    e.Graphics.DrawLine(new Pen(Color.Black), 0, this.Height - 1, this.Width, this.Height - 1);
        //}
    }
```

結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/470282/62367c03-ea84-35a2-33c0-20c2222f4e20.png)
良さそうだが…
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/470282/746b641c-f8f8-de15-060b-4d12a6b67653.png)
文字入れたら下線消えました。
でもこれは予想の範囲内。ウィンドウメッセージ拾って「描画時」に下線を描画しているので、TextBoxデフォルトの描画時には下線描画されないので消えるはずです。
ということは、TextBoxの描画が発生したときに同じように描画すればいいはず。

## WndProc + α : とりあえず題意を満たす
```c#:UnderLineTextBox.drawUnderLine
        private void drawUnderLine()
        {
            using (Graphics g = this.CreateGraphics())
            {
                //下だけボーダー表示
                g.DrawLine(new Pen(Color.Black), 0, this.Height - 1, this.Width, this.Height - 1);
            }
        }
```
とりあえず罫線引くメソッドだけ切り出しておいて、

```c#:UnderLineTextBox.cs
    public class UnderLineTextBox : System.Windows.Forms.TextBox
    {
        public UnderLineTextBox()
        {
            //ボーダーを全部消す
            this.BorderStyle = BorderStyle.None;
        }

        protected override void WndProc(ref Message m)
        {
            base.WndProc(ref m);

            if (m.Msg == 15)    //WM_PAINT = 15
            {
                drawUnderLine();
            }
        }

        /// <summary>
        /// Text変更時
        /// </summary>
        /// <param name="e"></param>
        protected override void OnTextChanged(EventArgs e)
        {
            base.OnTextChanged(e);
            drawUnderLine();
        }

        /// <summary>
        /// マウスでクリックされた(ボタンが下がったとき)
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);
            drawUnderLine();
        }

        /// <summary>
        /// フォーカスが移ったとき
        /// </summary>
        /// <param name="e"></param>
        protected override void OnGotFocus(EventArgs e)
        {
            base.OnGotFocus(e);
            drawUnderLine();
        }

        private void drawUnderLine()
        {
            using (Graphics g = this.CreateGraphics())
            {
                //下だけボーダー表示
                g.DrawLine(new Pen(Color.Black), 0, this.Height - 1, this.Width, this.Height - 1);
            }
        }
    }
```
とりあえず[OnTextChanged][OnMouseDown][OnGotFocus]をoverrideして下線を描画するようにしてみます。
結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/470282/0fd383a9-c07f-aa22-7ac8-811695018903.png)
画面表示時はこう
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/470282/a6ac095f-2a7f-0263-60cb-15b489d1f8da.png)
文字入れるとこう。
再描画の拾い方、もう少しきれいにやる方法もありそうですが、とりあえず今回はこれで完成にします。
使っていく中で下線が消えるタイミングが見つかったら、都度On****で追記すればいいという方針です。

# まとめ
既存コントロールのUIに「ちょっとだけ」手を加えたい場合、WndProc使うのが一番簡単な感じがします。
UIは完全に自前でやる覚悟がある、もしくは既存コントロールの動作だけ活かしてUIを別物にする場合は
UserPaintを有効にするのがいいのかなぁ。
キーボードから文字入力できるタイプのコントロールを作るのは結構骨が折れる気がします。
以上です。