如何在不激怒用户的情况下格式化绑定到TextBox的小数?

时间:2012-07-13 20:21:52

标签: wpf textbox decimal string-formatting

我正在尝试使用WPF中的数据绑定在TextBox中显示格式化的十进制数。

目标

目标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”。如果我们选择all并键入“13.5”,我们在TextBox中以“13.5.00”结束,因为格式化程序“有用”地插入小数和零。

尝试3:使用Extended WPF Toolkit的MaskedTextBox。

http://wpftoolkit.codeplex.com/wikipage?title=MaskedTextBox

假设我正确阅读文档,掩码## 0.00表示“两个可选数字,后跟一个必需的数字,一个小数点和两个更多的必需数字。这迫使我说”最大可能的数字可以进入这个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页面上的协调员之一建议在http://wpftoolkit.codeplex.com/discussions/352551/处修改ControlTemplate。这符合目标3,但违反了目标2,表现出与尝试2中相同的行为。

尝试5:使用样式数据触发器,仅在用户未编辑时才使用格式化。

Binding to a double with StringFormat on a TextBox

即使这个目标满足所有三个目标,我也不想使用它。 (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>

所有这些令人不舒服的事情......

违反目标2.首先,单击显示“13.50”的TextBox突然显示“13.5000”,这是意外的。其次,如果以空白TextBox开头,并输入“13.50”,则TextBox将包含“1350”。我无法解释原因,但如果光标位于TextBox中字符串的右端,则按句点键不会插入小数。如果TextBox包含长度为&gt;的字符串。 0,我将光标重新定位到除了字符串右端之外的任何地方,然后我可以插入小数点。

尝试6:自己动手。

我即将通过继承TextBox或创建附加属性并自己编写格式化代码来开始痛苦和痛苦。它将充满字符串操作,并导致大量的hairloss。


是否有人建议格式化绑定到满足上述所有目标的文本框的小数?

3 个答案:

答案 0 :(得分:5)

尝试在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;
}

答案 1 :(得分:2)

我创建了以下自定义行为,以便在使用StringFormat={}{0:0.00}时将用户光标移动到小数点后面,这会强制出现小数位,但这可能会导致以下问题:

  

违反目标2.说SomeDecimal是2.5,TextBox显示“2.50”。如果我们选择all并键入“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>

答案 2 :(得分:1)

尝试使用WPF Extended Toolkit Maskedtextbox来实现输入掩码: http://wpftoolkit.codeplex.com/wikipage?title=MaskedTextBox

示例:

<toolkit:MaskedTextBox Mask="(000) 000-0000" Value="(555) 123-4567" 
IncludeLiterals="True" />