wpf textbox decimal string-formatting

wpf - ¿Cómo puedo formatear un decimal vinculado a TextBox sin enojar a mis usuarios?



string-formatting (3)

Estoy tratando de mostrar un decimal formateado en un TextBox utilizando el enlace de datos en WPF.

Metas

Objetivo 1: cuando se establece una propiedad decimal en el código, se muestran 2 lugares decimales en el cuadro de texto.

Objetivo 2: cuando un usuario interactúa con (escribe en) el TextBox, no lo enoje.

Objetivo 3: los enlaces deben actualizar la fuente en PropertyChanged.

Intenta

Intento 1: sin formato.

Aquí comenzamos casi desde cero.

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

Viola Meta 1. SomeDecimal = 4.5 mostrará "4.50000" en el TextBox.

Intento 2: utilizar StringFormat en el enlace.

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

Viola el Objetivo 2. Decir SomeDecimal es 2.5 y el TextBox muestra "2.50". Si seleccionamos todo y escribimos "13.5", terminamos con "13.5.00" en el TextBox porque el formateador "amablemente" inserta decimales y ceros.

Intento 3: use MaskedTextBox de Extended WPF Toolkit.

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

Suponiendo que estoy leyendo la documentación correctamente, la máscara ## 0.00 significa "dos dígitos opcionales, seguidos por un dígito requerido, un punto decimal y dos dígitos adicionales requeridos. Esto me obliga a decir" el mayor número posible que puede entrar en este TextBox es 999.99 "pero digamos que estoy de acuerdo con eso.

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

Viola el Objetivo 2. El TextBox comienza con ___.__ y al seleccionarlo y al escribir 5.75 obtiene 575.__ . La única forma de obtener 5.75 es seleccionar el cuadro de texto y escribir <space><space>5.75 .

Intento 4: utilizar el control de giro de DecimalUpDown extendido de WPF Toolkit.

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

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

Viola la Meta 3. DecimalUpDown felizmente ignora UpdateSourceTrigger = PropertyChanged. Uno de los coordinadores en la página ampliada de WPF Toolkit Codeplex sugiere una versión modificada de ControlTemplate en http://wpftoolkit.codeplex.com/discussions/352551/ . Esto satisface el Objetivo 3, pero infringe el Objetivo 2 y muestra el mismo comportamiento que en el Intento 2.

Intento 5: Usando activadores de datos de estilo, solo use el formato si el usuario no está editando.

Enlace a un doble con StringFormat en un TextBox

Incluso si este cumplió los tres objetivos, no me gustaría usarlo. (A) Los cuadros de texto se convierten en 12 líneas cada uno en lugar de 1, y mi aplicación contiene muchos, muchos cuadros de texto. (B) Todos mis cuadros de texto ya tienen un atributo de Estilo que apunta a un StaticResource global que establece Margen, Altura y otras cosas. (C) Es posible que haya notado que el código a continuación establece la Ruta de enlace dos veces, lo que infringe el principio 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>

Todas estas cosas incómodas a un lado ...

Viola el objetivo 2. Primero, al hacer clic en un cuadro de texto que muestra "13.50" de repente aparece "13.5000", lo cual es inesperado. En segundo lugar, si empiezo con un TextBox en blanco y escribo "13.50", TextBox contendrá "1350". No puedo explicar por qué, pero presionar la tecla de período no inserta decimales si el cursor está en el extremo derecho de la cadena en el cuadro de texto. Si TextBox contiene una cadena con una longitud> 0, y yo cambio el cursor a cualquier lugar, excepto el extremo derecho de la cadena, puedo insertar puntos decimales.

Intento 6: Hágalo yo mismo.

Estoy a punto de embarcar en una aventura de dolor y sufrimiento, ya sea subclasificando TextBox, o creando una propiedad adjunta, y escribiendo el código de formateo yo mismo. Estará lleno de manipulación de cuerdas y causará una pérdida de cabello considerable.

¿Alguien tiene alguna sugerencia para formatear decimales vinculados a cuadros de texto que satisfagan todos los objetivos anteriores?


Intenta resolverlo en el nivel de ViewModel. Que es eso

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

MUESTRA

Xaml:

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

Código detrás:

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



StringFormat={}{0:0.00} el siguiente comportamiento personalizado para mover el cursor de los usuarios después del punto decimal al usar StringFormat={}{0:0.00} , lo que obliga a que haya un lugar decimal, sin embargo, esto puede causar el siguiente problema:

Viola el Objetivo 2. Decir SomeDecimal es 2.5 y el TextBox muestra "2.50". Si seleccionamos todo y escribimos "13.5", terminamos con "13.5.00" en el TextBox porque el formateador "amablemente" inserta decimales y ceros.

Lo he pirateado utilizando un comportamiento personalizado que moverá el cursor de los usuarios después de la posición decimal cuando presionen. llave:

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

Lo uso de la siguiente manera:

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