WPFのデータバインディングを使用して、TextBoxで書式設定された10進数を表示しようとしています。
目標1:コードで小数プロパティを設定するとき、TextBoxに小数第2位を表示します。
目標2:ユーザーがTextBoxと対話する(入力する)とき、腹を立てないでください。
目標3:バインディングはPropertyChangedのソースを更新する必要があります。
試行1:フォーマットなし
ここでは、ほぼゼロから始めています。
<TextBox Text="{Binding Path=SomeDecimal, UpdateSourceTrigger=PropertyChanged}" />
目標1に違反。SomeDecimal = 4.5
は、TextBoxに「4.50000」を表示します。
試行2:バインディングでStringFormatを使用します。
<TextBox Text="{Binding Path=SomeDecimal, UpdateSourceTrigger=PropertyChanged, StringFormat=F2}" />
目標2に違反しています。SomeDecimalが2.5で、TextBoxに「2.50」が表示されているとします。すべてを選択し、「13.5」と入力すると、フォーマッターが小数とゼロを「便利に」挿入するため、TextBoxに「13.5.00」が表示されます。
試行3:Extended WPF ToolkitのMaskedTextBoxを使用します。
http://wpftoolkit.codeplex.com/wikipage?title=MaskedTextBox
ドキュメントを正しく読んでいると仮定すると、マスク## 0.00は「2つのオプションの数字と、それに続く必要な数字、小数点、さらに2つの必要な数字」を意味します。このTextBoxは999.99 "ですが、それでいいとしましょう。
<xctk:MaskedTextBox Value="{Binding Path=SomeDecimal, UpdateSourceTrigger=PropertyChanged}" Mask="##0.00" />
目標2に違反します。TextBoxは___.__
で始まり、選択して5.75と入力すると575.__
になります。 5.75を取得する唯一の方法は、TextBoxを選択して<space><space>5.75
と入力することです。
試み4:Extended WPF ToolkitのDecimalUpDownスピナーを使用します。
http://wpftoolkit.codeplex.com/wikipage?title=DecimalUpDown
<xctk:DecimalUpDown Value="{Binding Path=SomeDecimal, UpdateSourceTrigger=PropertyChanged}" FormatString="F2" />
目標3に違反します。DecimalUpDownは、UpdateSourceTrigger = PropertyChangedを喜んで無視します。 Extended WPF Toolkit Codeplexページのコーディネーターの1人が http://wpftoolkit.codeplex.com/discussions/352551/ で修正されたControlTemplateを提案しています。これは目標3を満たしますが、目標2に違反し、試行2と同じ動作を示します。
試行5:スタイルデータトリガーを使用し、ユーザーが編集していない場合にのみフォーマットを使用します。
TextBoxでStringFormatを使用してdoubleにバインド
これで3つの目標がすべて満たされたとしても、それは使いたくありません。 (A)テキストボックスはそれぞれ1行ではなく12行になり、アプリケーションには多数のテキストボックスが含まれます。 (B)すべてのテキストボックスには、Margin、Height、その他を設定するグローバルStaticResourceを指すStyle属性が既にあります。 (C)以下のコードがバインディングパスを2回設定することに気付いたかもしれません。これはDRYプリンシパルに違反しています。
<TextBox>
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Text" Value="{Binding Path=SomeDecimal, StringFormat=F2}" />
<Style.Triggers>
<Trigger Property="IsFocused" Value="True">
<Setter Property="Text" Value="{Binding Path=SomeDecimal, UpdateSourceTrigger=PropertyChanged}" />
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
これらすべての不快なことはさておき...
目標2に違反する。最初に、「13.50」を表示するTextBoxをクリックすると、突然「13.5000」が表示されますが、これは予想外です。第二に、空のTextBoxで開始し、「13.50」と入力すると、TextBoxには「1350」が含まれます。理由は説明できませんが、カーソルがTextBoxの文字列の右端にある場合、ピリオドキーを押しても小数が挿入されません。 TextBoxに長さ> 0の文字列が含まれ、カーソルを文字列の右端以外の任意の場所に再配置した場合、小数点を挿入できます。
試行6:自分でやる
TextBoxをサブクラス化するか、添付プロパティを作成し、自分で書式設定コードを記述することにより、痛みと苦しみの喜びに乗り出します。それは文字列操作でいっぱいであり、かなりの抜け毛を引き起こします。
上記の目標をすべて満たす、テキストボックスにバインドされた小数をフォーマットするための提案はありますか?
ViewModelレベルで解決してください。そのこと:
public class FormattedDecimalViewModel : INotifyPropertyChanged
{
private readonly string _format;
public FormattedDecimalViewModel()
: this("F2")
{
}
public FormattedDecimalViewModel(string format)
{
_format = format;
}
private string _someDecimalAsString;
// String value that will be displayed on the view.
// Bind this property to your control
public string SomeDecimalAsString
{
get
{
return _someDecimalAsString;
}
set
{
_someDecimalAsString = value;
RaisePropertyChanged("SomeDecimalAsString");
RaisePropertyChanged("SomeDecimal");
}
}
// Converts user input to decimal or initializes view model
public decimal SomeDecimal
{
get
{
return decimal.Parse(_someDecimalAsString);
}
set
{
SomeDecimalAsString = value.ToString(_format);
}
}
// Applies format forcibly
public void ApplyFormat()
{
SomeDecimalAsString = SomeDecimal.ToString(_format);
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
[〜#〜] sample [〜#〜]
Xaml:
<TextBox x:Name="tb" Text="{Binding Path=SomeDecimalAsString, UpdateSourceTrigger=PropertyChanged}" />
コードビハインド:
public MainWindow()
{
InitializeComponent();
FormattedDecimalViewModel formattedDecimalViewModel = new FormattedDecimalViewModel { SomeDecimal = (decimal)2.50 };
tb.LostFocus += (s, e) => formattedDecimalViewModel.ApplyFormat(); // when user finishes to type, will apply formatting
DataContext = formattedDecimalViewModel;
}
StringFormat={}{0:0.00}
を使用するときにユーザーカーソルを小数点の後に移動する次のカスタム動作を作成しました。
目標2に違反しています。SomeDecimalが2.5で、TextBoxに「2.50」が表示されているとします。すべてを選択して「13.5」と入力すると、TextBoxに「13.5.00」が表示されます。これは、フォーマッタが「小切手」で小数点とゼロを挿入するためです。
ユーザーがを押したときにユーザーカーソルを小数点以下に移動するカスタム動作を使用して、これを回避しました。キー:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
namespace GUI.Helpers.Behaviors
{
public class DecimalPlaceHotkeyBehavior : Behavior<TextBox>
{
#region Methods
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PreviewKeyDown += AssociatedObject_PreviewKeyDown;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.PreviewKeyDown -= AssociatedObject_PreviewKeyDown;
}
protected override Freezable CreateInstanceCore()
{
return new DecimalPlaceHotkeyBehavior();
}
#endregion
#region Event Methods
private void AssociatedObject_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == System.Windows.Input.Key.OemPeriod || e.Key == System.Windows.Input.Key.Decimal)
{
var periodIndex = AssociatedObject.Text.IndexOf('.');
if (periodIndex != -1)
{
AssociatedObject.CaretIndex = (periodIndex + 1);
e.Handled = true;
}
}
}
#endregion
#region Initialization
public DecimalPlaceHotkeyBehavior()
: base()
{
}
#endregion
}
}
次のように使用します。
<TextBox xmlns:Behaviors="clr-namespace:GUI.Helpers.Behaviors"
xmlns:i="clr-namespace:System.Windows.Interactivity;Assembly=System.Windows.Interactivity"
Text="{Binding Value, UpdateSourceTrigger=PropertyChanged, StringFormat={}{0:0.00}}">
<i:Interaction.Behaviors>
<Behaviors:DecimalPlaceHotkeyBehavior></Behaviors:DecimalPlaceHotkeyBehavior>
</i:Interaction.Behaviors>
</TextBox>
WPF Extended Tookit Masked TextBoxを試して、入力マスクを実装します。 http://wpftoolkit.codeplex.com/wikipage?title=MaskedTextBox
例:
<toolkit:MaskedTextBox Mask="(000) 000-0000" Value="(555) 123-4567"
IncludeLiterals="True" />