52

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

目标

目标 1:在代码中设置小数属性时,在 TextBox 中显示 2 个小数位。

目标 2:当用户与 TextBox 交互(输入)时,不要惹恼他/她。

目标 3:绑定必须更新 PropertyChanged 上的源。

尝试

尝试1:没有格式化。

在这里,我们几乎是从零开始。

<TextBox Text="{Binding Path=SomeDecimal, UpdateSourceTrigger=PropertyChanged}" />

违反目标 1。SomeDecimal = 4.5将在文本框中显示“4.50000”。

尝试 2:在 Binding 中使用 StringFormat。

<TextBox Text="{Binding Path=SomeDecimal, UpdateSourceTrigger=PropertyChanged, StringFormat=F2}" />

违反目标 2。假设 SomeDecimal 是 2.5,而 TextBox 显示“2.50”。如果我们选择全部并输入“13.5”,我们最终会在文本框中输入“13.5.00”,因为格式化程序“有帮助”插入小数和零。

尝试 3:使用扩展 WPF 工具包的 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:使用样式数据触发器,仅在用户不编辑时使用格式。

在 TextBox 上使用 StringFormat 绑定到 double

即使这个满足所有三个目标,我也不想使用它。(A) 每个文本框变成 12 行而不是 1 行,我的应用程序包含很多很多文本框。(B) 我所有的文本框都已经有一个 Style 属性,它指向一个全局 StaticResource,它设置 Margin、Height 和其他东西。(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”的文本框会突然显示“13.5000”,这是出乎意料的。其次,如果从一个空白文本框开始,我输入“13.50”,文本框将包含“1350”。我无法解释为什么,但如果光标位于文本框中字符串的右端,则按句点键不会插入小数。如果 TextBox 包含长度 > 0 的字符串,并且我将光标重新定位到字符串右端以外的任何位置,则可以插入小数点。

尝试6:自己做。

通过继承 TextBox 或创建附加属性并自己编写格式化代码,我即将开始一段痛苦的旅程。它会充满串串操作,并导致大量脱发。


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

4

3 回答 3

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));
        }
    }

样本

xml:

<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;
}
于 2012-07-13T21:17:19.040 回答
2

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

违反目标 2。假设 SomeDecimal 是 2.5,而 TextBox 显示“2.50”。如果我们选择全部并输入“13.5”,我们最终会在文本框中输入“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>
于 2016-02-05T11:26:11.693 回答
1

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

例子:

<toolkit:MaskedTextBox Mask="(000) 000-0000" Value="(555) 123-4567" 
IncludeLiterals="True" />
于 2012-07-13T20:57:58.767 回答