Create custom FrameworkContentElement to add diagonal line over text in WPF

泄露秘密 提交于 2019-12-10 03:49:11

问题


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:

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

Any idea?


回答1:


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:

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.




回答2:


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




回答3:


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>


来源:https://stackoverflow.com/questions/5432395/create-custom-frameworkcontentelement-to-add-diagonal-line-over-text-in-wpf

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!