Styling decimal places

I want to style a TextBox with decimal places like this:

enter image description here

How can i do this?

+3


source to share


3 answers


You can expand TextBox

as follows.

using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;

public class DecimalTextBox : TextBox
{
    public static readonly DependencyProperty FloatColorProperty = DependencyProperty.Register("FloatColor", typeof(Color), typeof(DecimalTextBox), new FrameworkPropertyMetadata(Colors.Red));
    public Color FloatColor
    {
        get { return (Color)GetValue(FloatColorProperty); }
        set { SetValue(FloatColorProperty, value); }
    }

    protected TextBlock _textBlock;
    protected FrameworkElement _textBoxView;

    public DecimalTextBox()
    {
        _textBlock = new TextBlock() { Margin = new Thickness(1, 0, 0, 0) };
        Loaded += ExTextBox_Loaded;
    }

    private void ExTextBox_Loaded(object sender, RoutedEventArgs e)
    {
        Loaded -= ExTextBox_Loaded;

        // hide the original drawing visuals, by setting opacity on their parent
        var visual = this.GetChildOfType<DrawingVisual>();
        _textBoxView = (FrameworkElement)visual.Parent;
        _textBoxView.Opacity = 0;

        // add textblock to do the text drawing for us
        var grid = this.GetChildOfType<Grid>();
        if (grid.Children.Count >= 2)
            grid.Children.Insert(1, _textBlock);
        else
            grid.Children.Add(_textBlock); 
    }

    protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
    {
        base.OnLostKeyboardFocus(e);

        _textBoxView.Opacity = 0;
        _textBlock.Visibility = Visibility.Visible;
    }

    protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
    {
        base.OnGotKeyboardFocus(e);

        _textBoxView.Opacity = 1;
        _textBlock.Visibility = Visibility.Collapsed;
    }

    protected override void OnTextChanged(TextChangedEventArgs e)
    {
        base.OnTextChanged(e);

        // making sure text on TextBlock is updated as per TextBox
        var dotPos = Text.IndexOf('.');
        var textPart1 = dotPos == -1 ? Text : Text.Substring(0, dotPos + 1);
        var textPart2 = (dotPos == -1 || dotPos >= (Text.Length-1)) ? null : Text.Substring(dotPos + 1);

        _textBlock.Inlines.Clear();
        _textBlock.Inlines.Add(new Run {
            Text = textPart1,
            FontFamily = FontFamily,
            FontSize = FontSize,
            Foreground = Foreground });

        if (textPart2 != null)
            _textBlock.Inlines.Add(new Run {
                Text = textPart2,
                FontFamily = FontFamily,
                TextDecorations = System.Windows.TextDecorations.Underline,
                BaselineAlignment = BaselineAlignment.TextTop,
                FontSize = FontSize * 5/6,
                Foreground = new SolidColorBrush(FloatColor) });
    }
}

public static class HelperExtensions
{
    public static T GetChildOfType<T>(this DependencyObject depObj) where T : DependencyObject
    {
        if (depObj == null) return null;

        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
        {
            var child = VisualTreeHelper.GetChild(depObj, i);

            var result = (child as T) ?? GetChildOfType<T>(child);
            if (result != null) return result;
        }
        return null;
    }
}

      

Using XAML Code

<local:DecimalTextBox FloatColor="Maroon" />

      

And your output should look like this:

screenshot



Update 05/17

Explanation: As you can see from the image, DecimalTextBox

displays text in formatted mode only when it is not focused.

I originally designed a control to support formatting during editing (which else can be done by commenting out the OnLostKeyboardFocus

and methods OnGotKeyboardFocus

), but due to the difference in font sizes, the cursor position is slightly distorted, which in turn will translate into a poor user experience.

cursor problem

Hence, the paging logic is implemented during GotFocus

and LostFocus

to fix this.

+3


source


You cannot do this with a TextBox, because the TextBox only accepts color changes across the entire text. You should try with RichTextBox, which allows loop throug TextRange. Check out this sample syntax highlighting with RichTextBox.

I actually figured out how it works and did this:

private void richTextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            richTextBox.TextChanged -= this.richTextBox_TextChanged;

            if (richTextBox.Document == null)
                return;

            TextRange documentRange = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd);
            documentRange.ClearAllProperties();

            int dotIndex = documentRange.Text.IndexOf(".");
            if(dotIndex == -1)
            {
                richTextBox.TextChanged += this.richTextBox_TextChanged;
                return;
            }
            TextPointer dotStart = GetPoint(richTextBox.Document.ContentStart, dotIndex);
            TextPointer dotEnd = dotStart.GetPositionAtOffset(1, LogicalDirection.Forward);

            TextRange initRange = new TextRange(richTextBox.Document.ContentStart, dotStart);

            TextRange endRange = new TextRange(dotEnd, richTextBox.Document.ContentEnd);

            endRange.ApplyPropertyValue(TextElement.ForegroundProperty, new SolidColorBrush(Colors.Red));

            richTextBox.TextChanged += this.richTextBox_TextChanged;

        }

      

Sign the TextChanged textbox to this method. Now you can style each piece of text like this:



To change the last part (after the char point) endRange.ApplyPropertyValue(TextElement.ForegroundProperty, new SolidColorBrush(Colors.Red));

To change the first part (up to the char point) would be the same but for the initRange variable. If you want to change the point style, create a new TextRange using dotStart

both dotEnd

TextPointers and apply styles to it. You can do other things like changing font style, size, etc.

This output looks like this: enter image description here

All this is just for style. To verify that the number is up to you.

0


source


I would define a custom control using these basic properties:

  • FloatNumber

    : the initial number that should be in DependencyProperty

    order to bind to the control.
  • NumberOfDecimalDigits

    : a number to select the number of decimal digits DependencyProperty

    to bind to the control.
  • FirstPart

    : the string that will contain the first part of the decimal number
  • Decimals

    : a string that will contain decimal digits FloatNumber

Of course, this is just a scratch, these properties can be better realized for extracting FloatNumber

parts.

public partial class DecimalDisplayControl : UserControl, INotifyPropertyChanged
{
    public DecimalDisplayControl()
    {
        InitializeComponent();
        (Content as FrameworkElement).DataContext = this;
    }

    public static readonly DependencyProperty NumberOfDecimalDigitsProperty =
        DependencyProperty.Register(
            "NumberOfDecimalDigits", typeof(string),
            typeof(DecimalDisplayControl), new PropertyMetadata(default(string), OnFloatNumberChanged));

    public static readonly DependencyProperty FloatNumberProperty =
        DependencyProperty.Register(
            "FloatNumber", typeof(string),
            typeof(DecimalDisplayControl), new PropertyMetadata(default(string), OnFloatNumberChanged));

    private static void OnFloatNumberChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        (d as DecimalDisplayControl).OnFloatNumberChanged();
    }

    protected void OnFloatNumberChanged()
    {
        int numberOfDecimalDigits = Convert.ToInt32(NumberOfDecimalDigits);

        float fullNumber = Convert.ToSingle(FloatNumber);
        float firstPart = (float)Math.Truncate(fullNumber);
        float fullDecimalPart = fullNumber - firstPart;
        int desideredDecimalPart = (int)(fullDecimalPart * Math.Pow(10, numberOfDecimalDigits));

        FirstPart = $"{firstPart}.";
        Decimals = desideredDecimalPart.ToString();
    }

    public string FloatNumber
    {
        get => (string)GetValue(FloatNumberProperty);
        set { SetValue(FloatNumberProperty, value); }
    }

    public string NumberOfDecimalDigits
    {
        get => (string)GetValue(NumberOfDecimalDigitsProperty);
        set { SetValue(NumberOfDecimalDigitsProperty, value);  }
    }

    private string _firstPart;
    public string FirstPart
    {
        get => _firstPart;
        set
        {
            if (_firstPart == value)
                return;

            _firstPart = value;
            OnPropertyChanged();
        }
    }

    private string _decimals;
    public string Decimals
    {
        get => _decimals;
        set
        {
            if (_decimals == value)
                return;

            _decimals = value;
            OnPropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

      

Its XAML:

<UserControl
    x:Class="WpfApp1.CustomControls.DecimalDisplayControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <StackPanel
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            Orientation="Horizontal">

        <TextBlock
                FontSize="20"
                Foreground="Black"
                Text="{Binding FirstPart}" />
        <TextBlock
                FontSize="10"
                Foreground="Red"
                Text="{Binding Decimals}"
                TextDecorations="Underline" />

    </StackPanel>

</UserControl>

      

Then you can use it in your page and bind the property so that it changes dynamically:

<Grid>

    <StackPanel VerticalAlignment="Center" Orientation="Vertical">

        <customControls:DecimalDisplayControl
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            NumberOfDecimalDigits="2"
            FloatNumber="{Binding MyNumber}" />

        <TextBox
            Width="200"
            VerticalAlignment="Center"
            Text="{Binding MyNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

    </StackPanel>

</Grid>

      

Final result:

Final result

0


source







All Articles