Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to draw border around a word in RichTextBox?

Let's say I have 2 TextPointers. One pointing at the beginning of a word and the other at the end of the word.

I would like to draw single pixel border around the word. How would I go about this? The border should be tied to the word and move with it when user types or scrolls..

I already tried TextDecorations with DrawingBrush but couldn't come up with anything usable.

like image 880
Kugel Avatar asked May 27 '11 22:05

Kugel


1 Answers

I have done something similar, only underlining text in a TextBox. The principal seems to be mostly the same.

  1. Add an AdornerDecorator containing your RichTextBox but inside a ScrollViewer.

    <Border ...>
        <ScrollViewer ... >
            <AdornerDecorator>
                <RichTextBox
                    x:Name="superMagic"
                    HorizontalScrollBarVisibility="Hidden"
                    VerticalScrollBarVisibility="Hidden"
                    BorderBrush="{x:Null}"
                    BorderThickness="0"
                    ...
                    />
            </AdornerDecorator>
        </ScrollViewer>
    </Border>
    
  2. Create an Adorner to render the rectangle and add it to the AdornerLayer

    void HostControl_Loaded(object sender, RoutedEventArgs e)
    {
        _adorner = new RectangleAdorner(superMagic);
    
        AdornerLayer layer = AdornerLayer.GetAdornerLayer(superMagic);
        layer.Add(_adorner);
    }
    
  3. The adorner should hook the TextChanged event of the RichTextBox. All you need to do is call InvalidateVisuals() via the dispatcher using DispatcherPriority.Background to ensure it is rendered after the text box. I don't know if it's an issue for the RichTextBox, but getting the character coordinates from a TextBox is only possible if it has been rendered at least once since it's content last changed.

    class RectangleAdorner : Adorner
    {
        public RectangleAdorner(RichTextBox textbox)
            : base(textbox)
        {
            textbox.TextChanged += delegate
            {
                SignalInvalidate();
            };
        }
    
        void SignalInvalidate()
        {
            RichTextBox box = (RichTextBox)this.AdornedElement;
            box.Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)InvalidateVisual);
        }
    
        // ...
    }
    
  4. Override Adorner.OnRender() to draw the box using TextPointer.GetCharacterRect() to get the coordinates.

    protected override void OnRender(DrawingContext drawingContext)
    {
        TextPointer start;
        TextPointer end;
    
        // Find the start and end of your word
        // Actually, if you did this in the TextChanged event handler,
        // you could probably save some calculation time on large texts
        // by considering what actually changed relative to an earlier
        // calculation. (TextChangedEventArgs includes a list of changes
        //  - 'n' characters inserted here, 'm' characters deleted there).
    
        Rect startRect = start.GetCharacterRect(LogicalDirection.Backward);
        Rect endRect = end.GetCharacterRect(LogicalDirection.Forward);
    
        drawingContext.DrawRectangle(null, pen, Rect.Union(startRect, endRect));
    }
    

Note: Although the original code worked well, I wrote it a long time ago and have not tested my adaptions for this answer. It should at least help put you on the right path.

Also, this does not handle cases where the word is split across lines, but shouldn't be too hard to cater for.

like image 185
Brian Reichle Avatar answered Oct 02 '22 13:10

Brian Reichle