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?
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.
<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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With