Создайте пользовательский FrameworkContentElement, чтобы добавить диагональную линию поверх текста в WPF

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

Что-то вроде перечеркнутого декора, но с диагональной формой: пример перечеркнутого и диагонального декора

От TextDecoration или TextEffect нельзя родить (они опломбированы).

Любая идея?


person el_shayan    schedule 25.03.2011    source источник


Ответы (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));                    
            }));

}

развлекайся!

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

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

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

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

person Markus Hütter    schedule 26.03.2011
comment
ой! это похоже на долгий путь! Мой первоначальный план заключался в том, чтобы использовать его как Run, но сейчас я бы выбрал Inside FlowDocument. - person el_shayan; 27.03.2011
comment
Вау! а для диагонального эффекта имеет смысл держать всю деталь небьющейся (в обертке). Большое Вам спасибо. - person el_shayan; 29.03.2011

<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, мы должны установить присоединенное свойство после загрузки запуска.

person Sparkling    schedule 18.11.2011

Поместите свой текст в 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>
person Rachel    schedule 25.03.2011
comment
Спасибо, Рэйчел, конечно, это сработает, но вопрос скорее в расширении декораций, чем в рисовании линии. - person el_shayan; 25.03.2011
comment
Лично я бы создал собственный UserControl, который реализует этот стиль, и просто использовал бы его вместо TextBlock или Label, когда вам нужно такое поведение. В качестве альтернативы вы можете написать AttachedProperty, который делает то же самое. - person Rachel; 25.03.2011
comment
Это моя точка зрения: легко расширить элемент управления, но не встроенный элемент управления (если вы предпочитаете использовать его напрямую, а не обертывать внутри InlineUIContainer). Кроме того, что-то вроде рисования линии над текстом больше похоже на украшение или эффект. Вы даже не можете использовать Adorners со встроенным элементом. - person el_shayan; 25.03.2011