wpf 如何在不激怒我的用户的情况下格式化文本框的十进制绑定?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/11477821/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How can I format a decimal bound to TextBox without angering my users?
提问by epalm
I'm trying to display a formatted decimal in a TextBox using data binding in WPF.
我正在尝试使用 WPF 中的数据绑定在 TextBox 中显示格式化的十进制数。
Goals
目标
Goal 1: When setting a decimal property in code, display 2 decimal places in the TextBox.
目标 1:在代码中设置小数属性时,在 TextBox 中显示 2 个小数位。
Goal 2: When a user interacts with (types in) the TextBox, don't piss him/her off.
目标 2:当用户与(输入)TextBox 交互时,不要激怒他/她。
Goal 3: Bindings must update source on PropertyChanged.
目标 3:绑定必须在 PropertyChanged 上更新源。
Attempts
尝试
Attempt 1: No formatting.
尝试 1:没有格式化。
Here we're starting nearly from scratch.
在这里,我们几乎从头开始。
<TextBox Text="{Binding Path=SomeDecimal, UpdateSourceTrigger=PropertyChanged}" />
Violates Goal 1. SomeDecimal = 4.5
will show "4.50000" in the TextBox.
违反目标 1。SomeDecimal = 4.5
将在文本框中显示“4.50000”。
Attempt 2: Use StringFormat in the Binding.
尝试 2:在绑定中使用 StringFormat。
<TextBox Text="{Binding Path=SomeDecimal, UpdateSourceTrigger=PropertyChanged, StringFormat=F2}" />
Violates Goal 2. Say SomeDecimal is 2.5, and the TextBox is displaying "2.50". If we select all and type "13.5" we end up with "13.5.00" in the TextBox because the formatter "helpfully" inserts decimals and zeros.
违反目标 2。假设 SomeDecimal 是 2.5,并且 TextBox 显示“2.50”。如果我们全选并输入“13.5”,我们最终会在文本框中输入“13.5.00”,因为格式化程序“有帮助地”插入了小数和零。
Attempt 3: use Extended WPF Toolkit's MaskedTextBox.
尝试 3:使用 Extended WPF Toolkit 的 MaskedTextBox。
http://wpftoolkit.codeplex.com/wikipage?title=MaskedTextBox
http://wpftoolkit.codeplex.com/wikipage?title=MaskedTextBox
Assuming I'm reading the documentation correctly, the mask ##0.00 means "two optional digits, followed by a required digit, a decimal point, and two more required digits. This forces me to say "the largest possible number that can go into this TextBox is 999.99" but let's say I'm ok with that.
假设我正确阅读文档,掩码 ##0.00 表示“两个可选数字,后跟一个必需数字、一个小数点和另外两个必需数字。这迫使我说“可以进入的最大可能数字这个 TextBox 是 999.99",但假设我没问题。
<xctk:MaskedTextBox Value="{Binding Path=SomeDecimal, UpdateSourceTrigger=PropertyChanged}" Mask="##0.00" />
Violates Goal 2. The TextBox starts with ___.__
and selecting it and typing 5.75 yields 575.__
. The only way to get 5.75 is to select the TextBox and type <space><space>5.75
.
违反目标 2。 TextBox 开始___.__
并选择它并输入 5.75 yields 575.__
。获得 5.75 的唯一方法是选择 TextBox 并键入<space><space>5.75
.
Attempt 4: use Extended WPF Toolkit's DecimalUpDown spinner.
尝试 4:使用扩展 WPF 工具包的 DecimalUpDown 微调器。
http://wpftoolkit.codeplex.com/wikipage?title=DecimalUpDown
http://wpftoolkit.codeplex.com/wikipage?title=DecimalUpDown
<xctk:DecimalUpDown Value="{Binding Path=SomeDecimal, UpdateSourceTrigger=PropertyChanged}" FormatString="F2" />
Violates Goal 3. DecimalUpDown happily ignores UpdateSourceTrigger=PropertyChanged. One of the Coordinators on the Extended WPF Toolkit Codeplex page suggests a modified ControlTemplate at http://wpftoolkit.codeplex.com/discussions/352551/. This satisfies Goal 3, but violates Goal 2, exhibiting the same behavior as in Attempt 2.
违反目标 3。 DecimalUpDown 愉快地忽略了 UpdateSourceTrigger=PropertyChanged。Extended WPF Toolkit Codeplex 页面上的一位协调员建议在http://wpftoolkit.codeplex.com/discussions/352551/ 上修改 ControlTemplate 。这满足目标 3,但违反目标 2,表现出与尝试 2 相同的行为。
Attempt 5: Using style datatriggers, only use formatting if user is not editing.
尝试 5:使用样式数据触发器,仅在用户未编辑时使用格式。
Binding to a double with StringFormat on a TextBox
在 TextBox 上使用 StringFormat 绑定到双精度值
Even if this one satisfied all three goals, I wouldn't want to use it. (A) Textboxes become 12 lines each instead of 1, and my application contains many, many textboxes. (B) All my textboxes already have a Style attribute which points to a global StaticResource which sets Margin, Height, and other things. (C) You may have noticed the code below sets the binding Path twice, which violates the DRY principal.
即使这个满足所有三个目标,我也不想使用它。(A) 每个文本框变成 12 行而不是 1 行,我的应用程序包含很多很多文本框。(B) 我所有的文本框都已经有一个 Style 属性,它指向一个全局 StaticResource,它设置边距、高度和其他东西。(C) 您可能已经注意到下面的代码两次设置了绑定路径,这违反了 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>
All these uncomfortable things aside...
抛开这些不舒服的事情...
Violates Goal 2. First, clicking on a TextBox which displays "13.50" suddenly makes it display "13.5000", which is unexpected. Second, if starting with a blank TextBox, and I type "13.50", the TextBox will contain "1350". I can't explain why, but pressing the period key doesn't insert decimals if the cursor is at the right end of the string in the TextBox. If the TextBox contains a string with length > 0, and I reposition the cursor to anywhere except the right end of the string, I can then insert decimal points.
违反目标 2。首先,单击显示“13.50”的 TextBox 突然使其显示“13.5000”,这是出乎意料的。其次,如果从一个空白的 TextBox 开始,我输入“13.50”,则 TextBox 将包含“1350”。我无法解释原因,但如果光标位于文本框中字符串的右端,则按句点键不会插入小数。如果 TextBox 包含长度 > 0 的字符串,并且我将光标重新定位到字符串右端以外的任何位置,则可以插入小数点。
Attempt 6: Do it myself.
尝试 6:自己做。
I'm about to embark on a jouney of pain and suffering, by either subclassing TextBox, or creating an attached property, and writing the formatting code myself. It will be full of string manipulation, and cause substantial hairloss.
通过继承 TextBox 或创建附加属性并自己编写格式代码,我即将开始痛苦和痛苦的旅程。它会充满字符串操作,并导致大量脱发。
Does anyone have any suggestions for formatting decimals bound to textboxes that satisfies all the above goals?
有没有人对格式化绑定到满足上述所有目标的文本框的小数有任何建议?
回答by Vladimir Dorokhov
Try to resolve that on ViewModel level. That it:
尝试在 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:
Xml:
<TextBox x:Name="tb" Text="{Binding Path=SomeDecimalAsString, UpdateSourceTrigger=PropertyChanged}" />
Code behind:
后面的代码:
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;
}
回答by Alex Hope O'Connor
I created the following custom behavior to move the users cursor to after the decimal point when using StringFormat={}{0:0.00}
, which forces a decimal place to be present, however this can cause the following issue:
我创建了以下自定义行为以在使用时将用户光标移动到小数点后StringFormat={}{0:0.00}
,这会强制出现小数位,但这可能会导致以下问题:
Violates Goal 2. Say SomeDecimal is 2.5, and the TextBox is displaying "2.50". If we select all and type "13.5" we end up with "13.5.00" in the TextBox because the formatter "helpfully" inserts decimals and zeros.
违反目标 2。假设 SomeDecimal 是 2.5,并且 TextBox 显示“2.50”。如果我们全选并输入“13.5”,我们最终会在文本框中输入“13.5.00”,因为格式化程序“有帮助地”插入了小数和零。
I have hacked around this using a custom behavior that will move the users cursor to after the decimal place when they press the . key:
我已经使用自定义行为解决了这个问题,当用户按下 . 钥匙:
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
}
}
I use it as follows:
我使用它如下:
<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>
回答by Xcalibur37
Try the WPF Extended Tookit Masked TextBox to implement an Input Mask: http://wpftoolkit.codeplex.com/wikipage?title=MaskedTextBox
尝试使用 WPF Extended Toolkit Masked TextBox 来实现输入掩码:http://wpftoolkit.codeplex.com/wikipage?title=MaskedTextBox
Example:
例子:
<toolkit:MaskedTextBox Mask="(000) 000-0000" Value="(555) 123-4567"
IncludeLiterals="True" />