NumericUpDownを改造してみる (C#)

# はじめに

C#で`NumericUpDown`を使おうとすると，いろいろと不満が出てくることがあります。
例えば

- マウスホイール操作時の増分を変更したい
- `Increment`に負の値を設定したい (稀に需要がある)
- `int`で値を操作できると嬉しい

といった感じです。
文句があるなら自作してしまえということで上記の不満を解決してくれるコントロールを作成しました。
(使いまわせばいいのですがいつも探し回ることになるので自分用のメモも兼ねて投稿)

# ソースコード

```C#
using System;
using System.Windows.Forms;

namespace Qiita
{
    public class MyNumericUpDown : NumericUpDown
    {
        private int _abs_increment, _increment;
        private int _abs_scroll_increment, _scroll_increment;

        /// <summary>
        /// Gets or sets the value to increment or decrement the spin box (also known as an up-down control) when the up or down buttons are clicked.
        /// </summary>
        new public int Increment
        {
            set
            {
                this._increment = value;
                this._abs_increment = Math.Abs(value);
            }
            get => this._increment;
        }

        /// <summary>
        /// Gets or sets the value to increment or decrement the spin box (also known as an up-down control) when th mousee wheel spined.
        /// </summary>
        public int ScrollIncrement
        {
            set
            {
                this._scroll_increment = value;
                this._abs_scroll_increment = Math.Abs(value);
            }
            get => this._scroll_increment;
        }

        public int ValueAsInt
        {
            set => this.Value = value;
            get => (int)this.Value;
        }

        public MyNumericUpDown() : base()
        {
            this.Increment= 1;
            this.ScrollIncrement= 1;
            this.ImeMode = ImeMode.Disable;
        } // ctor ()

        override public void UpButton() => UpDown(this._increment > 0);

        override public void DownButton() => UpDown(this._increment < 0);

        private void UpDown(bool up)
            => this.Value = up
                ? Math.Min(this.Value + this._abs_increment, this.Maximum) // increment
                : Math.Max(this.Value - this._abs_increment, this.Minimum) // decrement
                ;

        override protected void OnMouseWheel(MouseEventArgs e)
        {
            if (e is HandledMouseEventArgs hme) hme.Handled = true;

            this.Value = e.Delta > 0 ^ this._scroll_increment > 0
                ? Math.Max(this.Value - this._abs_increment, this.Minimum) // decrement
                : Math.Min(this.Value + this._abs_increment, this.Maximum) // increment
                ;
        } // override protected void OnMouseWheel (MouseEventArgs)
    } // public class MyNumericUpDown : NumericUpDown
} // namespace Qiita
```

# 解説

ここではC#の基本的な構文(e.g., `using`ディレクティブ)の解説はしません。

## フィールド

ボタン，マウスホイール操作時の増分を保持するためのフィールドです。
絶対値を使用することも多いので，絶対値も持っておけるようにしてあります。

## プロパティ

### `Increment`，`ScrollIncrement`

それぞれ，ボタン，マウスホイール操作時の増分を表しています。
値の設定時には，それぞれの値の絶対値を計算して保存しています。
`Increment`については親に同名のプロパティが存在するため`new`で修飾しています。

### `ValueAsInt`

`Value`を`int`で操作したいという要望に応えるべく追加したプロパティです。
実装はご覧の通りです。

## コンストラクタ

C#では値型は全ビットが0で初期化されるので，何もしないと`Increment`と`ScrollIncrement`が0になってしまいます。
それではあまりにもかわいそうなことになってしまうので，初期値を設定しています。

## メソッド

### `UpButton`，`DownButton`

この人たちをオーバーライドすることで矢印ボタンクリック時の挙動を変更できます。
ここでは`bool`型の引数を取る`UpDown`を参照しています。
渡している値については`UpDown`の項で説明します。

### `UpDown`

ボタン操作時の動作を規定しているメソッドです。

#### 引数

引数は，「(`Increment`プロパティの符号に関わらず)`Value`が増加する方向に変化するか」を表しています。
例えば，上向きのボタンが押された場合

- `_increment`が正ならば増加
- `_increment`が負ならば減少

となるので，「増加するかどうか」は`_increment > 0`で表せます。
下向きのボタンについては条件が逆になるだけで考え方は同じです。

#### 値の更新

特に難しいことはしていません。
増加の場合は，増分を足した値と`Maximum`の小さい方を`Math.Min`で選択することで最大値を超えないようにしています。
減少の場合も考え方は同じです。

本当はオーバーフローした際には最大値/最小値を設定するようにした方がいいような気はしますが，そこまで絶対値が大きい値を扱う需要が今のところないので実装していません。 (手抜き)

### `OnMouseWheel`

この人をオーバーライドするとマウスホイール操作時の挙動を変更できます。
やりたいことは`UpDown`と同じなのですが，増加か減少かの判定が若干怪しいので少しまとめておきます。

増加させるか減少させるかを表にまとめると以下のようになります。

| マウスホイール操作方向  | `_scroll_increment > 0` | `_scroll_increment < 0` |
|:----------------------|:------------------------|:------------------------|
| 正                    | 増加                     | 減少                    |
| 負                    | 減少                     | 増加                    |

この表を眺めていると，なんとなく排他的論理和に見えてきます。
マウスホイールの操作量は`e.Delta`で取得できるので，排他的論理和をとって`e.Delta > 0 ^ _scroll_increment > 0`で増加か減少かを判定できます。
ここまで来れば，あとはやっていることは`UpDown`と全く同じです。

# さいごに

この記事で紹介したソースコードは自由に利用していただいて構いません。
よりよい書き方や改善すべき点などがありましたら(C#のコードもQiitaの記事も)，コメントなどで教えていただけると幸いです。

## 追加・変更点など

### 変更 (2019/10/06)

増加/減少の処理の実装を条件演算子と`Math.Min`/`Math.Min`を使用するものに変更しました。