WPF - Auto width for FlowDocument's content

Nicolás Bosi 1 Reputation point
2020-04-13T14:07:26.567+00:00

Hello,

I'm having a hard time trying to make a RichTextBox's content in my app auto-sized. Specifically, I want the content inside my RichTextBox to be of automatic width (with a MaxWidth boundary), and Wrap when the width exceeds the available space.

The general structure of my UI is as follows:

<RichTextBox (some properties of the RichTextBox here)>
    <FlowDocument (some properties of the FlowDocument here)>
        <Paragraph>Paragraph 1</Paragraph>
        <Paragraph>Paragraph 2</Paragraph>
        ...
        <Paragraph>Paragraph n</Paragraph>
    </FlowDocument>
</RichTextBox>

Let me clarify this before moving on: The nature of my content is read-only. However, I'm using a read-write control (RichTextBox), and setting its IsReadOnly property to true, because I need to be able to select content, which is not available for TextBlock.

Text Wrapping is working as expected, but I can't make the RichTextBox's width just small enough so that the content of a paragraph is rendered, and no more than it. It just takes all the available width.

Digging into the documentation on FlowDocument, FrameworkElement and FrameworkContentElement, and also the WPF content model, I've found that the problem might just be that Paragraph extends from FrameworkContentElement, which does not come with all the sizing properties. So even if the FlowDocument tries to make its page as narrow as its content, the content (a collection of blocks) does not expose any "Width" property, thus taking up all the available space.

I've looked at UWP's RichTextBlock, which might be what I'm looking for. However, it's only available in a WPF project via XAML Islands, which requires moving to .NET Core 3.0. Unfortunately I cannot do that because I need the app to run on Windows versions prior to Windows 10.

So at this point I'd like to hear the opinion from someone who knows better about WPF content, on the following questions:

  1. Do controls extending from FrameworkContentElement work like this as designed? (i.e. not having an auto-sizing capability)
  2. Are blocks the only kind of content a FlowDocument can directly contain?
  3. Is there any way supported by the framework to set auto-size for blocks? (Particular case of question N°1)
  4. Can choosing another FlowDocument-visualizer help? e.g. moving to a FlowDocumentScrollViewer.
  5. Is Microsoft still making changes to WPF classes? Or are new features/changes only available in UWP, as a way to encourage developers to move to UWP?

Thank you very much in advance for your time. I'll really appreciate any answers you can provide on this.

Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,667 questions
0 comments No comments
{count} votes

1 answer

Sort by: Most helpful
  1. Alex Li-MSFT 1,096 Reputation points
    2020-04-15T06:26:27.137+00:00

    Welcome to our Microsoft Q&A platform!

    If you would like to make a RichTextBox's content auto-sized,you can use my code:

    xaml:

     <RichTextBox     MaxWidth="200" Name="myRichTextBox" TextChanged="MyRichTextBox_OnTextChanged" >  
                <FlowDocument  >  
                    <Paragraph>1</Paragraph>  
                </FlowDocument>  
            </RichTextBox>  
    

    C#:

     public partial class MainWindow : Window  
        {  
            public MainWindow()  
            {  
                InitializeComponent();  
            }  
            private void MyRichTextBox_OnTextChanged(object sender, TextChangedEventArgs e)  
            {  
                myRichTextBox.Width = myRichTextBox.Document.GetFormattedText().WidthIncludingTrailingWhitespace + 20;  
            }  
        }  
        public static class FlowDocumentExtensions  
        {  
            private static IEnumerable<TextElement> GetRunsAndParagraphs(FlowDocument doc)  
            {  
                for (TextPointer position = doc.ContentStart;  
                  position != null && position.CompareTo(doc.ContentEnd) <= 0;  
                  position = position.GetNextContextPosition(LogicalDirection.Forward))  
                {  
                    if (position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementEnd)  
                    {  
                        Run run = position.Parent as Run;  
      
                        if (run != null)  
                        {  
                            yield return run;  
                        }  
                        else  
                        {  
                            Paragraph para = position.Parent as Paragraph;  
      
                            if (para != null)  
                            {  
                                yield return para;  
                            }  
                        }  
                    }  
                }  
            }  
      
            public static FormattedText GetFormattedText(this FlowDocument doc)  
            {  
                if (doc == null)  
                {  
                    throw new ArgumentNullException("doc");  
                }  
      
                FormattedText output = new FormattedText(  
                  GetText(doc),  
                  CultureInfo.CurrentCulture,  
                  doc.FlowDirection,  
                  new Typeface(doc.FontFamily, doc.FontStyle, doc.FontWeight, doc.FontStretch),  
                  doc.FontSize,  
                  doc.Foreground);  
      
                int offset = 0;  
      
                foreach (TextElement el in GetRunsAndParagraphs(doc))  
                {  
                    Run run = el as Run;  
      
                    if (run != null)  
                    {  
                        int count = run.Text.Length;  
      
                        output.SetFontFamily(run.FontFamily, offset, count);  
                        output.SetFontStyle(run.FontStyle, offset, count);  
                        output.SetFontWeight(run.FontWeight, offset, count);  
                        output.SetFontSize(run.FontSize, offset, count);  
                        output.SetForegroundBrush(run.Foreground, offset, count);  
                        output.SetFontStretch(run.FontStretch, offset, count);  
                        output.SetTextDecorations(run.TextDecorations, offset, count);  
      
                        offset += count;  
                    }  
                    else  
                    {  
                        offset += Environment.NewLine.Length;  
                    }  
                }  
      
                return output;  
            }  
      
            private static string GetText(FlowDocument doc)  
            {  
                StringBuilder sb = new StringBuilder();  
      
                foreach (TextElement el in GetRunsAndParagraphs(doc))  
                {  
                    Run run = el as Run;  
                    sb.Append(run == null ? Environment.NewLine : run.Text);  
                }  
                return sb.ToString();  
            }  
        }  
    

    7345-1.gif

    Thanks.

    2 people found this answer helpful.
    0 comments No comments