locked
Finding text on a page and scrolling to it. RRS feed

  • Question

  • Hi

    I've been searching everywhere, and maybe I'm looking for the wrong thing, but I can't seem to find much on searching a RichTextBlock or Scrolling to that text if I were to find it.

    I am programmatically adding text from my data source to a RichTextBlock that is wrapped in a ScrollViewer (orientation is vertical if it matters).  Once I've loaded that text I'd like to be able to programmatically scroll the first instance of a particular string in the text into view if it is there but not visible. 

    FYI: Code-wise, I have a stackpanel in the XAML, and when I show the text, I programmatically create a RichTextBlock, then add Paragraphs with Runs based on data I am reading from my data source.  I then add the RichTextBlock to the stack panel.  Is there a better way to do that, so that I can accomplish what I want to do?

    Saturday, January 25, 2014 12:57 AM

Answers

  • If you add controls dynamicaly, you have to update the layout before any size related calculation. 

    UIElement.UpdateLayout() is what you need.

    var paragraphs = Enumerable.Range(0, 1000)
                    .Select(x => x.ToString())
                    .Select(x =>
                    {
                        var p = new Paragraph();
                        p.Inlines.Add(new Run { Text = x });
                        return p;
                    });
                    
                foreach(var p in paragraphs)
                {
                    Rtb.Blocks.Add(p);
                }
    
                Rtb.UpdateLayout();
    
                //Get all the "Run" elements from your RichTextBlock
                var runs = Rtb.Blocks
                    .OfType<Paragraph>()
                    .SelectMany(block => block.Inlines)
                    .OfType<Run>();
    
                foreach(var run in runs)
                {
                    //get the position of your text (ex : 666 )
                    var index = run.Text.IndexOf("666");
    
                    //if your string is in this run
                    if(index != -1)
                    {
                        //Get position for the first character of your string inside the richtextblock
                        var rect = run.ContentStart
                            .GetPositionAtOffset(index, LogicalDirection.Forward)
                            .GetCharacterRect(LogicalDirection.Forward);
    
                        //then scroll
                        Scrollviewer.ChangeView(null, rect.Y, null);
    
                        // finaly break to scroll only to the first occurence
                        break;
                    }
                }

    • Marked as answer by NewCitySteve Monday, January 27, 2014 4:08 PM
    Saturday, January 25, 2014 9:52 PM

All replies

  • You can use the TextPointer.GetCharacterRect and the ScrollViewer.ChangeView to do this.

                //Get all the "Run" elements from your RichTextBlock
                var runs = Rtb.Blocks
                    .OfType<Paragraph>()
                    .SelectMany(block => block.Inlines)
                    .OfType<Run>();
    
                foreach(var run in runs)
                {
                    //locate the string in "run" (ex : Praesent )
                    var index = run.Text.IndexOf("Praesent");
    
                    //if your string is in this run
                    if(index != -1)
                    {
                        //Get the visual rectangle for the first character of your string inside the richtextblock
                        var rect = run.ContentStart
                            .GetPositionAtOffset(index, LogicalDirection.Forward)
                            .GetCharacterRect(LogicalDirection.Forward);
    
                        //then scroll
                        Scrollviewer.ChangeView(null, rect.Y, null);
    
                        // finaly break to scroll only to the first occurence
                        break;
                    }
                }

    There is the xaml part to test this code.

    <ScrollViewer HorizontalScrollBarVisibility="Disabled"
                          VerticalScrollBarVisibility="Auto"
                          x:Name="Scrollviewer">
                <RichTextBlock Width="300" x:Name="Rtb">
                    <Paragraph FontSize="22">
                        <Run Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam at tortor vitae risus auctor laoreet at ut risus. Donec congue ante turpis, at tincidunt erat consequat in. Donec quis sem et leo varius faucibus. Donec blandit nisi et fermentum vehicula. Etiam nisi neque, euismod quis sollicitudin at, pellentesque eget elit. Etiam vitae dolor vitae lacus commodo semper nec vel est. Fusce eu sagittis nisl. Etiam at ante urna. Nullam vehicula arcu eget dui lobortis, ut consectetur lorem luctus. Sed euismod tristique felis, et mollis neque mollis in. Praesent condimentum justo nec velit gravida scelerisque. Cras viverra nulla non tellus suscipit dignissim. Ut arcu lectus, tempus at mi euismod, euismod sollicitudin metus. Maecenas venenatis metus vel dui auctor, eget semper tellus pharetra. In volutpat erat in fringilla pellentesque."/>
                    </Paragraph>
                    <Paragraph FontSize="22">
                        <Run Text="Nulla adipiscing magna ante, ut sagittis orci suscipit et. Nam eu elit id felis sollicitudin dignissim. Nam sagittis adipiscing elit eu tempor. Fusce sapien risus, tincidunt nec urna eget, porta ultrices eros. Integer fermentum mollis libero, nec eleifend dui semper a. Duis in felis nec dolor tincidunt posuere. Quisque interdum condimentum dui a blandit."/>
                    </Paragraph>
                    <Paragraph FontSize="22">
                        <Run Text="In at rutrum ipsum. Mauris rhoncus, felis quis sodales molestie, dui velit suscipit felis, ac pellentesque magna quam sit amet justo. Maecenas a justo id dolor varius tempor. Mauris ac libero vel ligula tempus pellentesque. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Fusce quis cursus turpis. Mauris eget ante quis sem elementum blandit. Maecenas eget aliquet massa. Sed at magna diam. Donec enim erat, condimentum et convallis sit amet, auctor et turpis. Quisque eget magna quis nibh rhoncus semper quis non dolor. Nullam interdum eget risus ac porta."/>
                    </Paragraph>
                </RichTextBlock>
            </ScrollViewer>

    Don't hesitate to let me know if you need more explanations.


    • Edited by Guillaume Salles Saturday, January 25, 2014 7:02 PM
    • Marked as answer by NewCitySteve Saturday, January 25, 2014 7:09 PM
    • Unmarked as answer by NewCitySteve Saturday, January 25, 2014 8:39 PM
    Saturday, January 25, 2014 7:01 PM
  • Oops, I jumped the gun here.  Using pretty much the exact code, I am able to locate the text, (I even change the color afterward) but rect.Y comes back as 0.0.  The only difference I can see is that I'm adding the paragraphs and runs dynamically.  Did this work for you? 

    Saturday, January 25, 2014 7:16 PM
  • If you add controls dynamicaly, you have to update the layout before any size related calculation. 

    UIElement.UpdateLayout() is what you need.

    var paragraphs = Enumerable.Range(0, 1000)
                    .Select(x => x.ToString())
                    .Select(x =>
                    {
                        var p = new Paragraph();
                        p.Inlines.Add(new Run { Text = x });
                        return p;
                    });
                    
                foreach(var p in paragraphs)
                {
                    Rtb.Blocks.Add(p);
                }
    
                Rtb.UpdateLayout();
    
                //Get all the "Run" elements from your RichTextBlock
                var runs = Rtb.Blocks
                    .OfType<Paragraph>()
                    .SelectMany(block => block.Inlines)
                    .OfType<Run>();
    
                foreach(var run in runs)
                {
                    //get the position of your text (ex : 666 )
                    var index = run.Text.IndexOf("666");
    
                    //if your string is in this run
                    if(index != -1)
                    {
                        //Get position for the first character of your string inside the richtextblock
                        var rect = run.ContentStart
                            .GetPositionAtOffset(index, LogicalDirection.Forward)
                            .GetCharacterRect(LogicalDirection.Forward);
    
                        //then scroll
                        Scrollviewer.ChangeView(null, rect.Y, null);
    
                        // finaly break to scroll only to the first occurence
                        break;
                    }
                }

    • Marked as answer by NewCitySteve Monday, January 27, 2014 4:08 PM
    Saturday, January 25, 2014 9:52 PM
  • Thanks for this, by the way.  It worked beautifully!
    Monday, March 2, 2015 2:36 PM