午後わてんのブログ

ベランダ菜園とWindows用アプリ作成(WPFとC#)

WPFにもNumericUpDownみたいなのをユーザーコントロールで、その6

前回の
gogowaten.hatenablog.com
これに

gogowaten.hatenablog.com
このときのを取り入れて

f:id:gogowaten:20200703161314g:plain
「-0.」とかの入力と、書式も設定できるようになった

  • TextBoxに数字の入力中にもMyValueに反映するようにした
  • 書式の設定の入力中にもMyTextに反映するようにした
  • TextBoxに入力中はMyValueの値をそのまま表示
  • TextBoxからフォーカスが離れたら書式を適用

プロパティ名 既定値 説明
MyValue decimal 0m
MyText String "" MyValueを書式に従ってToStringしたもの、表示用
MyMinValue decimal decimal.MinValue 最小値
MyMaxValue decimal decimal.MaxValue 最大値
MySmallChange decimal 1m 小変更値、ボタン押したときの変化値
MyLargeChange decimal 10m 大変更値、ボタンの上でホイール回したときの変化値
MyStringFormat String "" 書式、MyValue.ToString(書式)で使う


userControl/ControlLibraryCore20200620 at 0703_2312_blog · gogowaten/userControl

github.com

f:id:gogowaten:20200703231918p:plain

MyValueは内部の生の値:decimal型
MyTextは表示用の値:String型
どちらも依存関係プロパティで、値が変更されたときだけ実行されるPropertyChangedCallbackを設定している。

前回までは
MyValueのCallbackでの処理内容は
MyText = 新しい値.ToString(書式)

MyTextのCallbackでの処理内容は
MyValue = decimal.Parse(新しい値)

f:id:gogowaten:20200703165408p:plain
書式が0.0のときMyValueが1.52だと、MyTextは1.5表記
ここでMyValueが2.52に変更される(処理の流れ1)と、MyValueのCallbackが実行されて、2.52に書式設定された"2.5"がMyTextに入力される(2)
MyTextが"1.5"から"2.5"に変わったので、MyTextのCallbackが実行されて、"2.5"をdecimal型に変換した2.5がMyValueに入力される(3)
MyValueが2.52から2.5に変わったので、MyValueのCallbackが実行されて、2.5に書式設定された"2.5"がMyTextに入力される(4)
MyTextは"2.5"から"2.5"で変化なしなのでCallbackは実行されない
これで処理完了になる。PropertyChangedCallbackは値が変化したときだけ実行されるおかげで、無限ループしなくて済んでいるけど、結果を見ると2.52が入力されたMyValueが2.5になってしまう。

今回の
MyValueのCallbackでの処理内容
MyText = 新しい値.ToString(書式)
MyValue = 新しい値

MyTextのCallbackでの処理内容
MyValue = decimal.Parse(新しい値)

赤字のところを付け足した

処理の流れ
f:id:gogowaten:20200703171410p:plain
6番目以降が付け足した赤字部分の処理になる
結果は期待通り
これで-0.とか入力できるようになって、内部の値MyValueと表示用の値MyTextをそれぞれ設定できるようになった

数値を文字列に変換するときの書式設定

これも入力中にリアルタイムに反映するようにしたかったので、String型の依存関係プロパティを追加した
名前はMyStringFormat

//書式指定用の文字列型依存関係プロパティ
public string MyStringFormat
{
    get { return (string)GetValue(MyStringFormatProperty); }
    set { SetValue(MyStringFormatProperty, value); }
}

public static readonly DependencyProperty MyStringFormatProperty =
    DependencyProperty.Register(nameof(MyStringFormat), typeof(string), typeof(NumericUpDown),
        new PropertyMetadata("", OnMyStrinfFormatChanged, CoerceMyStrinfFormatValue));

private static void OnMyStrinfFormatChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var ud = d as NumericUpDown;
    var sf = (string)e.NewValue;
    decimal m = ud.MyValue;

    ud.MyText = ud.MyValue.ToString(sf);
    ud.MyValue = m;

}

//新しい書式の判定、不適切な場合は古い書式で上書きする
private static object CoerceMyStrinfFormatValue(DependencyObject d, object baseValue)
{
    var ud = d as NumericUpDown;
    var format = (string)baseValue;//新しい書式

    //正の値、負の値、0のときで書式を変えられる
    //セミコロンで区切る
    //(正の値書式 ; 負の値書式 ; 0の書式)

    //無限ループになる書式は数値に変換できる、かつ
    // ┣ 0以外
    // ┣ 「,.」が含まれている
    // ┣ 正の値の書式の先頭がハイフン
    // ┗ 負の値の書式の先頭がハイフンではない          
    //MyValueとMyTextのCallback間で無限ループになってStackOverflowになってしまう

    //正、負、0、それぞれを判定するため「;」で文字列を分割
    var neko = format.Split(";");
    for (int i = 0; i < neko.Length; i++)
    {
        string sf = neko[i];
        if (decimal.TryParse(sf, out decimal m))
        {
            if (Math.Abs(m) != 0 || sf.Contains(",."))
            {
                return ud.MyStringFormat;
            }
            if (i == 0 && sf.StartsWith("-"))
            {
                return ud.MyStringFormat;
            }
            if(i==1 && sf.StartsWith("-") == false)
            {
                return ud.MyStringFormat;
            }
        }
        try
        {
            //新しい書式適用してみてエラーならcatchに飛ぶ
            var text = ud.MyValue.ToString(format);

            //正の値が負の値に変化するような書式も弾く
            decimal dc = 1m;
            if (decimal.TryParse(dc.ToString(format), out decimal dd))
            {
                //「-.」や「-,.」などは「-」と同じ効果で、正の値が負の値に反転する
                if (dd == -1m)
                {
                    return ud.MyStringFormat;
                }
            }
        }
        catch (Exception)
        {
            return ud.MyStringFormat;
        }
    }

    return format;

}

PropertyChangedCallbackでの処理は236行目
f:id:gogowaten:20200703172953p:plain
240行目
今のMyValueを確保してから
242行目
新しい書式をMyValueに適用して、MyTextに入れて
243行目
確保しておいたMyValueをMyValueに入れる
これは、さっきのMyValueのCallbackで付け足した処理と同じことで、こうしておかないとMyTextを変更したときのMyTextのCallbackでMyValueが変な値になってしまう

Propertyに値が入る直前に必ず実行されるCoerceValueCallback
f:id:gogowaten:20200703183525p:plain
長い
ToString()で指定できる書式設定はかなり柔軟?で123って数値に

書式  結果
"0"     "123"
"0."    "123"
"0,"    "0"
"0,."   "0"
"50"    "5123"
"99088" "9912388"
"a"     エラー
"a0"    "a123"
";"     ""
"-0"    "-123"

基本的には0のところに元の数値が入るけど、0になったり、空白になったり、エラーになったりするし他にも変なのがあるかも。数値が変化してしまうようなものはMyTextとMyValueのCallback間で無限ループになるので、そのような書式だった場合は元の古い書式に戻す(上書きする)ようにしている。

わかった範囲での対処療法的な処理だからツギハギだらけになってしまった。もっといい方法があるはずだけど、今回はこうなった

入力値のリアルタイムでの反映をしなければこんなに大変じゃなかったけど、なんかできそう、やっぱできないかもを繰り返してなんとかできた。まだなんかある気もするけどこれでいいかな



関連記事
次回は3日後
gogowaten.hatenablog.com 今回のUserControlを公開してみた

その7は10日後
gogowaten.hatenablog.com