Search code examples
wpfxamldata-bindingstring-formatting

show decimal places of number in WPF according to user selection


I want to display a number of type double in a WPF window. The user should be enabled to select how many decimal places should be displayed.

I want to solve this in the view (XAML) directly without formatting the number in code behind. I try to display the bound number with selected amount of decimal places using StringFormat and MultiBinding:

MainWindow.xaml:

<Window x:Class="WpfApp1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    DataContext="{Binding RelativeSource={RelativeSource Self}}"
    Width="400"  Height="100">

<StackPanel Orientation="Horizontal" Height="30">

    <TextBlock  Margin="5" >Precision:</TextBlock>
    
    <ComboBox x:Name="cbPrecision" 
                Margin="5" 
                MinWidth="80"
                ItemsSource="{Binding Path=DecimalPlaces}" 
                DisplayMemberPath="Value" 
                SelectedValuePath="Key"/>

    <TextBlock Margin="5" >
        <TextBlock.Text>
            <MultiBinding StringFormat="Number: {0:N{1}}">
                <Binding Path="SomeNumber"/>
                <Binding Path="SelectedValue" ElementName="cbPrecision"/>
            </MultiBinding>
        </TextBlock.Text>
    </TextBlock>

</StackPanel>

MainWindow.xaml.cs:

using System.Collections.Generic;
using System.Windows;

namespace WpfApp1
{
    public partial class MainWindow : Window
    {

        public MainWindow()
        {
            InitializeComponent();
        }

        public Dictionary<int, double> DecimalPlaces { get; } = new() {
            { 0, 1 },
            { 1, 0.1 },
            { 2, 0.01 },
            { 3, 0.001 }
        };

        public double SomeNumber { get; set; } = 123.45678;
    }
}

As result the TextBox is not displayed at all:

enter image description here

Expected result:

enter image description here


Observation:

There must be some problem with the "nested" string formatting

<MultiBinding StringFormat="Number: {0:N{1}}">

because changing the code line containing the StringFormat to

<MultiBinding StringFormat="Number: {0:N4}-DecimalPlaces: {1}">

displays the values in the TextBox correctly:

enter image description here


Solution

  • Following Clemens comment I created a converter. Complete solution:

    MainWindow.xaml:

    <Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        xmlns:local="clr-namespace:WpfApp1"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        Width="400"  Height="100">
    
    <StackPanel Orientation="Horizontal" Height="30">
    
        <TextBlock  Margin="5" >Precision:</TextBlock>
        
        <ComboBox x:Name="cbPrecision" 
                    Margin="5" 
                    MinWidth="80"
                    ItemsSource="{Binding Path=DecimalPlaces}" 
                    DisplayMemberPath="Value" 
                    SelectedValuePath="Key"/>
    
        <TextBlock Margin="5" >
            <TextBlock.Text>
                <MultiBinding>
                    <MultiBinding.Converter>
                        <local:DecimalPlacesConverter />
                    </MultiBinding.Converter>
                    <Binding Path="SomeNumber"/>
                    <Binding Path="SelectedValue" ElementName="cbPrecision"/>
                </MultiBinding>
            </TextBlock.Text>
        </TextBlock> 
    </StackPanel>
    

    MainWindow.xaml.cs:

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
        public Dictionary<int, double> DecimalPlaces { get; } = new() {
            { 0, 1 },
            { 1, 0.1 },
            { 2, 0.01 },
            { 3, 0.001 }
        };
        public double SomeNumber { get; set; } = 123.45678;
    }
    

    DecimalPlacesConverter.cs:

    public class DecimalPlacesConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            try
            {
                return Math.Round((double)values[0], (int)values[1]).ToString("N" + values[1]);
            }
            catch (Exception)
            {
                return double.NaN;
            }
        }
    
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }