Styling decimal places
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:
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.
Hence, the paging logic is implemented during GotFocus
and LostFocus
to fix this.
source to share
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.
All this is just for style. To verify that the number is up to you.
source to share
I would define a custom control using these basic properties:
-
FloatNumber
: the initial number that should be inDependencyProperty
order to bind to the control. -
NumberOfDecimalDigits
: a number to select the number of decimal digitsDependencyProperty
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 digitsFloatNumber
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:
source to share