wpf 在具有虚缐底缐TextDecoration的TextBlock中显示DateTime

myzjeezk  于 2022-11-18  发布在  其他
关注(0)|答案(4)|浏览(111)

我试图在TextBlock中显示DateTime2019-10-07 17:00。文本应该加下划线和虚线。为此,我使用了以下xaml

<TextBlock Text="2019-10-07 17:00">
    <TextBlock.TextDecorations>
        <TextDecoration Location="Underline">
            <TextDecoration.Pen>
                <Pen Brush="Black">
                    <Pen.DashStyle>
                        <DashStyle Dashes="5"/>
                    </Pen.DashStyle>
                </Pen>
            </TextDecoration.Pen>
        </TextDecoration>
    </TextBlock.TextDecorations>
</TextBlock>

但是,这会产生一些非常意外的结果,似乎每个连字符都会导致虚线下划线重新开始呈现。请注意,在每个连字符之后,虚线图案看起来几乎是随机的。

如果我将“减号连字符”更改为“不间断连字符”,看起来非常相似(- vs ‐),则渲染效果如预期。

<TextBlock Text="2019‐10‐07 17:00" ...>

每当我在文本中添加减号-连字符时,虚线下划线的错误呈现就会发生,但在我能找到的任何其他字符中却不会发生。是否有人注意到了这一点?是否有人有解决方案?如果没有,这种奇怪行为的原因可能是什么?

8yoxcaq7

8yoxcaq71#

根据您的格式,大小应该始终大致相同,这样您就可以使用另一个文本块,并让它覆盖另一个框

<TextBlock Text="This is a really lon" Foreground="Transparent" IsHitTestVisible="False">
    <TextBlock.TextDecorations>
        <TextDecoration Location="Underline">
            <TextDecoration.Pen>
                <Pen Brush="Black">
                    <Pen.DashStyle>
                        <DashStyle Dashes="5"/>
                    </Pen.DashStyle>
                </Pen>
            </TextDecoration.Pen>
        </TextDecoration>
    </TextBlock.TextDecorations>
</TextBlock>
<TextBlock Text="2019-10-07 17:00" />
q3aa0525

q3aa05252#

这可能是在WPF字形呈现代码中发现的一些奇怪的破折号破解的结果。在.NET源代码中,您将找到AdjustAdvanceForDisplayLayout()方法及其注解:

// AdvanceHeight is used to compute the bounding box. In some case, eg. the dash
// character '-', the bounding box is computed to be empty in Display
// TextFormattingMode (because the metrics are rounded to be pixel aligned) and so the
// dash is not rendered!

TextBlock上设置TextOptions.TextFormattingMode="Display"将产生稍微不同的伪像:

这告诉我们,我们确实找到了这个“变通办法”(参见GlyphRun.cs第1326行)。
因此,问题是我们是否能以某种方式得到第三个变体,而不需要任何这些工件。到目前为止,我还没有成功,但我确实试图找到发生这种连字符检查的地方。它似乎发生在本机代码中。请参阅TextFormatterContext.cs和LoCreateContext。

ny6fqffe

ny6fqffe3#

我不知道为什么会出现这种奇怪的行为。看起来像是Pen创建的破折号Map到了TextDecoration的修饰文本。这是有道理的,因为破折号或TextDecoration通常会自动调整为例如字体大小。减号字符似乎会产生不同的间距。也许这种行为不会在使用等宽字体时发生。
无论如何,您可以创建一个平铺的DrawingBrush,并将其赋给Pen.Brush属性以创建虚线。您可以使用DrawingBrush.ViewPort来更改虚线的位置或长度。
Viewport由四个值组成,实际上是一个Rect,用于描述图块的位置和尺寸:x, y, width, height。宽度和高度值越大,虚线越长。
结果是一个由虚线和空格组成的均匀绘图:

<TextBlock Text="2019-10-07 17:00">
  <TextBlock.TextDecorations>
    <TextDecoration Location="Underline">
      <TextDecoration.Pen>
        <Pen>
          <Pen.Brush>
            <DrawingBrush Viewport="0,0,10,10"
                          ViewportUnits="Absolute"
                          TileMode="Tile">
              <DrawingBrush.Drawing>
                <GeometryDrawing Brush="Black">
                  <GeometryDrawing.Geometry>
                    <GeometryGroup>
                      <RectangleGeometry Rect="0,0,5,5" />
                      <RectangleGeometry Rect="5,5,5,5" />
                    </GeometryGroup>
                  </GeometryDrawing.Geometry>
                </GeometryDrawing>
              </DrawingBrush.Drawing>
            </DrawingBrush>
          </Pen.Brush>
        </Pen>
      </TextDecoration.Pen>
    </TextDecoration>
  </TextBlock.TextDecorations>
</TextBlock>

这种方法的缺点是虚线的大小和位置不再适应字体的大小。

4jb9z9bj

4jb9z9bj4#

最后,我们构建了一个名为DashTextBlock的自定义控件来解决这个问题。它从TextBox派生而来,样式类似于TextBlock加上TextDecorationTextDecoration使用Pen加上x1m5 n1 a,LinearGradientBrush是根据“dash-properties”指定的内容和DashThickness的粗细设置的。
为了实现这一点,它使用TextBox方法GetRectFromCharacterIndex来确定如何设置LinearGradientBrush
TextBox.GetRectFromCharacterIndex Method
返回指定索引处字符边缘的矩形。
它会产生这样的结果

样品使用

<StackPanel>
        <controls:DashTextBlock Text="Testing DashTextBlock"
                                DashThickness="1"
                                DashColor="Blue">
            <controls:DashTextBlock.DashStyle>
                <DashStyle Dashes="4,4,4,4" Offset="0" />
            </controls:DashTextBlock.DashStyle>
        </controls:DashTextBlock>

        <controls:DashTextBlock Text="Testing DashTextBlock"
                                Margin="0 5 0 0"
                                DashThickness="2"
                                DashColor="Orange">
            <controls:DashTextBlock.DashStyle>
                <DashStyle Dashes="8 4 8 4" Offset="0" />
            </controls:DashTextBlock.DashStyle>
        </controls:DashTextBlock>
    </StackPanel>

** Jmeter 板文本块**

public class DashTextBlock : TextBox
    {
        public static readonly DependencyProperty DashColorProperty =
            DependencyProperty.Register("DashColor",
                                        typeof(Color),
                                        typeof(DashTextBlock),
                                        new FrameworkPropertyMetadata(Colors.Black, OnDashColorChanged));

        public static readonly DependencyProperty DashThicknessProperty =
            DependencyProperty.Register("DashThickness",
                                        typeof(double),
                                        typeof(DashTextBlock),
                                        new FrameworkPropertyMetadata(1.0, OnDashThicknessChanged));

        public static readonly DependencyProperty DashStyleProperty =
            DependencyProperty.Register("DashStyle",
                                        typeof(DashStyle),
                                        typeof(DashTextBlock),
                                        new FrameworkPropertyMetadata(DashStyles.Solid, OnDashStyleChanged));

        private static readonly DependencyProperty FontSizeCallbackProperty =
            DependencyProperty.Register("FontSizeCallback",
                                        typeof(double),
                                        typeof(DashTextBlock),
                                        new FrameworkPropertyMetadata(0.0, OnFontSizeCallbackChanged));

        public static readonly DependencyProperty TextLengthProperty =
            DependencyProperty.Register("TextLength",
                                        typeof(double),
                                        typeof(DashTextBlock),
                                        new FrameworkPropertyMetadata(0.0));

        public static readonly DependencyProperty DashEnabledProperty =
            DependencyProperty.Register("DashEnabled",
                                        typeof(bool),
                                        typeof(DashTextBlock),
                                        new FrameworkPropertyMetadata(true, OnDashEnabledChanged));

        private static void OnDashColorChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
        {
            DashTextBlock dashTextBlock = source as DashTextBlock;
            dashTextBlock.DashColorChanged();
        }

        private static void OnDashThicknessChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
        {
            DashTextBlock dashTextBlock = source as DashTextBlock;
            dashTextBlock.DashThicknessChanged();
        }

        private static void OnDashStyleChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
        {
            DashTextBlock dashTextBlock = source as DashTextBlock;
            dashTextBlock.DashStyleChanged();
        }

        private static void OnFontSizeCallbackChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
        {
            DashTextBlock dashTextBlock = source as DashTextBlock;
            dashTextBlock.FontSizeChanged();
        }

        private static void OnDashEnabledChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
        {
            DashTextBlock dashTextBlock = source as DashTextBlock;
            dashTextBlock.DashEnabledChanged();
        }

        private static Pen _transparentPen;

        static DashTextBlock()
        {
            _transparentPen = new Pen(Brushes.Transparent, 0);
            _transparentPen.Freeze();

            DefaultStyleKeyProperty.OverrideMetadata(typeof(DashTextBlock), new FrameworkPropertyMetadata(typeof(DashTextBlock)));
        }

        private TextDecoration _dashDecoration = new TextDecoration();

        public DashTextBlock()
        {
            Binding fontSizeCallbackBinding = new Binding();
            fontSizeCallbackBinding.Source = this;
            fontSizeCallbackBinding.Path = new PropertyPath(TextBlock.FontSizeProperty);
            this.SetBinding(FontSizeCallbackProperty, fontSizeCallbackBinding);
            TextChanged += DashTextBlock_TextChanged;
            this.LayoutUpdated += DashTextBlock_LayoutUpdated;
        }

        private void DashTextBlock_LayoutUpdated(object sender, EventArgs e)
        {
            if (IsLoaded)
            {
                var textRect = GetRectFromCharacterIndex(Text.Length);
                double availableWidth = textRect.Right;
                if (textRect.IsEmpty == false &&
                    availableWidth > 0)
                {
                    this.LayoutUpdated -= DashTextBlock_LayoutUpdated;
                    UpdateTextWithDashing();
                }
            }
        }

        public Color DashColor
        {
            get { return (Color)GetValue(DashColorProperty); }
            set { SetValue(DashColorProperty, value); }
        }

        public double DashThickness
        {
            get { return (double)GetValue(DashThicknessProperty); }
            set { SetValue(DashThicknessProperty, value); }
        }

        public DashStyle DashStyle
        {
            get { return (DashStyle)GetValue(DashStyleProperty); }
            set { SetValue(DashStyleProperty, value); }
        }

        private double FontSizeCallback
        {
            get { return (double)GetValue(FontSizeCallbackProperty); }
            set { SetValue(FontSizeCallbackProperty, value); }
        }

        public double TextLength
        {
            get { return (double)GetValue(TextLengthProperty); }
            set { SetValue(TextLengthProperty, value); }
        }

        public bool DashEnabled
        {
            get { return (bool)GetValue(DashEnabledProperty); }
            set { SetValue(DashEnabledProperty, value); }
        }

        private void DashTextBlock_TextChanged(object sender, TextChangedEventArgs e)
        {
            UpdateTextWithDashing();
        }

        private void FontSizeChanged()
        {
            //UpdateTextWithDashing();
        }

        private void DashEnabledChanged()
        {
            UpdateTextWithDashing();
        }

        private void DashColorChanged()
        {
            UpdateTextWithDashing();
        }

        private void DashStyleChanged()
        {
            UpdateTextWithDashing();
        }

        private void DashThicknessChanged()
        {
            UpdateTextWithDashing();
        }

        public void UpdateTextWithDashing()
        {
            AddDashDecoration();
            _dashDecoration.Pen = CreatePenFromProperties();
        }

        private Pen CreatePenFromProperties()
        {
            if (!DashEnabled)
            {
                return _transparentPen;
            }
            if (DashStyle.Dashes.Count < 2 ||
                IsLoaded == false ||
                Text.Length == 0)
            {
                return new Pen(new SolidColorBrush(DashColor), DashThickness);
            }
            double length = 0.0;
            foreach (var dash in DashStyle.Dashes)
            {
                length += dash;
            }
            double stepLength = 1.0 / length;

            TextBox textBox = this as TextBox;
            Rect textRect = Rect.Empty;
            for (int l = (textBox.Text.Length - 1); l >= 0; l--)
            {
                if (textBox.Text[l] != ' ')
                {
                    try
                    {
                        textRect = textBox.GetRectFromCharacterIndex(l + 1);
                    }
                    catch
                    {
                        // See possible bug here:
                        // https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/VirtualizingStackPanel.cs,8060
                        // TODO: Revisit after migrate to .NET 5
                    }
                    break;
                }
            }
            double target = FontSize;
            double availableWidth = textRect.Right;
            if (textRect.IsEmpty == false &&
                availableWidth > 0)
            {
                TextLength = availableWidth;
                double current = 0;
                bool count = true;
                bool foundTargetLength = false;
                double savedDashes = 0.0;
                while (!foundTargetLength)
                {
                    for (int i = 0; i < DashStyle.Dashes.Count; i++)
                    {
                        var dash = DashStyle.Dashes[i];
                        savedDashes += dash;
                        double increase = (target * (dash * stepLength));
                        double preDiff = availableWidth - current;
                        current += increase;
                        double postDiff = current - availableWidth;
                        if (current > availableWidth)
                        {
                            if (!count)
                            {
                                if (postDiff < preDiff || Text.Length <= 2)
                                {
                                    if ((i + 1) < DashStyle.Dashes.Count)
                                    {
                                        savedDashes += DashStyle.Dashes[i + 1];
                                    }
                                    else
                                    {
                                        savedDashes += DashStyle.Dashes[0];
                                    }
                                }
                                else
                                {
                                    if (i == 0)
                                    {
                                        savedDashes -= DashStyle.Dashes.Last();
                                    }
                                    else
                                    {
                                        savedDashes -= DashStyle.Dashes[i - 1];
                                    }
                                }
                            }
                            foundTargetLength = true;
                            target = availableWidth / (savedDashes * stepLength);
                            break;
                        }
                        count = !count;
                    }
                }
            }

            LinearGradientBrush dashBrush = new LinearGradientBrush();
            dashBrush.StartPoint = new Point(0, 0);
            dashBrush.EndPoint = new Point(target, 0);
            dashBrush.MappingMode = BrushMappingMode.Absolute;
            dashBrush.SpreadMethod = GradientSpreadMethod.Repeat;

            double offset = 0.0;
            bool isFill = true;
            foreach (var dash in DashStyle.Dashes)
            {
                GradientStop gradientStop = new GradientStop();
                gradientStop.Offset = offset;
                gradientStop.Color = isFill ? DashColor : Colors.Transparent;
                dashBrush.GradientStops.Add(gradientStop);

                offset += (dash * stepLength);

                gradientStop = new GradientStop();
                gradientStop.Offset = offset;
                gradientStop.Color = isFill ? DashColor : Colors.Transparent;
                dashBrush.GradientStops.Add(gradientStop);

                isFill = !isFill;
            }

            Pen dashPen = new Pen(dashBrush, DashThickness);
            return dashPen;
        }

        private void AddDashDecoration()
        {
            foreach (TextDecoration textDecoration in TextDecorations)
            {
                if (textDecoration == _dashDecoration)
                {
                    return;
                }
            }
            TextDecorations.Add(_dashDecoration);
        }
    }

样式

<Style TargetType="{x:Type controls:DashTextBlock}">
        <Setter Property="IsReadOnly" Value="True"/>
        <Setter Property="IsTabStop" Value="False"/>
        <Setter Property="Focusable" Value="False"/>
        <Setter Property="VerticalAlignment" Value="Center"/>
        <Setter Property="VerticalContentAlignment" Value="Center"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type controls:DashTextBlock}">
                    <Border x:Name="border"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="0"
                            Background="{TemplateBinding Background}"
                            SnapsToDevicePixels="True">
                        <ScrollViewer x:Name="PART_ContentHost"
                                        Focusable="False"
                                        HorizontalScrollBarVisibility="Hidden"
                                        VerticalScrollBarVisibility="Hidden"/>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsEnabled" Value="False">
                            <Setter Property="Opacity" TargetName="border" Value="0.56"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

相关问题