サードパーティ製 WPF 数値コントロールのカスタマイズ

現在、Infragistics 社の NetAdvantage for WPF と 先日発売になったばかりの GrapeCity 社の InputMan for WPF を使って開発しています。どちらも数値コントロールを提供しててどちらもなかなか凝った仕様なんですが、両社の数値コントロールを開発しながら比較するとお互い一長一短あるのが正直な感想。開発中のシステムの仕様がかなり細かいので、対応するためには少々細かいカスタマイズが必要です。

ちなみに NetAdvantage for WPF のバージョンは 10.3.20103.2096、InputMan for WPF は 1.0.2010.1108 です。将来仕様に変更あるかも知れませんが、とりあえず 2011/04/20 現時点での評価ですのでご了承ください。


NetAdvantage for WPF の XamNumericEditor

NetAdvantage for WPF の数値コントロール XamNumericEditor ですが、初期値が 0 の場合、0 の後にカーソルを置きキーボードで 1 を入力すると、01 という表示になります。フォーカスがロストすると 1 の表示になります。


これ WinForm 版使ってたユーザーからクレームがあって、WinForm のコントロールと同じ挙動・・・初期値が 0 で数値の右側にカーソルがある場合、キーボードから 1 を入力したら直ちに 1 になる・・・ようにしろとの要求が出ました。


・・・ぶっちゃけ Forms で使ってたコントロールって InputMan の GcNumber なんですけどね(汗)


Infragistics 社に直接問い合わせてみたところ、現状 TextChanged イベントハンドラで対応するしかないとのことでコードも教えて頂きましたが、これをいちいち View に埋め込むのはやはり抵抗があります。で、使いまわせるように添付ビヘイビアにしてみました。以下コードです。

using System;
using System.Windows;
using Infragistics.Windows.Editors;

namespace tenz.Tools.Presentation {

	/// <summary>
	/// XamDataGrid 添付ビヘイビア
	/// </summary>
	public class XamNumericEditorBehaviors {

		public static readonly DependencyProperty IsDeletableToFirstZeroOfEditingProperty =
			DependencyProperty.RegisterAttached(
				"IsDeletableToFirstZeroOfEditing", typeof(bool),
				typeof(XamNumericEditorBehaviors), 
				new UIPropertyMetadata(false, OnIsDeletableToFirstZeroOfEditingChanged));

		public static bool GetIsDeletableToFirstZeroOfEditing(DependencyObject obj) {
			if (obj == null) {
				throw new ArgumentNullException("obj");
			}
			return (bool)obj.GetValue(IsDeletableToFirstZeroOfEditingProperty);
		}

		public static void SetIsDeletableToFirstZeroOfEditing(DependencyObject obj, bool value) {
			if (obj == null) {
				throw new ArgumentNullException("obj");
			}
			obj.SetValue(IsDeletableToFirstZeroOfEditingProperty, value);
		}

		private static void OnIsDeletableToFirstZeroOfEditingChanged
			(DependencyObject sender, DependencyPropertyChangedEventArgs e) {

			var editor = sender as XamNumericEditor;
			if (editor == null) return;

			editor.TextChanged -= OnTextChanged;
			var newValue = (bool)e.NewValue;
			if (newValue) {
				editor.TextChanged += OnTextChanged;
			}
		}

		static void OnTextChanged(object sender, RoutedEventArgs e) {
			var editor = sender as XamNumericEditor;
			if (editor == null) return;
			// 先頭の 0 を削除
			if (editor.Text.Length > 1 && editor.Text.Substring(0, 1) == "0") {
				editor.Text = editor.Text.Substring(1);
			}
		}
	}
}


これで後はこんな感じで依存関係プロパティとして使えばよし。XamNumericEditorBehaviors.IsDeletableToFirstZeroOfEditing を True にすると、XamNumericEditor の値が 0 の時に数値を入力すると、直ちにその数値にかわります。

<igEditors:XamNumericEditor 
		pz:XamNumericEditorBehaviors.IsDeletableToFirstZeroOfEditing="True" />

InputMan for WPF の GcNumber

InputMan for WPF の数値コントロール GcNumber。こちらは WinForm の仕様を引き継いでるのか、上記のような問題は発生しません。ただしこれも問題があって、MaxValue プロパティで上限値を設定しても、編集中は MaxValue 以上の値が入力出来てしまいます。しかも数値を入力するたび ValueChanged イベントが走るので、これを知らないと場合によってはオーバーフローエラーが発生する場合があります。
GcNumber の挙動を見ると MaxValue が適用されるのはロストフォーカス時と思われます。バインドしてるなら問題ないのでしょうが、コードビハインドでイベントハンドラを使う場合は注意が必要です。
これを回避するのもやっぱり添付ビヘイビア。TextChanging イベントでチェックして、MaxValue を超えるなら値の変更をキャンセルするようにします。

using System;
using System.Windows;
using GrapeCity.Windows.InputMan;

namespace tenz.Tools.Presentation {

	/// <summary>
	/// GcNumber 添付ビヘイビア
	/// </summary>
	public class GcNumberBehavior {

		public static readonly DependencyProperty IsLimitMaxValueProperty =
			DependencyProperty.RegisterAttached(
				"IsLimitMaxValue", typeof(bool),
				typeof(GcNumberBehavior),
				new UIPropertyMetadata(false, OnIsLimitMaxValueOfTextChanging));

		public static bool GetIsLimitMaxValue(DependencyObject obj) {
			if (obj == null) {
				throw new ArgumentNullException("obj");
			}
			return (bool)obj.GetValue(IsLimitMaxValueProperty);
		}

		public static void SetIsLimitMaxValue(DependencyObject obj, bool value) {
			if (obj == null) {
				throw new ArgumentNullException("obj");
			}
			obj.SetValue(IsLimitMaxValueProperty, value);
		}

		private static void OnIsLimitMaxValueOfTextChanging
			(DependencyObject sender, DependencyPropertyChangedEventArgs e) {

			var editor = sender as GcNumber;
			if (editor == null) return;

			editor.TextChanging -= OnTextChanging;
			var newValue = (bool)e.NewValue;
			if (newValue) {
				editor.TextChanging += OnTextChanging;
			}
		}

		static void OnTextChanging(object sender, TextChangingEventArgs e) {

			var number = sender as GcNumber;
			if (number == null) return;

			decimal d;
			if (decimal.TryParse(e.Result, out d) && d > number.MaxValue) {
				e.Cancel = true;
			}
		}
	}
}


後は XAML で以下のように設定すればおk。

<im:GcNumber pz:GcNumberBehavior.IsLimitMaxValue="true" />


え、MVVM 使えよと?・・・WPF 使うならそれが理想というか本筋なんでしょうが、ActiveX を View に使ってる関係でコードビハインドから抜けらんないという大人の事情があるんですよ。orz


比較してみて・・・

先日誰かが言ってたけど、コンポーネント業界も棲み分けが始まってるようで、入力系が得意な会社と表示系が得意な会社に段々分かれてきてると聞きました。私がここ2〜3ヶ月近く使った感じでは、表示系では表現力豊かなグリッド類を提供する Infragistics が頭一つ抜きん出てて、入力系では日本語環境に強い GrapeCity に軍配が上がるんじゃないでしょうか。ま、あくまで私感ですのであしからず。