locked
How to get visible paragraphs of the FlowDocument at the moment? RRS feed

  • Question

  • Somebody, help me please! Let an user scrolls to the certain paragraph using the FlowDocumentScrollViewer. How can I get that paragraph?
    Thursday, March 18, 2010 9:08 AM

Answers

  • Hi povter3091,

    Welcome to MSDN forum. Based on my understanding, you want to achieve a feature. When the user scrolls the scroll bar, your application can know that which paragraph the user scrolls to. Please let me know if I have misunderstood your concern.

    FlowDocumentScrollViewer control does not provide a method that can monitor the elements in the control. However, we can get all elements from the VisualTree, including the elements defined by the system theme. Thanks to VisualTreeHelper, We can look for the elements of our concern layer by layer in the visual tree of control.

    I analyze the visual tree of FlowDocumentScrollViewer. I find that if one paragraph element is scrolled into the visual area, then the FlowDocumentScrollViewer will add a LineVisual element under the ParagraphVisual. LineVisual is the basic element for displaying a paragraph, so that we can check whether there are some LineVisual elements under the ParagraphVisual to determine whether this Paragraph has been scrolled into the visible region.

    Below is the part of visual tree in FlowDocumentScrollViewer:

    And I design a simple sample may help you , below is the code:
    XAML:

    <Window x:Class="FlowDocument.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="437" Width="478">
        <Grid>
            <FlowDocumentScrollViewer Margin="0,0,0,39" ScrollViewer.ScrollChanged="FlowDocumentScrollViewer_ScrollChanged" >
                <FlowDocument x:Name="flowDocument">
                    <Paragraph>Paragraph 1:......</Paragraph>
                    <Paragraph>Paragraph 2:......</Paragraph>
                    <Paragraph>Paragraph 3:......</Paragraph>
                    <Paragraph>Paragraph 4:......</Paragraph>
                    <Paragraph>Paragraph 5:......</Paragraph>
                    <Paragraph>Paragraph 6:......</Paragraph>
                </FlowDocument>
            </FlowDocumentScrollViewer>
            <TextBlock Height="21" HorizontalAlignment="Left" Margin="12,0,0,12" Name="textBlock1" VerticalAlignment="Bottom" Width="120" />
        </Grid>
    </Window>

    Code:
    private void FlowDocumentScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
    	//Get the PageVisual in the VisualTree of the FlowDocumentScrollViewer
    	DependencyObject pageVisual = 
    		 VisualTreeHelper.GetChild(
    			 VisualTreeHelper.GetChild(
    				 VisualTreeHelper.GetChild(
    					 VisualTreeHelper.GetChild(
    						 VisualTreeHelper.GetChild(
    							 VisualTreeHelper.GetChild(
    								 VisualTreeHelper.GetChild(fdsv, 0), 0), 0), 0), 1), 0), 0);
    
    	ContainerVisual container = (ContainerVisual)FindVisualElement(pageVisual, typeof(ContainerVisual));
    	for (int i = 0; i < VisualTreeHelper.GetChildrenCount(container); i++)
    	{ 
    		Type PargraphVusialType = VisualTreeHelper.GetChild(container,i).GetType();
    		if (VisualTreeHelper.GetChildrenCount((FindVisualElement(VisualTreeHelper.GetChild(container, i), PargraphVusialType))) > 0)
    		{
    			textBlock1.Text = "Paragraph " + i.ToString();
    		}
    	}
    }
    
    private DependencyObject FindVisualElement(DependencyObject o, Type type)
    {
    	if (VisualTreeHelper.GetChildrenCount(o) > 0)
    	{
    		if (VisualTreeHelper.GetChild(o, 0).GetType() == type)
    		{
    			return FindVisualElement(VisualTreeHelper.GetChild(o, 0), type);
    		}
    		else { return o; }
    	}
    	else { return o; }
    }


    The following articles introduce the visual tree and logic tree in WPF may help you:

    http://msdn.microsoft.com/en-us/library/ms753391.aspx

    If you have any problem, please feel free to contact me.
     
    Sincerely,
     
    Bob Bao

     


    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    • Proposed as answer by Alexander Yudakov Thursday, March 25, 2010 10:29 AM
    • Marked as answer by Jie Bao Monday, March 29, 2010 1:51 PM
    Friday, March 19, 2010 7:28 AM
  • Hi,

    LogicalTreeHelper.GetChildren() returns an object implements IEnumerable interface, so you can use foreach or GetEnumerator() to visit each child in the element.

    Below is my code based on the above sample:

    .......
    var myParagraph = LogicalTreeHelper.GetChildren(flowDocument).GetEnumerator();
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(container); i++)
    {
      Type PargraphVusialType = VisualTreeHelper.GetChild(container, i).GetType();
      if (myParagraph.MoveNext()&&(VisualTreeHelper.GetChildrenCount((FindVisualElement(VisualTreeHelper.GetChild(container, i), PargraphVusialType))) > 0))
      {
        textBlock1.Text = ((Run)((Paragraph)myParagraph.Current).Inlines.FirstInline).Text;
      }
    }
    ......

    Sincerely,

    Bob Bao


    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    • Marked as answer by povter3091 Monday, March 29, 2010 12:34 PM
    • Marked as answer by povter3091 Monday, March 29, 2010 12:35 PM
    Monday, March 29, 2010 3:06 AM

All replies

  • Hi povter3091,

    Welcome to MSDN forum. Based on my understanding, you want to achieve a feature. When the user scrolls the scroll bar, your application can know that which paragraph the user scrolls to. Please let me know if I have misunderstood your concern.

    FlowDocumentScrollViewer control does not provide a method that can monitor the elements in the control. However, we can get all elements from the VisualTree, including the elements defined by the system theme. Thanks to VisualTreeHelper, We can look for the elements of our concern layer by layer in the visual tree of control.

    I analyze the visual tree of FlowDocumentScrollViewer. I find that if one paragraph element is scrolled into the visual area, then the FlowDocumentScrollViewer will add a LineVisual element under the ParagraphVisual. LineVisual is the basic element for displaying a paragraph, so that we can check whether there are some LineVisual elements under the ParagraphVisual to determine whether this Paragraph has been scrolled into the visible region.

    Below is the part of visual tree in FlowDocumentScrollViewer:

    And I design a simple sample may help you , below is the code:
    XAML:

    <Window x:Class="FlowDocument.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="437" Width="478">
        <Grid>
            <FlowDocumentScrollViewer Margin="0,0,0,39" ScrollViewer.ScrollChanged="FlowDocumentScrollViewer_ScrollChanged" >
                <FlowDocument x:Name="flowDocument">
                    <Paragraph>Paragraph 1:......</Paragraph>
                    <Paragraph>Paragraph 2:......</Paragraph>
                    <Paragraph>Paragraph 3:......</Paragraph>
                    <Paragraph>Paragraph 4:......</Paragraph>
                    <Paragraph>Paragraph 5:......</Paragraph>
                    <Paragraph>Paragraph 6:......</Paragraph>
                </FlowDocument>
            </FlowDocumentScrollViewer>
            <TextBlock Height="21" HorizontalAlignment="Left" Margin="12,0,0,12" Name="textBlock1" VerticalAlignment="Bottom" Width="120" />
        </Grid>
    </Window>

    Code:
    private void FlowDocumentScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
    	//Get the PageVisual in the VisualTree of the FlowDocumentScrollViewer
    	DependencyObject pageVisual = 
    		 VisualTreeHelper.GetChild(
    			 VisualTreeHelper.GetChild(
    				 VisualTreeHelper.GetChild(
    					 VisualTreeHelper.GetChild(
    						 VisualTreeHelper.GetChild(
    							 VisualTreeHelper.GetChild(
    								 VisualTreeHelper.GetChild(fdsv, 0), 0), 0), 0), 1), 0), 0);
    
    	ContainerVisual container = (ContainerVisual)FindVisualElement(pageVisual, typeof(ContainerVisual));
    	for (int i = 0; i < VisualTreeHelper.GetChildrenCount(container); i++)
    	{ 
    		Type PargraphVusialType = VisualTreeHelper.GetChild(container,i).GetType();
    		if (VisualTreeHelper.GetChildrenCount((FindVisualElement(VisualTreeHelper.GetChild(container, i), PargraphVusialType))) > 0)
    		{
    			textBlock1.Text = "Paragraph " + i.ToString();
    		}
    	}
    }
    
    private DependencyObject FindVisualElement(DependencyObject o, Type type)
    {
    	if (VisualTreeHelper.GetChildrenCount(o) > 0)
    	{
    		if (VisualTreeHelper.GetChild(o, 0).GetType() == type)
    		{
    			return FindVisualElement(VisualTreeHelper.GetChild(o, 0), type);
    		}
    		else { return o; }
    	}
    	else { return o; }
    }


    The following articles introduce the visual tree and logic tree in WPF may help you:

    http://msdn.microsoft.com/en-us/library/ms753391.aspx

    If you have any problem, please feel free to contact me.
     
    Sincerely,
     
    Bob Bao

     


    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    • Proposed as answer by Alexander Yudakov Thursday, March 25, 2010 10:29 AM
    • Marked as answer by Jie Bao Monday, March 29, 2010 1:51 PM
    Friday, March 19, 2010 7:28 AM
  • Bob Bao, thank you! Your solution is excellent.
    • Marked as answer by povter3091 Friday, March 19, 2010 9:37 AM
    • Unmarked as answer by povter3091 Thursday, March 25, 2010 10:10 AM
    Friday, March 19, 2010 9:36 AM
  • I am sorry. I can't find the Paragraph using the VisualLine. I tried to use an index of the paragraph, but it's a partial solution for only the FlowDocumentScrollViewer. Because the FlowDocumentReader builds the visual tree only for the current page. Somebody, tell me please, how to find a logical element by visual.
    Thursday, March 25, 2010 10:18 AM
  • Hi,

    WPF also has a class named "LogicalTreeHelper", please refer to this link: http://msdn.microsoft.com/en-us/library/system.windows.logicaltreehelper.aspx.

    And the following blog introduces how to print Visual and Logical tree may help you: http://blog.lab49.com/archives/217

    Sincerely,

    Bob Bao


    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    Friday, March 26, 2010 1:18 AM
  • Nevertheless I can't find a logical element by visual.
    Friday, March 26, 2010 5:19 PM
  • Hi,

    LogicalTreeHelper.GetChildren() returns an object implements IEnumerable interface, so you can use foreach or GetEnumerator() to visit each child in the element.

    Below is my code based on the above sample:

    .......
    var myParagraph = LogicalTreeHelper.GetChildren(flowDocument).GetEnumerator();
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(container); i++)
    {
      Type PargraphVusialType = VisualTreeHelper.GetChild(container, i).GetType();
      if (myParagraph.MoveNext()&&(VisualTreeHelper.GetChildrenCount((FindVisualElement(VisualTreeHelper.GetChild(container, i), PargraphVusialType))) > 0))
      {
        textBlock1.Text = ((Run)((Paragraph)myParagraph.Current).Inlines.FirstInline).Text;
      }
    }
    ......

    Sincerely,

    Bob Bao


    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    • Marked as answer by povter3091 Monday, March 29, 2010 12:34 PM
    • Marked as answer by povter3091 Monday, March 29, 2010 12:35 PM
    Monday, March 29, 2010 3:06 AM
  • This is one of the most awful hacks I have ever seen to do something this simple. And it kills my app, performance of these steps is simply unacceptable. I have 30000 Runs in the document, to check if run is visible or not using this technique is taking 20 seconds.
    Tuesday, June 7, 2011 9:31 PM
  • Please try to follow up the below method:

    private bool IsUserVisible(FrameworkElement element, FrameworkElement container) {
        if (!element.IsVisible)
            return false;
        Rect bounds = element.TransformToAncestor(container).TransformBounds(new Rect(0.0, 0.0, element.ActualWidth, element.ActualHeight));
        Rect rect = new Rect(0.0, 0.0, container.ActualWidth, container.ActualHeight);
        return rect.Contains(bounds.TopLeft) || rect.Contains(bounds.BottomRight);
    }
    

     

    this method can determine if an element is visible. We could refer to it and code to compare the bound of the Run with the bound of the FlowDocumentScrollViewer.


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Wednesday, June 8, 2011 2:57 AM
  • Thanks for the reply, however it seems that the above method will not work, since Run/Inline isn't a FrameworkElement.
    Monday, June 13, 2011 10:54 PM
  • Bob, any update on how to determine if Run is visible inside of a FlowDocumentScrollViewer?
    Thursday, June 16, 2011 2:38 PM
  • Hmm, yes, Run is not a FrameworkElement, so we cannot apply the TransformToAncestor method. However, in you use the RichTextBox to show the FlowDocument, we could use RichTextBox.GetPositionFromPoint Method to get the position from one TextPointer of the Run, and do the comparation.

     


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Friday, June 17, 2011 5:03 AM