我如何格式化一个十进制的文本框而不会激怒我的用户?
我试图在TextBox中使用WPF中的数据绑定显示格式化的十进制数。
目标
目标1:在代码中设置小数位数时,在文本框中显示2个小数位。
目标2:当用户与TextBox进行交互时,不要把他/她closures。
目标3:绑定必须更新PropertyChanged上的源代码。
尝试
尝试1:没有格式。
这里我们几乎从头开始。
<TextBox Text="{Binding Path=SomeDecimal, UpdateSourceTrigger=PropertyChanged}" />
违反目标1. SomeDecimal = 4.5
将在文本框中显示“4.50000”。
尝试2:在绑定中使用StringFormat。
<TextBox Text="{Binding Path=SomeDecimal, UpdateSourceTrigger=PropertyChanged, StringFormat=F2}" />
违反目标2.说SomeDecimal是2.5,文本框显示“2.50”。 如果我们select全部并input“13.5”,我们最终在TextBox中input“13.5.00”,因为格式化程序“有用地”插入了小数和零。
尝试3:使用扩展WPF工具包的MaskedTextBox。
http://wpftoolkit.codeplex.com/wikipage?title=MaskedTextBox
假设我正确地阅读了文档,掩码## 0.00的意思是“两个可选的数字,后面跟着一个必要的数字,一个小数点,还有两个必需的数字,这迫使我说”可能进入的最大数字这个文本框是999.99“但是让我们说我没事。
<xctk:MaskedTextBox Value="{Binding Path=SomeDecimal, UpdateSourceTrigger=PropertyChanged}" Mask="##0.00" />
违反目标2.文本框以___.__
开始并select它并input5.75得到575.__
。 获得5.75的唯一方法是select文本框并键入<space><space>5.75
。
尝试4:使用扩展WPF工具包的DecimalUpDown微调器。
http://wpftoolkit.codeplex.com/wikipage?title=DecimalUpDown
<xctk:DecimalUpDown Value="{Binding Path=SomeDecimal, UpdateSourceTrigger=PropertyChanged}" FormatString="F2" />
违反目标3. DecimalUpDown愉快地忽略UpdateSourceTrigger = PropertyChanged。 扩展WPF Toolkit Codeplex页面上的一位协调员build议在http://wpftoolkit.codeplex.com/discussions/352551/上修改ControlTemplate。 这符合目标3,但违反了目标2,performance出与尝试2相同的行为。
尝试5:使用样式数据触发器,仅在用户未编辑时才使用格式。
绑定到TextBox上的StringFormat的double
即使这个人满足这三个目标,我也不想使用它。 (A)文本框变成12行而不是1,我的应用程序包含很多很多的文本框。 (B)我所有的文本框已经有一个Style属性,指向一个全局的StaticResource,它设置Margin,Height和其他东西。 (C)您可能已经注意到下面的代码设置绑定path两次,这违反了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”的文本框,突然显示“13.5000”,这是意想不到的。 其次,如果从一个空白的TextBox开始,并且input“13.50”,则TextBox将包含“1350”。 我不能解释为什么,但如果光标位于TextBox中string的右端,按下句点键不会插入小数。 如果TextBox包含长度大于0的string,并将光标重新定位到除string右端外的任何位置,则可以插入小数点。
尝试6:自己做。
我即将开始痛苦和折磨,通过inheritanceTextBox,或者创build一个附属属性,并自己编写格式化代码。 它将充满string操作,并导致大量hairloss。
有没有人有任何build议,格式化满足所有上述目标的文本框绑定小数?
尝试在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)); } }
样品
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; }
我创build了以下自定义行为,以便在使用StringFormat={}{0:0.00}
时强制将小数点后的用户光标移到小数点位置,这将强制显示小数位,但是这可能会导致以下问题:
违反目标2.说SomeDecimal是2.5,文本框显示“2.50”。 如果我们select全部并input“13.5”,我们最终在TextBox中input“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来实现input掩码: http ://wpftoolkit.codeplex.com/wikipage?title=MaskedTextBox
例:
<toolkit:MaskedTextBox Mask="(000) 000-0000" Value="(555) 123-4567" IncludeLiterals="True" />