例えば、TextBoxVMを改造して数値ボックス用のViewModelを作ったとする。
using System;InputValueのプロパティ型がdecimal?なのは、空文字列をnulとして受け取りたい為。
namespace TawamureDays.ViewModels {
/// <summary>
/// 数値用TextBox用ViewModelクラス
/// </summary>
public class NumericBoxVM : UIElementVM {
#region コンストラクタ
/// <summary>
/// コンストラクタ
/// </summary>
public NumericBoxVM() : base() {
}
/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="inputValue">初期値</param>
public NumericBoxVM(decimal? inputValue) : this() {
inputValue_ = inputValue;
}
#endregion
#region プロパティ
/// <summary>入力値</summary>
private decimal? inputValue_;
/// <summary>
/// 入力値を取得|設定します。
/// </summary>
public decimal? InputValue {
get {return inputValue_;}
set {
if (inputValue_ != value) {
inputValue_ = value;
this.NotifyPropertyChanged("InputValue");
}
}
}
#endregion
}
}
これを使ってみる。
○ViewModel側
using System.Windows;
using System.Windows.Input;
namespace TawamureDays {
/// <summary>
/// ViewModelクラス
/// </summary>
public class TawamureVM : BaseWindowVM {
/// <summary>
/// 数値入力用VMを取得します。
/// </summary>
public NumericBoxVM NumericBoxVM {
get; private set;
}
/// <summary>
/// コンストラクタ
/// </summary>
public TawamureVM() {
closeCommand_ = new RelayCommand(ExecuteCloseCommand);
NumericBoxVM = new NumericBoxVM();
return;
}
}
}
○View側
<Window x:Class="TawamureDays.MainWindow"そうした場合、起動は特に問題ない。問題は入力の時に出てくる。
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tw="http://schemas.tawamuredays.jp/wpf/gui"
Title="MainWindow" Height="350" Width="525">
<tw:TawamureContents>
<StackPanel>
<TextBox DataContext="{Binding NumericBoxVM}"
Text="{Binding InputValue}"
/>
</StackPanel>
<tw:TawamureContents.Footer>
<Button Content="終われ!" Command="{Binding CloseCommand}"/>
</tw:TawamureContents.Footer>
</tw:TawamureContents>
</Window>
数値を入力する分には問題ない。問題は、数値を入力した後で、文字列をクリアした時に発生する。
数値を入力する。

その後、空にすると…

バインディングエラーを示す赤色になる。裏で発生しているエラーが下のようになる。
どうも、空文字列をdecimal?のnullに変換できないらしい。まあ、decimal?系のnullは、クラス等のnullとは違うようなので、当たり前といえば当たり前の話だった。System.Windows.Data Error: 7 : ConvertBack cannot convert value '' (type 'String'). BindingExpression:Path=InputValue; DataItem='NumericBoxVM' (HashCode=13635742); target element is 'TextBox' (Name=''); target property is 'Text' (type 'String') FormatException:'System.FormatException: 入力文字列の形式が正しくありません。
場所 System.Number.StringToNumber(String str, NumberStyles options, NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal)
場所 System.Number.ParseDecimal(String value, NumberStyles options, NumberFormatInfo numfmt)
場所 System.Convert.ToDecimal(String value, IFormatProvider provider)
場所 System.String.System.IConvertible.ToDecimal(IFormatProvider provider)
場所 System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider)
場所 MS.Internal.Data.SystemConvertConverter.ConvertBack(Object o, Type type, Object parameter, CultureInfo culture)
場所 System.Windows.Data.BindingExpression.ConvertBackHelper(IValueConverter converter, Object value, Type sourceType, Object parameter, CultureInfo culture)'
じゃあ、どうやれば、変換できるのか?といえば、これはもうConverterを使うしかない。
using System;ソース→ターゲットは、decimal?型をstring型に、ターゲット→ソースは、string型をdecimalにパースしている。decimal型に変換できない場合にdecimal?のnullを返している。
namespace TawamureDays.Converters {
/// <summary>
/// 数値型(decimal?)を文字列に変えるコンバータクラス
/// </summary>
[System.Windows.Data.ValueConversion(typeof(decimal?), typeof(string))]
public sealed class NullableDecimal2StringConverter : System.Windows.Data.IValueConverter {
#region IValueConverter メンバー
/// <summary>
/// 値を変換します。
/// </summary>
/// <param name="value">バインディング ソースによって生成された値。</param>
/// <param name="targetType">バインディング ターゲット プロパティの型。</param>
/// <param name="parameter">使用するコンバーター パラメーター</param>
/// <param name="culture">コンバーターで使用するカルチャ。</param>
/// <returns>変換された値</returns>
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
if (value == null) {
return string.Empty;
}
decimal decVal;
if (!decimal.TryParse(value.ToString(), out decVal)) {
return value;
}
var format = parameter == null ? string.Empty : parameter.ToString();
return decVal.ToString(format);
}
/// <summary>
/// 値を変換します。
/// </summary>
/// <param name="value">バインディング ターゲットによって生成される値。</param>
/// <param name="targetType">変換後の型。</param>
/// <param name="parameter">使用するコンバーター パラメーター</param>
/// <param name="culture">コンバーターで使用するカルチャ。</param>
/// <returns>変換された値</returns>
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
if (value == null) {
return (decimal?)null;
}
decimal decVal = decimal.Zero;
if (decimal.TryParse(value.ToString(), out decVal)) {
return decVal;
}
return (decimal?)null;
}
#endregion
}
}
これで、空文字列でもバインドエラーが言われなくなった。ただ、このコンバータを噛ましてしまうと、StringFormatが効かなくなる。まあ、これはどうしようもないので、ConverterParameterに書式を設定するとみなして、変換するしかない。何もかもが上手くいくものではないか。