【.NET】NumericUpDownコントロールのボタンを変更する

こんにちは、irislabのちひろです。

今日は久しぶりの.Netネタです。
かなり力技のような話なのでご利用は計画的に……。

Microsoft .NET Framework(C#, VB.NET)のWindowFormsで用意されているコントロールの中にNumericUpDownというコントロールがあるのはご存知でしょうか。数値の入出力に特化したコントロールで、ちょっとした画面を作成する際に手間やお金を掛けずに利用できるのでなかなか便利ですね。

また名前の通り数値を増加/現象させることがGUI操作で簡単に出来るコントロールでもあり、それ用のボタンが付いているのも特徴です。

今回の話はこのコントロールに付いているボタンの代わりにユーザーが独自に作成したボタンを追加するというお話です。まぁ利用シーンがかなり限られそうですけどね……。

主な利用シーンとしてはNumericUpDownコントロールを継承したオリジナルのコントロールを作成する場合に、独自のボタンを配置することで入力支援として電卓を表示したり、書式を切り替える機能を付けたりといった事が思い浮かびます。

他にも面白そうな使い方が有るかもしれませんがまずはソースをご覧下さい。

[C#]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class NumericUpDownEx
    : NumericUpDown
{
    public NumericUpDownEx() : base()
    {
        var oldButton = this.Controls[0];
        var newButton = new Button()
        {
            TabStop = false,
            Text = string.Empty,
            Size = oldButton.Size,
            Location = oldButton.Location
        };
        this.Controls.Add(newButton);
        newButton.BringToFront();
        oldButton.SizeChanged += delegate(object s, EventArgs e) { 
            newButton.Size = oldButton.Size; };
        oldButton.LocationChanged += delegate(object s, EventArgs e) {
            newButton.Location = oldButton.Location; };
        oldButton.Visible = false;
    }
}

[VB.NET]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Public Class NumericUpDownEx
    Inherits NumericUpDown
 
    Public Sub New()
 
        MyBase.New()
 
        Dim oldButton = Me.Controls(0)
        Dim newButton = New Button() With
                        {
                            .TabStop = False,
                            .Text = String.Empty,
                            .Size = oldButton.Size,
                            .Location = oldButton.Location
                        }
        Me.Controls.Add(newButton)
        newButton.BringToFront()
        AddHandler oldButton.SizeChanged,
            New EventHandler(Sub(s As Object, e As EventArgs)
                                 newButton.Size = oldButton.Size
                             End Sub)
        AddHandler oldButton.LocationChanged,
            New EventHandler(Sub(s As Object, e As EventArgs)
                                 newButton.Location = oldButton.Location
                             End Sub)
        oldButton.Visible = False
 
    End Sub
 
End Class

サンプルソースではNumericUpDownを継承してNumericUpDownExというクラスを作成しています。

見て頂くと分かりますがかなり力技と言いますか反則レベルになっています……が一応解説します。

NumericUpDownが持つ子コントロールの1つ目を取得していますが、これが例のボタンにあたるコントロールになります。
型の情報を取得すると”System.Windows.Forms.UpDownBase+UpDownButtons”という値が取得できる事からも分かるように内部で定義されているクラスですね。

その後に新しいButtonクラスのインスタンスを生成し子コントロールとして追加していますが、これがユーザーが独自に作成したボタンになります。

今回はサンプルのために標準のButtonコントロールを使用していますが、別に用意したコントロールで大丈夫です。

その後、最初に取得したボタンのSizeChangedイベントとLocationChangedイベントにイベントハンドラを設定しています。
内容は単純でサイズや位置が変化したら新たに作成したユーザー独自のボタンも同じように変更させるという物になります。

こうすることでフォントが変更された場合等に内部で再計算されたボタンの大きさが、ユーザーが追加したボタンにも伝わるようになります。

最後に元々のボタンを非表示にすることで、ユーザーが追加したボタンのみが残る事になります。

後はユーザーが追加したボタンの方を好きにカスタマイズしていけば良いでしょう。

もしもComboBoxのような見た目のボタンが欲しいということであれば、VisualStyleRendererやControlPaintを使って描画してあげる事で実現できそうですね。
参考までに以下のような感じでできるかも?

[C#]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
using System.Windows.Forms.VisualStyles;
class DownButton:Button
{
    Dictionary<VisualStyleElement, VisualStyleRenderer> _renderCache;
    protected override void OnPaint(PaintEventArgs pevent)
    {
        VisualStyleElement visualStyleElement;
        ButtonState buttonState;
 
        if (!Enabled)
        {
            visualStyleElement = VisualStyleElement.ComboBox.DropDownButton.Disabled;
            buttonState = ButtonState.Inactive;
        }
        else if (Capture && Control.MouseButtons != System.Windows.Forms.MouseButtons.None)
        {
            visualStyleElement = VisualStyleElement.ComboBox.DropDownButton.Pressed;
            buttonState = ButtonState.Pushed;
        }
        else if (ClientRectangle.Contains(PointToClient(Control.MousePosition)))
        {
            visualStyleElement = VisualStyleElement.ComboBox.DropDownButton.Hot;
            buttonState = ButtonState.Normal;
        }
        else
        {
            visualStyleElement = VisualStyleElement.ComboBox.DropDownButton.Normal;
            buttonState = ButtonState.Normal;
        }
 
        if (Application.RenderWithVisualStyles && VisualStyleRenderer.IsSupported)
        {
            _renderCache = _renderCache ?? new Dictionary<VisualStyleElement, VisualStyleRenderer>();
            if (!_renderCache.ContainsKey(visualStyleElement))
                _renderCache[visualStyleElement] = new VisualStyleRenderer(visualStyleElement);
            _renderCache[visualStyleElement].DrawBackground(pevent.Graphics, this.ClientRectangle);
        }
        else
        {
            ControlPaint.DrawComboButton(pevent.Graphics, this.ClientRectangle, buttonState);
        }
    }
}

キャッシュを使っているのは私の趣味なので削除しても問題ありません。
また、VisualStyleを無効にしないのであればControlPaintの処理は不要ですし、その逆もしかりです。

念のため最後にもう一度記載しておきますが、この方法はかなり強引なやり方なので別の方法が有るのであればそちらを使用してください。


関連する記事

カテゴリー: .Net タグ: , , パーマリンク

コメントを残す

メールアドレスが公開されることはありません。

日本語が含まれないコメントは無視されますのでご注意ください。(スパム対策)