Wpf小数样式化

3

我想要像这样为文本框设置小数位数:

enter image description here

我该怎么做?


你想要它可编辑吗? - 15ee8f99-57ff-4f92-890c-b56153
最好是的。 - Juan Pablo Gomez
你可以使用RichTextEdit来实现,但是这会非常麻烦(忘记绑定binding),并且格式会在用户编辑时被破坏。你可以在focusout或其他事件中重新整理它,但那会很丑陋。你有多需要这个特性呢?只读很简单:只需在TextBox中绑定两个Runs,使用转换器将十进制值的不同部分拼成字符串即可。 - 15ee8f99-57ff-4f92-890c-b56153
我曾尝试使用自定义控件和绑定,但那是一场噩梦。现在只需要在未编辑时有一个样式。 - Juan Pablo Gomez
必须使用TextBox吗?如果不是很匹配,那么花时间让TextBox做这件事情会很浪费。请参阅https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem 我认为可以通过编写包含两个文本框和大量键处理代码的ControlTemplate来完成此操作。 - Emond
@ErnodeWeerd,使用ControlTemplate可能是一个不错的方法。 - Juan Pablo Gomez
3个回答

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期间实现了交换逻辑以修复这个问题。


嗨,我正在尝试使用这段代码,但是using语句缺失,我在System.Drawing.Color和System.Windows.Media.Color之间存在歧义引用,请问你能提供正确的using语句吗? - Juan Pablo Gomez
这正是我正在寻找的东西。只有一个小问题,当它包含在DataGridTemplateColumn中时,当行被选中时,它不会采用默认的选定颜色。但这是另一个问题了。非常感谢你的帮助。 - Juan Pablo Gomez
1
这个很好用,应该接受这个答案。@SharadaGururaj的回答唯一缺少的是一些解释 - 他的控件有两种模式 - 一种用于编辑,另一种(在失去焦点时显示)用于显示格式化文本。 同样的效果可以通过切换模板来实现(一个用于聚焦控件,一个用于非聚焦控件)。 Juan Pablo Gomez - 如果可以的话 - 不要使用RichTextBox。它总是很慢且难以维护。 - Maciek Świszczowski
@swiszcz 我原以为附加的图片已经传达了这个意思 :) - 我会在我的回答中更新解释。谢谢。 - Sharada Gururaj

0

你无法使用 TextBox 实现此功能,因为 TextBox 只接受整个文本的颜色更改。你应该尝试使用 RichTextBox,它允许遍历 TextRange。请查看使用 RichTextBox 进行语法突出显示的 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;

        }

将文本框的TextChanged事件订阅到此方法。现在您可以像这样为文本的每个部分设置所需的样式:
要更改最后一部分(点字符后面),请使用以下代码:endRange.ApplyPropertyValue(TextElement.ForegroundProperty, new SolidColorBrush(Colors.Red));
要更改第一部分(点字符前面)也是一样,只需使用initRange变量即可。如果您想更改“点”的样式,则创建一个具有dotStart和dotEnd TextPointers的新TextRange,并对其应用样式。您还可以执行其他操作,例如更改字体样式、大小等。
此代码的结果如下所示: enter image description here 所有这些都只是为了样式。检查是否为数字由您决定。

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


不可以直接编辑绑定属性,您需要通过其他方式进行修改。 - Francesco Bonizzi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接