C# - 金額入力コントロール

概要

現在オフショアでやってる代物と関係のあるものだが、とりあえずサックリと金額入力コントロールを作った。
なんかこういうのとかあるけど、
http://www.c-sharpcorner.com/UploadFile/mgold/MaskedCurrencyTextBox08142005135643PM/MaskedCurrencyTextBox.aspx?ArticleID=3dc8ca4b-7166-4851-ab40-a2150c5ca787
言っちゃ悪いがしょぼくね?という事で同じくらいしょぼいのを作成中。


そのうちWitchGardenにまとめ、さらにその後Polestar行きにする予定。
その頃にはもうちょいソースコードも綺麗になっている予定。
今は要所にやる気の無さが見えるが。

ソース

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.ComponentModel;

/// <summary>
/// 金額入力欄を担当するテキストボックス
/// </summary>
/// <remarks>
/// 現状SelectionStart、SelectionLength等の処理は未実装。ちゃんと実装すればよりマシになる。
/// 継承の設計などもちゃんとやってない(プロパティではなくメンバを見てる場所があるなど)。
/// 継承前提じゃないので、継承する場合は気合を入れてやること(個人的には継承よりソース変更を推奨)。
/// クリップボードからの貼り付けは現在かなり適当に対応中
/// </remarks>
/// <logs>
///  <create>2007/04/30</create>
///  <lastupdate>2007/04/30</lastupdate>
/// </logs>
/// <author>t.tsugehara</author>
public class CurrencyBox : TextBox {
#region static
private static Dictionary<Keys, char> KEY_VALUE = null;

protected static void InitKeyValue() {
    KEY_VALUE = new Dictionary<Keys, char>(20);
    KEY_VALUE.Add(Keys.D0, '0');
    KEY_VALUE.Add(Keys.D1, '1');
    KEY_VALUE.Add(Keys.D2, '2');
    KEY_VALUE.Add(Keys.D3, '3');
    KEY_VALUE.Add(Keys.D4, '4');
    KEY_VALUE.Add(Keys.D5, '5');
    KEY_VALUE.Add(Keys.D6, '6');
    KEY_VALUE.Add(Keys.D7, '7');
    KEY_VALUE.Add(Keys.D8, '8');
    KEY_VALUE.Add(Keys.D9, '9');
    KEY_VALUE.Add(Keys.NumPad0, '0');
    KEY_VALUE.Add(Keys.NumPad1, '1');
    KEY_VALUE.Add(Keys.NumPad2, '2');
    KEY_VALUE.Add(Keys.NumPad3, '3');
    KEY_VALUE.Add(Keys.NumPad4, '4');
    KEY_VALUE.Add(Keys.NumPad5, '5');
    KEY_VALUE.Add(Keys.NumPad6, '6');
    KEY_VALUE.Add(Keys.NumPad7, '7');
    KEY_VALUE.Add(Keys.NumPad8, '8');
    KEY_VALUE.Add(Keys.NumPad9, '9');
}
#endregion

/// <summary>
/// Keyを処理するためのイベントハンドラ
/// </summary>
/// <param name="key"></param>
public delegate void CurrencyKeyEventHandler(Keys key);

#region private field
/// <summary>Valueプロパティの実体</summary>
private string m_value;
/// <summary>カーソルの論理位置</summary>
private int m_cursorPoint;
/// <summary>Prefix</summary>
private string m_prefix;
/// <summary>通貨マーク</summary>
private string m_currencyMark;
/// <summary>セパレート挿入単位</summary>
private int m_sepLength = 3;
/// <summary>符号</summary>
private bool m_isSign;
/// <summary>BackspaceとDelete共用の削除処理</summary>
CurrencyKeyEventHandler m_deleteLogicEventHandller;
/// <summary>文字挿入処理</summary>
CurrencyKeyEventHandler m_insertLogicEventHandller;
/// <summary>カーソル移動処理</summary>
CurrencyKeyEventHandler m_moveLogicEventHandller;
/// <summary>符号切り替え処理</summary>
CurrencyKeyEventHandler m_changeSignLogicEventHandller;
/// <summary>Keyとイベントのマップ</summary>
Dictionary<Keys, CurrencyKeyEventHandler> m_keyEventMap;
#endregion

#region Constructor
public CurrencyBox() {
    // MEMO:微妙?
    if (KEY_VALUE == null) {
        InitKeyValue();
    }

    // initial value
    this.m_value = "0";
    this.m_isSign = false;
    this.m_keyEventMap = new Dictionary<Keys, CurrencyKeyEventHandler>(32);
    this.m_currencyMark = "\\";
    syncPrefix();
    
    // create event handllers
    this.m_deleteLogicEventHandller = new CurrencyKeyEventHandler(deleteLogic);
    this.m_insertLogicEventHandller = new CurrencyKeyEventHandler(insertLogic);
    this.m_moveLogicEventHandller = new CurrencyKeyEventHandler(moveLogic);
    this.m_changeSignLogicEventHandller = new CurrencyKeyEventHandler(changeSignLogic);

    // mapping key events
    this.m_keyEventMap.Add(Keys.D0, this.m_insertLogicEventHandller);
    this.m_keyEventMap.Add(Keys.D1, this.m_insertLogicEventHandller);
    this.m_keyEventMap.Add(Keys.D2, this.m_insertLogicEventHandller);
    this.m_keyEventMap.Add(Keys.D3, this.m_insertLogicEventHandller);
    this.m_keyEventMap.Add(Keys.D4, this.m_insertLogicEventHandller);
    this.m_keyEventMap.Add(Keys.D5, this.m_insertLogicEventHandller);
    this.m_keyEventMap.Add(Keys.D6, this.m_insertLogicEventHandller);
    this.m_keyEventMap.Add(Keys.D7, this.m_insertLogicEventHandller);
    this.m_keyEventMap.Add(Keys.D8, this.m_insertLogicEventHandller);
    this.m_keyEventMap.Add(Keys.D9, this.m_insertLogicEventHandller);
    this.m_keyEventMap.Add(Keys.NumPad0, this.m_insertLogicEventHandller);
    this.m_keyEventMap.Add(Keys.NumPad1, this.m_insertLogicEventHandller);
    this.m_keyEventMap.Add(Keys.NumPad2, this.m_insertLogicEventHandller);
    this.m_keyEventMap.Add(Keys.NumPad3, this.m_insertLogicEventHandller);
    this.m_keyEventMap.Add(Keys.NumPad4, this.m_insertLogicEventHandller);
    this.m_keyEventMap.Add(Keys.NumPad5, this.m_insertLogicEventHandller);
    this.m_keyEventMap.Add(Keys.NumPad6, this.m_insertLogicEventHandller);
    this.m_keyEventMap.Add(Keys.NumPad7, this.m_insertLogicEventHandller);
    this.m_keyEventMap.Add(Keys.NumPad8, this.m_insertLogicEventHandller);
    this.m_keyEventMap.Add(Keys.NumPad9, this.m_insertLogicEventHandller);

    this.m_keyEventMap.Add(Keys.Back, this.m_deleteLogicEventHandller);
    this.m_keyEventMap.Add(Keys.Delete, this.m_deleteLogicEventHandller);

    this.m_keyEventMap.Add(Keys.Up, this.m_moveLogicEventHandller);
    this.m_keyEventMap.Add(Keys.Home, this.m_moveLogicEventHandller);
    this.m_keyEventMap.Add(Keys.Left, this.m_moveLogicEventHandller);
    this.m_keyEventMap.Add(Keys.Right, this.m_moveLogicEventHandller);
    this.m_keyEventMap.Add(Keys.Down, this.m_moveLogicEventHandller);
    this.m_keyEventMap.Add(Keys.End, this.m_moveLogicEventHandller);
    this.m_keyEventMap.Add(Keys.Enter, this.m_moveLogicEventHandller);

    this.m_keyEventMap.Add(Keys.OemMinus, this.m_changeSignLogicEventHandller);
    this.m_keyEventMap.Add(Keys.Subtract, this.m_changeSignLogicEventHandller);

    // property initial value
    this.TextAlign = HorizontalAlignment.Right;
    this.ShortcutsEnabled = false;

    // initial display
    updateText();
}

#endregion


#region Property
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[Description("Currency value of string")]
public string Value {
    get { return this.m_value; }
    set {
        this.m_value = value;
        updateText();
    }
}

[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public decimal CurrencyValue {
    get {
        decimal value = 0;
        try {
            value = decimal.Parse(this.Value);
        } catch (FormatException) {
            value = 0;
        } catch (OverflowException) {
            value = 0;
        }
        if (this.m_isSign) {
            value = -value;
        }
        return value;
    }
}

[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[Description("Currency mark. (ex: \\, $)")]
public string CurrencyMark {
    get { return this.m_currencyMark; }
    set {
        this.m_currencyMark = value;
        syncPrefix();
    }
}

// 全然設計してないprotected君達
protected string Prefix {
    get { return this.m_prefix; }
}

protected int SeparateLength {
    get { return this.m_sepLength;}
    set { this.m_sepLength = value; }
}

protected Dictionary<Keys, CurrencyKeyEventHandler> KeyEventMap {
    get { return this.m_keyEventMap; }
}
#endregion

#region override raise event method
// ほんとはちゃんとやりたいねぇ
protected override void OnMouseDown(MouseEventArgs e) {
    base.OnMouseDown(e);
    this.SelectionStart = getSelectionStart();
    this.SelectionLength = 0;
}

// ほんとはちゃんとやりたいねぇ
protected override void OnMouseUp(MouseEventArgs mevent) {
    base.OnMouseUp(mevent);
    this.SelectionStart = getSelectionStart();
    this.SelectionLength = 0;
}

// イベント処理とKeyPressの抹殺
protected override void OnKeyDown(KeyEventArgs e) {
    base.OnKeyDown(e);
    if (this.m_keyEventMap.ContainsKey(e.KeyCode)) {
        this.m_keyEventMap[e.KeyCode](e.KeyCode);
        e.SuppressKeyPress = true;
    }

    // MEMO:dust
    if (e.Control && (e.KeyCode == Keys.V)) {
        Paste();
    }
    if (e.Shift && (e.KeyCode == Keys.Insert)) {
        Paste();
    }
}

// 抹殺
protected override void OnKeyPress(KeyPressEventArgs e) {
    base.OnKeyPress(e);
    e.Handled = true;
}

// Focusとった時にずれるので抹殺・・。切ねぇ
protected override void OnGotFocus(EventArgs e) {
    base.OnGotFocus(e);
    this.SelectionLength = 0;
    this.SelectionStart = getSelectionStart();
}

#endregion

#region virtual and non virtual protected method
/// <summary>Valueの値に合わせて表示文字列を更新する</summary>
protected virtual void updateText() {
    Char[] chars = new Char[
        this.m_prefix.Length +
        this.Value.Length +
        this.Value.Length / this.m_sepLength
        ];
    int j;
    for (j = 0; j < this.m_prefix.Length; j++) {
        chars[j] = this.m_prefix[j];
    }
    int valueLength = this.Value.Length;
    for (int i = 0; i < valueLength; i++) {
        if ((i > 0) && ((valueLength - i) % this.m_sepLength == 0)) {
            chars[j++] = ',';
        }
        chars[j++] = this.Value[i];
    }
    this.Text = new String(chars);
}

/// <summary>
/// 論理カーソル位置から表示用カーソル位置を計算して返す
/// </summary>
/// <returns>SelectionStartに適用出来る値</returns>
protected int getSelectionStart() {
    int separateCorrection;
    if (this.Value.Length > this.m_sepLength) {
        int odd = this.m_sepLength - this.Value.Length % this.m_sepLength;
        if (odd == this.m_sepLength)
            odd = 0;
        separateCorrection = (this.m_cursorPoint + odd) / this.m_sepLength;
    } else {
        separateCorrection = this.m_cursorPoint / this.m_sepLength;
    }
    int cursor =
        this.m_cursorPoint
        + this.m_prefix.Length
        + separateCorrection;
    return cursor;
}

/// <summary>
/// プリフィックスの値を現在の状態に合わせて更新する
/// </summary>
protected void syncPrefix() {
    if (this.m_isSign) {
        this.m_prefix = this.m_currencyMark + "-";
    } else {
        this.m_prefix = this.m_currencyMark;
    }
}
#endregion

#region private event handller
// 符号反転
void changeSignLogic(Keys key) {
    this.m_isSign = !this.m_isSign;
    syncPrefix();
    updateText();
    this.SelectionStart = getSelectionStart();
}

// 文字列消去
void deleteLogic(Keys key) {
    if (this.Value.Length > 0) {
        int delP = this.m_cursorPoint;
        if (key == Keys.Back) {
            delP--;
        }
        if ((delP >= 0) && (delP < this.Value.Length)) {
            this.Value = this.Value.Remove(
                delP,
                1
            );
            this.m_cursorPoint = delP;
        }
    }
    updateText();
    this.SelectionStart = getSelectionStart();
}

// 文字列挿入
void insertLogic(Keys key) {
    this.Value = this.Value.Insert(
        this.m_cursorPoint,
        KEY_VALUE[key].ToString()
    );
    this.m_cursorPoint++;
    this.SelectionStart = getSelectionStart();
}

// カーソル移動
void moveLogic(Keys key) {
    switch (key) {
        case Keys.Up:
        case Keys.Home:
            this.m_cursorPoint = 0;
            this.SelectionStart = getSelectionStart();
            break;
        case Keys.Down:
        case Keys.End:
            this.m_cursorPoint = this.Value.Length;
            this.SelectionStart = getSelectionStart();
            break;
        case Keys.Right:
            if (this.m_cursorPoint < this.Value.Length) {
                this.m_cursorPoint++;
            }
            this.SelectionStart = getSelectionStart();
            break;
        case Keys.Left:
            if (this.m_cursorPoint > 0) {
                this.m_cursorPoint--;
            }
            this.SelectionStart = getSelectionStart();
            break;
        case Keys.Enter:
            Control ctl = this.Parent.GetNextControl(this, true);
            while (ctl.TabStop == false) {
                ctl = this.Parent.GetNextControl(ctl, true);
                if (ctl == null) {
                    // ??
                    break;
                }
            }
            if (ctl != null)
                ctl.Focus();
            break;
    }
    this.SelectionLength = 0;
}
#endregion

#region dust box
// ごみ
public override bool PreProcessMessage(ref Message msg) {
    if (msg.Msg == 0x0100) {
        switch ((int)msg.WParam) {
            case (int)Keys.Insert:
                // ShortcutEnabledの設定を横取り
                return false;
            case (int)Keys.V:
                // ShortcutEnabledの設定を横取り
                return false;
        }
    }
    return base.PreProcessMessage(ref msg);
}

// TODO:インスタントコード
public new void Paste() {
    try {
        Paste(Clipboard.GetText());
    } catch (Exception) {
        // しらね
    }
}
public new void Paste(string text) {
    try {
        string test = text;
        foreach (char c in test.ToCharArray()) {
            if (this.m_keyEventMap.ContainsKey((Keys)c)) {
                this.m_keyEventMap[(Keys)c]((Keys)c);
            }
        }
    } catch (Exception) {
        // しらね
    }
}
#endregion
}

解説

  • 負荷が高くてはてなさんが実は嫌な顔していたら、スマン。
  • コメント書いてない辺りだと、TextBox継承してるのでTextBox関係のプロパティ(ShortcutsEnabledとかね)をいじられたらサックリ死にます。
  • keyEventMapとかはなんも考えてない。素直にIf文分岐にしろよという声が聞こえてきそう。
  • 今回初めてTextBoxの継承ってのを試してみたが、そんなに悪くないかも。
  • SelectionLengthをいじってないので使いづらい
  • マウスでカーソル位置を調節出来ないので使いづらい
  • IME関係は試してない

以上。
誰か素敵コードにしてくれないかなぁ、なんて言ってみたり・・。