How can I format a decimal bound to TextBox without angering my users?

前端 未结 3 1024
陌清茗
陌清茗 2020-12-23 19:20

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

Goals

Goal 1: When setting a decimal property in code, display 2 decimal p

相关标签:
3条回答
  • 2020-12-23 19:51

    Try the WPF Extended Tookit Masked TextBox to implement an Input Mask: http://wpftoolkit.codeplex.com/wikipage?title=MaskedTextBox

    Example:

    <toolkit:MaskedTextBox Mask="(000) 000-0000" Value="(555) 123-4567" 
    IncludeLiterals="True" />
    
    0 讨论(0)
  • 2020-12-23 20:04

    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:

    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.

    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>
    
    0 讨论(0)
  • 2020-12-23 20:06

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

    SAMPLE

    Xaml:

    <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;
    }
    
    0 讨论(0)
提交回复
热议问题