如何在不激怒用户的情况下格式化绑定到 TextBox 的小数? [英] How can I format a decimal bound to TextBox without angering my users?

查看:13
本文介绍了如何在不激怒用户的情况下格式化绑定到 TextBox 的小数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to display a formatted decimal in a TextBox using data binding in WPF.

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

Goal 1: When setting a decimal property in code, display 2 decimal places in the TextBox.

目标 2:当用户与文本框交互(输入)时,不要惹他/她生气.

Goal 2: When a user interacts with (types in) the TextBox, don't piss him/her off.

目标 3:绑定必须在 PropertyChanged 上更新源.

Goal 3: Bindings must update source on PropertyChanged.

尝试 1:无格式.

我们几乎从零开始.

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

违反目标 1.SomeDecimal = 4.5 将显示4.50000";在文本框中.

Violates Goal 1. SomeDecimal = 4.5 will show "4.50000" in the TextBox.

尝试 2:在 Binding 中使用 StringFormat.

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

违反了目标 2.假设 SomeDecimal 为 2.5,而 TextBox 显示2.50".如果我们全选并输入13.5";我们最终得到13.5.00";在 TextBox 中,因为格式化程序有帮助"插入小数和零.

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.

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

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

假设我正确阅读了文档,掩码 ##0.00 表示两个可选数字,后跟一个必填数字、一个小数点和另外两个必填数字.这迫使我说可以进入这个 TextBox 的最大可能数字是 999.99".但假设我对此没意见.

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.

<xctk:MaskedTextBox Value="{Binding Path=SomeDecimal, UpdateSourceTrigger=PropertyChanged}" Mask="##0.00" />

违反目标 2.文本框以 ___.__ 开头,选择它并输入 5.75 会产生 575.__.获得 5.75 的唯一方法是选择 TextBox 并输入 <space><space>5.75.

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.

尝试 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 上修改 ControlTemplate.com/discussions/352551/.这满足了目标 3,但违反了目标 2,表现出与尝试 2 中相同的行为.

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.

尝试 5:使用样式数据触发器,仅在用户未进行编辑时使用格式设置.

在 TextBox 上使用 StringFormat 绑定到 double

即使这个满足所有三个目标,我也不想使用它.(A) 每个文本框变成 12 行而不是 1 行,我的应用程序包含很多很多文本框.(B) 我所有的文本框都已经有一个 Style 属性,它指向一个全局 StaticResource,它设置 Margin、Height 和其他东西.(C) 您可能已经注意到下面的代码设置了两次绑定路径,这违反了 DRY 原则.

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.

<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...

违反了目标 2.首先,单击显示13.50"的文本框.突然让它显示13.5000",这是出乎意料的.其次,如果从空白文本框开始,并且我输入13.50",则文本框将包含1350".我无法解释为什么,但如果光标位于文本框中字符串的右端,则按句点键不会插入小数.如果 TextBox 包含长度 > 的字符串;0,然后我将光标重新定位到字符串右端以外的任何位置,然后我可以插入小数点.

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.

尝试 6:自己动手.

我即将踏上一段痛苦的旅程,要么继承 TextBox,要么创建一个附加属性,然后自己编写格式化代码.它会充满字符串操作,并导致大量脱发.

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.

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

Does anyone have any suggestions for formatting decimals bound to textboxes that satisfies all the above goals?

推荐答案

尝试在 ViewModel 级别解决该问题.那它:

Try to resolve that on ViewModel level. That it:

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

这篇关于如何在不激怒用户的情况下格式化绑定到 TextBox 的小数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆