Создайте пользовательский FrameworkContentElement для добавления диагональной линии над текстом в WPF

Есть ли способ создать кастом FrameworkContentElement (или Inline) что рисует диагональную линию над своим содержимым?

Что-то наподобие зачистного украшения, но диагональной формы:пример зачистного и диагонального оформления

Невозможно присущи от TextDecoration или же TextEffect (они опечатаны).

Любая идея?

3 ответа

Решение

ОБНОВЛЕНИЕ:

Я попытался создать пример как можно меньше. В более сложных сценариях вам придется расширить это. Вот как это выглядит:

это соответствующий 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));                    
            }));

}

повеселись!

исходное сообщение:

это вообще довольно сложно. Мне удалось прикрепить рекламное объявление к конкретным элементам в flowdocuments, но есть много угловых случаев для рассмотрения. например: что должно произойти, если этот Inline обернут? далее: если этот потоковый документ находится в текстовом поле richtextbox, его внутренние компоненты продолжают перестраивать прогоны (объединяя или разделяя их), что в значительной степени портит все. Вы должны все тщательно настроить.

Пожалуйста, опишите подробнее, где эта строка будет находиться. Внутри FlowdocumentScrollviewer? Или текстовый блок? Или Richtextbox? Поскольку вы должны прикрепить рекламное объявление к управляющему FrameworkElement (как вы, вероятно, уже заметили, вы не можете напрямую прикрепить рекламное устройство к FrameworkContentElement), нам нужно знать, где находится встроенный элемент.

Я опишу общий путь для достижения этой цели: создайте прикрепленное свойство, которое будет создавать рекламодателя. прикрепленное свойство установлено на встроенном объекте, который будет украшен. владелец сохраняет ссылку на встроенный элемент и присоединяется к управляющему элементу FrameworkElement. подпишитесь на макет, обновленный на этом элементе фрейма, и создайте InvalidateVisual на Adorner. Украшающие OnRender рисуют линию с координатами в зависимости от прямоугольников Inlines ContentStart и ContentEnd GetCharacterRect. сделанный.

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

Адорнер не покажет, потому что

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

всегда возвращать ноль, мы должны установить присоединенное свойство после того, как Run is Loaded.

Поместите ваш текст в Canvas или Grid (то, что позволяет элементам управления перекрываться) и добавьте объект 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>
Другие вопросы по тегам