¿Cómo formatear el número de lugares decimales en wpf usando estilo / plantilla?

94

Estoy escribiendo un programa WPF y estoy tratando de encontrar una forma de formatear datos en un TextBox a través de algún método repetible como un estilo o plantilla. Tengo muchos TextBoxes (95 para ser exactos) y cada uno está vinculado a sus propios datos numéricos que pueden tener cada uno su propia resolución definida. Por ejemplo, si los datos son 99,123 con una resolución de 2, debería mostrar 99,12. De manera similar, un valor de datos de 99 y una resolución 3 deben mostrarse como 99.000 (no 99). ¿Hay alguna forma de hacer esto?

Editar: Debo aclarar, hay 95 TextBoxes en la pantalla actual en la que estoy trabajando, pero quiero que cada TextBox en las distintas pantallas de mi programa muestre el número correcto de lugares decimales. Ahora que lo pienso, algunos de estos son TextBoxes (como la pantalla en la que estoy trabajando ahora) y algunos son DataGrids o ListViews, pero si puedo averiguar cómo hacer que funcione para TextBoxes, estoy seguro de que puedo entender también para los otros controles.

No hay mucho código para compartir en este caso, pero intentaré aclararlo:

Tengo un modelo de vista que contiene las siguientes propiedades (vb.net):

    Public ReadOnly Property Resolution As Integer
        Get
            Return _signal.DisplayResolution
        End Get
    End Property

    Public ReadOnly Property Value As Single
        Get
            Return Math.Round(_signal.DisplayValue, Resolution)
        End Get
    End Property

y en el XAML tengo:

<UserControl.Resources>
    <vm:SignalViewModel x:Key="Signal" SignalPath="SomeSignal"/>
</UserControl.Resources>
<TextBox Grid.Column="3" IsEnabled="False" Text="{Binding Path=Value, Source={StaticResource Signal}, Mode=OneWay}" />

EDIT2 (mi solución): Resulta que después de alejarme de la computadora por un tiempo, regresé para encontrar una respuesta simple que me estaba mirando a la cara. ¡Formatee los datos en el modelo de vista!

    Public ReadOnly Property Value As String
        Get
            Return (Strings.FormatNumber(Math.Round(_signal.DisplayValue, _signal.DisplayResolution), _signal.DisplayResolution))
        End Get
    End Property
AXG1010
fuente
1
utilizar un IValueConverter? Pase el valor real y la resolución al convertidor y déjelo redondear por usted mismo. Es difícil sugerir un StringFormatsin saber exactamente cómo TextBoxse generan estos 95 .
Viv
Publique el código actual y XAML. De lo contrario, todo son especulaciones y conjeturas inútiles.
Federico Berasategui
Agregué más información a la pregunta que, con suerte, debería aclararla.
AXG1010

Respuestas:

197

Debería utilizar StringFormaten el Binding. Puede utilizar formatos de cadena estándar o formatos de cadena personalizados :

<TextBox Text="{Binding Value, StringFormat=N2}" />
<TextBox Text="{Binding Value, StringFormat={}{0:#,#.00}}" />

Tenga en cuenta que StringFormatsolo funciona cuando la propiedad de destino es de tipo cadena. Si está intentando establecer algo como una Contentpropiedad ( typeof(object)), necesitará usar un archivo personalizado StringFormatConverter( como aquí ) y pasar su cadena de formato como ConverterParameter.

Editar para pregunta actualizada

Entonces, si tu ViewModeldefine la precisión, te recomiendo hacer esto como MultiBindingy crear el tuyo propio IMultiValueConverter. Esto es bastante molesto en la práctica, pasar de un enlace simple a uno que necesita expandirse a un MultiBinding, pero si no se conoce la precisión en el momento de la compilación, esto es prácticamente todo lo que puede hacer. Debería IMultiValueConvertertomar el valor y la precisión y generar la cadena formateada. Podrías hacer esto usando String.Format.

Sin embargo, para cosas como a ContentControl, puede hacer esto mucho más fácilmente con a Style:

<Style TargetType="{x:Type ContentControl}">
    <Setter Property="ContentStringFormat" 
            Value="{Binding Resolution, StringFormat=N{0}}" />
</Style>

Cualquier control que exponga un ContentStringFormatse puede usar así. Desafortunadamente, TextBoxno tiene nada de eso.

Abe Heidebrecht
fuente
6
El ejemplo con StringFormat establecido en #,#.00no se compila: la coma se interpreta como un delimitador para los atributos en la Bindingextensión de marcado.
Gigi
@Gigi, tienes razón, pero se puede utilizar fácilmente este modo: StringFormat={}{0:#,#.00}. Actualizaré la respuesta para que funcione correctamente.
Abe Heidebrecht
El 'StringFormat = N {0}' funciona muy bien. Para una precisión de 2, me gustaría que se mostraran dos decimales, excepto '10 .00 ', en cuyo caso quiero que se muestre' 10 '. ¿Hay alguna forma de hacer esto cuando se vincula a la precisión? Parece que tendré que usar un convertidor.
Gordon Slysz
No creo que haya una manera de cambiar los decimales presentados usando formatos de cadena .NET, por lo que probablemente sea mejor que escriba un convertidor.
Abe Heidebrecht
¿Puede explicar por qué usar dos especificadores 0: #, #. 00, no será suficiente solo uno de ellos?
Lei Yang
7

La respuesta aceptada no muestra 0 en lugar entero al dar una entrada como 0.299. Muestra .3 en la interfaz de usuario de WPF. Entonces mi sugerencia de usar el siguiente formato de cadena

<TextBox Text="{Binding Value,  StringFormat={}{0:#,0.0}}" 
Manish Dubey
fuente
Hola, tu solución está bien, pero prefiero usar la palabra clave N1 (2,3 ...) porque evita errores tipográficos y al menos estás seguro de cómo se mostrará. Pero, de hecho, la segunda sugerencia no muestra el 0 si el valor <0 como mencionas.
Kevin VDF
-2
    void NumericTextBoxInput(object sender, TextCompositionEventArgs e)
    {
        TextBox txt = (TextBox)sender;
        var regex = new Regex(@"^[0-9]*(?:\.[0-9]{0,1})?$");
        string str = txt.Text + e.Text.ToString();
        int cntPrc = 0;
        if (str.Contains('.'))
        {
            string[] tokens = str.Split('.');
            if (tokens.Count() > 0)
            {
                string result = tokens[1];
                char[] prc = result.ToCharArray();
                cntPrc = prc.Count();
            }
        }
        if (regex.IsMatch(e.Text) && !(e.Text == "." && ((TextBox)sender).Text.Contains(e.Text)) && (cntPrc < 3))
        {
            e.Handled = false;
        }
        else
        {
            e.Handled = true;
        }
    }
Monali Pawar
fuente
9
Algunas explicaciones mejorarían significativamente la calidad de su respuesta.
lunes