Wpf小数风格

时间:2017-05-06 20:15:06

标签: wpf

我想设置带有小数位的TextBox,如下所示:

enter image description here

我该怎么做?

3 个答案:

答案 0 :(得分:3)

您可以像这样扩展TextBox

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

XAML代码用法

<local:DecimalTextBox FloatColor="Maroon" />

您的输出应如下所示:

screenshot

更新05/17

说明:正如您从图像中看到的那样,DecimalTextBox仅在未聚焦时才以格式化模式显示文本。

我最初开发了控件以支持编辑期间的格式化(仍然可以通过注释方法OnLostKeyboardFocusOnGotKeyboardFocus来完成) - 但由于字体大小不同,光标定位是稍微倾斜,这反过来会转化为糟糕的用户体验。

cursor issue

因此,在GotFocusLostFocus期间实施了交换逻辑以解决此问题。

答案 1 :(得分:0)

使用TextBox无法做到这一点,因为TextBox只接受对整个文本的颜色更改。您应该尝试使用RichTextBox,它允许循环通过TextRange。查看this使用RichTextBox突出显示语法的示例。

我实际上理解它是如何工作的并且做到了:

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;

        }

将文本框TextChanged事件保存到此方法。您现在可以像这样设置文本的每个部分所需的样式:

更改最后一部分(点字符后面)endRange.ApplyPropertyValue(TextElement.ForegroundProperty, new SolidColorBrush(Colors.Red));

更改第一部分(在点字符之前)与initRange变量相同。如果要更改“点”样式,请使用dotStartdotEnd TextPointers创建一个新的TextRange,并为其应用样式。您可以执行其他操作,例如更改字体样式,大小等。

此代码结果如下所示: enter image description here

这一切只是为了风格。对于检查,数字取决于您。

答案 2 :(得分:0)

我将使用以下主要属性定义自定义控件:

  • FloatNumber:原始号码,应该是DependencyProperty与控件绑定。
  • NumberOfDecimalDigits:一个数字,用于选择要表示的小数位数,应该是从控件绑定的DependencyProperty
  • FirstPart:一个包含十进制数字第一部分的字符串
  • Decimals:包含FloatNumber
  • 的十进制数字的字符串

当然这只是一个划痕,可以更好地实现这些属性来提取FloatNumber部分。

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

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

然后,您可以在页面中使用它并绑定属性以使其动态更改:

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

最终结果:

The final result