创建自定义 FrameworkContentElement 以在 WPF 中的文本上添加对角线

发布于 2024-10-27 11:21:56 字数 248 浏览 8 评论 0原文

有没有办法创建一个自定义的FrameworkContentElement(或内联),在其内容上绘制对角线?

类似于删除线装饰,但具有对角线形状: 删除线和对角装饰的示例

不可能是 TextDecorationTextEffect(它们是密封的)。

有什么想法吗?

Is there any way to create a custom FrameworkContentElement (or an Inline) that draws a diagonal line over its content?

Something like Strike-through decoration but with a diagonal shape:
example of strike-through and diagonal decorations

It is not possible to inherent from TextDecoration or TextEffect (they are sealed).

Any idea?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

病女 2024-11-03 11:21:56

更新

我尝试创建一个尽可能小的示例。在更复杂的场景中,您将不得不扩展它。它的外观如下:

在此处输入图像描述

这是相应的 xaml:

<AdornerDecorator>
    <StackPanel>
        <TextBlock>
            <Run>this is normal Text</Run><LineBreak/>
            <Run local:DiagonalStrikeThroughAdorner.StrikeThroughBrush="Red">Some text with diagonal decoration</Run><LineBreak/>
            <Run>more normal text yeah</Run>
        </TextBlock> 
        <FlowDocumentScrollViewer>
            <FlowDocument>
                <Paragraph>
                    <Run>this is normal Text</Run>
                    <LineBreak/>
                    <Run local:DiagonalStrikeThroughAdorner.StrikeThroughBrush="Red">Some text with diagonal decoration</Run>
                    <LineBreak/>
                    <Run>more normal text yeah</Run>
                </Paragraph>
            </FlowDocument>
        </FlowDocumentScrollViewer>
    </StackPanel>
</AdornerDecorator>

这就是隐藏代码:

public class DiagonalStrikeThroughAdorner : Adorner
{
    private readonly Inline _inline;
    private readonly Pen _pen;

    public DiagonalStrikeThroughAdorner(UIElement adornedElement, Inline inline, Brush brush) : base(adornedElement)
    {
        _inline = inline;
        _pen = new Pen(brush, 2);
    }

    protected override void OnRender(DrawingContext drawingContext)
    {

        if(!(_inline.ContentStart.HasValidLayout && _inline.ContentEnd.HasValidLayout))
            return;
        var startrect = _inline.ContentStart.GetCharacterRect(LogicalDirection.Forward);
        var endrect = _inline.ContentEnd.GetCharacterRect(LogicalDirection.Backward);

        drawingContext.DrawLine(_pen,startrect.BottomLeft,endrect.TopRight);
    }

    public static Brush GetStrikeThroughBrush(DependencyObject obj)
    {
        return (Brush)obj.GetValue(StrikeThroughBrushProperty);
    }

    public static void SetStrikeThroughBrush(DependencyObject obj, Brush value)
    {
        obj.SetValue(StrikeThroughBrushProperty, value);
    }

    public static readonly DependencyProperty StrikeThroughBrushProperty =
        DependencyProperty.RegisterAttached("StrikeThroughBrush", typeof(Brush), typeof(DiagonalStrikeThroughAdorner), new UIPropertyMetadata((o, args) =>
            {
                if(!(o is TextElement)) return;
                var parent = ((TextElement)o).Parent;
                while (parent is FrameworkContentElement)
                    parent = ((FrameworkContentElement) parent).Parent;
                if (parent == null || !(parent is Visual)) return;
                var adornerLayer = AdornerLayer.GetAdornerLayer((Visual) parent);
                if(adornerLayer == null) return;
                adornerLayer.Add(new DiagonalStrikeThroughAdorner((UIElement) parent,o as Inline,(Brush) args.NewValue));                    
            }));

}

玩得开心!

原始消息

这通常很难。我已成功将装饰器附加到流程文档中的特定元素,但有许多特殊情况需要考虑。例如:如果 Inline 被包裹起来会发生什么?此外:如果此流程文档位于 Richtextbox 中,则其内部会不断重新排列运行(连接或分离它们),这几乎会弄乱所有内容。你必须仔细设置。

请详细说明该内联的位置。在 FlowdocumentScrollviewer 内部?还是文本块?或者 Richtextbox?由于您必须将装饰器附加到管理 FrameworkElement(您可能已经注意到无法将装饰器直接附加到 FrameworkContentElement),因此我们需要知道内联位于何处。

我将描述如何完成此任务的一般路线:创建一个用于创建装饰器的附加属性。附加属性设置在将要装饰的内联上。装饰器保留对内联的引用并附加到管理 FrameworkElement。订阅该框架元素上的布局更新并在装饰器上执行 InvalidateVisual。装饰器 OnRender 根据内联 ContentStart 和 ContentEnd GetCharacterRect 矩形。完毕。

UPDATE:

I tried to create an example as minimal as possible. In more complex scenarios you will have to extend this. Here is how it looks:

enter image description here

this is the corresponding xaml:

<AdornerDecorator>
    <StackPanel>
        <TextBlock>
            <Run>this is normal Text</Run><LineBreak/>
            <Run local:DiagonalStrikeThroughAdorner.StrikeThroughBrush="Red">Some text with diagonal decoration</Run><LineBreak/>
            <Run>more normal text yeah</Run>
        </TextBlock> 
        <FlowDocumentScrollViewer>
            <FlowDocument>
                <Paragraph>
                    <Run>this is normal Text</Run>
                    <LineBreak/>
                    <Run local:DiagonalStrikeThroughAdorner.StrikeThroughBrush="Red">Some text with diagonal decoration</Run>
                    <LineBreak/>
                    <Run>more normal text yeah</Run>
                </Paragraph>
            </FlowDocument>
        </FlowDocumentScrollViewer>
    </StackPanel>
</AdornerDecorator>

and that's the codebehind:

public class DiagonalStrikeThroughAdorner : Adorner
{
    private readonly Inline _inline;
    private readonly Pen _pen;

    public DiagonalStrikeThroughAdorner(UIElement adornedElement, Inline inline, Brush brush) : base(adornedElement)
    {
        _inline = inline;
        _pen = new Pen(brush, 2);
    }

    protected override void OnRender(DrawingContext drawingContext)
    {

        if(!(_inline.ContentStart.HasValidLayout && _inline.ContentEnd.HasValidLayout))
            return;
        var startrect = _inline.ContentStart.GetCharacterRect(LogicalDirection.Forward);
        var endrect = _inline.ContentEnd.GetCharacterRect(LogicalDirection.Backward);

        drawingContext.DrawLine(_pen,startrect.BottomLeft,endrect.TopRight);
    }

    public static Brush GetStrikeThroughBrush(DependencyObject obj)
    {
        return (Brush)obj.GetValue(StrikeThroughBrushProperty);
    }

    public static void SetStrikeThroughBrush(DependencyObject obj, Brush value)
    {
        obj.SetValue(StrikeThroughBrushProperty, value);
    }

    public static readonly DependencyProperty StrikeThroughBrushProperty =
        DependencyProperty.RegisterAttached("StrikeThroughBrush", typeof(Brush), typeof(DiagonalStrikeThroughAdorner), new UIPropertyMetadata((o, args) =>
            {
                if(!(o is TextElement)) return;
                var parent = ((TextElement)o).Parent;
                while (parent is FrameworkContentElement)
                    parent = ((FrameworkContentElement) parent).Parent;
                if (parent == null || !(parent is Visual)) return;
                var adornerLayer = AdornerLayer.GetAdornerLayer((Visual) parent);
                if(adornerLayer == null) return;
                adornerLayer.Add(new DiagonalStrikeThroughAdorner((UIElement) parent,o as Inline,(Brush) args.NewValue));                    
            }));

}

have fun!

original message:

this is generally quite hard. I have managed to attach an adorner to specific elements in flowdocuments but there are many cornercases to consider. for example: what's supposed to happen if that Inline is wrapped around? further: if this flowdocument sits in a richtextbox, its internals keep rearranging runs (joining or separating them) which pretty much messes up everything. you have to set things up carefully.

Please elaborate more on where this inline is going to be at. Inside a FlowdocumentScrollviewer? Or a TextBlock? Or a Richtextbox? As you have to attach the adorner to the governing FrameworkElement (as you probably already noticed you can't attach an Adorner to a FrameworkContentElement directly) we need to know where the inline sits.

I will describe the general route for how to accomplish this though: create an attached property thats going to create the adorner. the attached property is set on the inline that's going to be adorned. the adorner keeps a reference to the inline and is attached to the governing FrameworkElement. subscibe to layoutupdated on that frameworkelement and do an InvalidateVisual on the Adorner. The adorners OnRender draws the line with coordinates depending on the Inlines ContentStart and ContentEnd GetCharacterRect rectangles. done.

等风来 2024-11-03 11:21:56
<TextBlock>
        <Run>this is normal Text</Run><LineBreak/>
        <Run local:DiagonalStrikeThroughAdorner.StrikeThroughBrush="Red">Some text with diagonal decoration</Run><LineBreak/>
        <Run>more normal text yeah</Run>
    </TextBlock>

Adorner 不会显示,因为

var adornerLayer = AdornerLayer.GetAdornerLayer((Visual) parent);
            if(adornerLayer == null)

总是返回 null,我们应该在加载 Run 后设置附加属性。

<TextBlock>
        <Run>this is normal Text</Run><LineBreak/>
        <Run local:DiagonalStrikeThroughAdorner.StrikeThroughBrush="Red">Some text with diagonal decoration</Run><LineBreak/>
        <Run>more normal text yeah</Run>
    </TextBlock>

the Adorner will not show because

var adornerLayer = AdornerLayer.GetAdornerLayer((Visual) parent);
            if(adornerLayer == null)

always return null, we should set the attached property after the Run is Loaded.

热情消退 2024-11-03 11:21:56

将您的文本放入画布或网格(允许控件重叠的东西)中,并添加一个 Line 对象,其 X/Y 点绑定到您的 TextBlock 位置,

如下所示:

<Canvas HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Yellow">
    <TextBlock x:Name="TestText" Text="This is a Test"  />
    <Line X1="{Binding ElementName=TestText, Path=ActualWidth}"
            Y1="{Binding ElementName=TestText, Path=ActualHeight}"
            X2="{Binding ElementName=TestText, Path=Canvas.Left}"
            Y2="{Binding ElementName=TestText, Path=Canvas.Top}"
            Stroke="Red" StrokeThickness="2" />
</Canvas>

Put your Text in a Canvas or Grid (something that allows controls to overlap) and add a Line object with its X/Y points bound to your TextBlock position

Something like this:

<Canvas HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Yellow">
    <TextBlock x:Name="TestText" Text="This is a Test"  />
    <Line X1="{Binding ElementName=TestText, Path=ActualWidth}"
            Y1="{Binding ElementName=TestText, Path=ActualHeight}"
            X2="{Binding ElementName=TestText, Path=Canvas.Left}"
            Y2="{Binding ElementName=TestText, Path=Canvas.Top}"
            Stroke="Red" StrokeThickness="2" />
</Canvas>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文