Comment puis-je formater une décimale liée à TextBox sans mettre mes utilisateurs en colère?

j'essaie d'afficher une décimale formatée dans une boîte de texte en utilisant la liaison de données dans WPF.

Objectifs

Objectif 1: lors de la définition d'une propriété décimale dans le code, afficher 2 décimales dans la zone de texte.

Objectif 2: Lorsqu'un utilisateur interagit avec (tape) la boîte de texte, ne l'énervez pas.

Objectif 3: les reliures doivent mettre à jour la source sur PropertyChanged.

tentatives

tentative 1: Pas de formatage.

ici, nous partons presque de zéro.

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

Viole L'Objectif 1. SomeDecimal = 4.5 affichera" 4.50000 " dans la zone de texte.

" tentative 2: Utiliser StringFormat dans la reliure.

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

Viole L'Objectif 2. Disons que quelque chose est égal à 2,5, et le TextBox affiche "2.50". Si nous sélectionnons tout et tapons "13.5" nous finissons avec "13.5.00" dans la zone de texte parce que le formateur "helpfully" insère des décimales et des zéros.

tentative 3: utiliser la boîte MaskedTextBox de WPF étendue.

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

si je lis la documentation correctement, le masque ##0,00 signifie "deux facultatif chiffres, suivi d'un nécessaire chiffres, un point décimal, et deux autres chiffres. Cela me force à dire "le plus grand nombre possible qui peut aller dans cette boîte de texte est 999.99" mais disons que je suis d'accord avec cela.

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

Viole L'Objectif 2. La zone de texte commence par ___.__ et le sélectionne et en tapant 5.75 donne 575.__ . La seule façon d'obtenir 5.75 est de sélectionner la boîte de texte et de taper <space><space>5.75 .

" tentative 4: Utilisez le spinner DecimalUpDown de WPF Toolkit étendu.

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

<xctk:DecimalUpDown Value="{Binding Path=SomeDecimal, UpdateSourceTrigger=PropertyChanged}" FormatString="F2" />

Viole L'Objectif 3. DecimalUpDown ignore heureusement UpdateSourceTrigger = PropertyChanged. Un des coordinateurs de la page Codeplex de la boîte à outils étendue du FPF suggère un template de contrôle modifié à http://wpftoolkit.codeplex.com/discussions/352551 / . Cela satisfait le but 3, mais viole le but 2, montrant le même comportement que dans la tentative 2.

tentative 5: en utilisant des datatriggers de style, n'utilisez le formatage que si l'utilisateur n'est pas en train d'éditer.

fixation à un double avec StringFormat sur une boîte de texte

même si celui-ci satisfait les trois objectifs, je ne voudrais pas l'utiliser. (A) Textboxes deviennent 12 lignes chacun au lieu de 1, et mon application contient beaucoup, beaucoup de textboxes. (B) Toutes mes boîtes de texte ont déjà un attribut de Style qui pointe vers une source statique globale qui fixe la marge, la hauteur, et d'autres choses. (C) Vous avez peut-être remarqué que le code ci-dessous définit le chemin de liaison deux fois, ce qui viole le 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>

toutes ces choses inconfortables mis à part...

viole Objectif 2. Tout d'abord, cliquer sur une boîte de texte qui affiche "13.50" le fait soudainement afficher "13.5000", ce qui est inattendu. Deuxièmement, si vous commencez avec une zone de texte vide, et que je tape "13.50", la zone de texte contiendra "1350". Je ne peux pas expliquer pourquoi, mais appuyer sur la touche period n'insère pas de décimales si le curseur est à l'extrémité droite de la chaîne dans la zone de texte. Si la boîte de texte contient une chaîne de caractères d'une longueur > 0, et que je repositionne le curseur n'importe où sauf à l'extrémité droite de la chaîne, je peux alors insérer virgule.

tentative 6: Do it myself.

je suis sur le point de m'embarquer dans un jouney de douleur et de souffrance, soit en sous-titrant TextBox, soit en créant une propriété jointe, et en écrivant moi-même le code de formatage. Il sera plein de manipulation de ficelle, et causer la perte de cheveux substantielle.


est-ce que quelqu'un a des suggestions pour formater les décimales? lié à des boîtes de texte qui satisfait tous les objectifs ci-dessus?

52
demandé sur Community 2012-07-14 00:21:52

3 réponses

essayez de résoudre cela au niveau de ViewModel. Qu'il:

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

échantillon

Xaml:

<TextBox x:Name="tb" Text="{Binding Path=SomeDecimalAsString, UpdateSourceTrigger=PropertyChanged}" />

Code derrière:

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;
}
5
répondu Vladimir Dorokhov 2012-07-13 21:17:19

j'ai créé le comportement personnalisé suivant pour déplacer le curseur de l'utilisateur après le point décimal en utilisant StringFormat={}{0:0.00} , ce qui force une place décimale pour être présent, mais cela peut causer la question suivante:

Viole L'Objectif 2. Disons que SomeDecimal est 2.5, et la boîte de texte affiche "2.50". Si nous sélectionnons tout et tapons "13.5" nous finissons avec "13.5.00" dans la zone de texte parce que le formateur "helpfully" insère des décimales et des zéros.

j'ai piraté ceci en utilisant un comportement personnalisé qui déplacera le curseur de l'utilisateur à la place après la décimale quand ils pressent le . légende:

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

je l'utilise comme suit:

<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
répondu Alex Hope O'Connor 2016-02-05 11:26:11

essayez la WPF Extended Tookit Masked TextBox pour implémenter un masque de saisie: http://wpftoolkit.codeplex.com/wikipage?title=MaskedTextBox

exemple:

<toolkit:MaskedTextBox Mask="(000) 000-0000" Value="(555) 123-4567" 
IncludeLiterals="True" />
1
répondu Xcalibur37 2012-07-13 20:57:58