locked
RichTextBox paragraph spacing is inconsistent RRS feed

  • Question

  • Hi everyone. I'm having a bit of an issue with a WPF RichTextBox that I'm using in a VS 2005 (VB.NET) project. The control works great except for a really annoying spacing issue with paragraphs. When a user presses the Enter key, it double-spaces the paragraph. So when they finish typing their report and click the Save button that I have on my project, the RTF contents of the box are written to a SQL database. The user can then later open and review that same data, and when they do it shows up as single-spaced. This also happens when I load the data in a Crystal Report.

    I did some hunting around and found people with similar problems, but their questions were either unanswered or the recommendations they were given had no effect. I ended up adding this code to the form's load sub:

       Dim p As Paragraph = TryCast(RichTextBox1.Document.Blocks.FirstBlock, Paragraph)
       p.LineHeight = 1

    This makes the paragraph spacing look fine when entering data, but when opening the saved data in Crystal Reports for example, the paragraphs are all crushed in on top of each other. I changed the second line of the code above to p.LineHeight = 10, and this was giving me larger spaces in the text box but it was spacing out on the Crystal Report fine.

    I just want the paragraph spacing on this control to be normal. If the user hits Enter, it just drops to the next line -- no special formatting, no special paragraph spacing. How can I do this and make sure it's saving the RTF to reflect this?
    Thursday, October 8, 2009 1:26 PM

Answers

  • This is a bug, and I wrote a small article about it (including a workaround). Also submitted this to Microsoft Connect, but nothing has happened yet.
    Geert van Horrik - CatenaLogic
    Visit my blog: http://blog.catenalogic.com

    Looking for a way to deploy your updates to all your clients? Try Updater!
    Friday, October 9, 2009 5:39 AM
  • If you just want to remove the paragraph spacing at all from the rich text box, you might want to try this method below from the code I posted. Use it on the whole document like this new TextRange(rtb.ContentStart, rtb.ContentEnd);

    /// <summary>
    /// Clears the margins of a <see cref="TextRange"/>.
    /// </summary>
    /// <param name="range"><see cref="TextRange"/> to clear the margins of.</param>
    public static void ClearTextMargins(this TextRange range)
    {
        // Validate input
        if (range == null) return;
    
        // Loop all blocks in the range
        if (range.Start.Paragraph != null)
        {
            // Get the current range
            TextRange curRange = null;
            Block cur = range.Start.Paragraph;
    
            // Loop
            do
            {
                // In case of a list item, set special margin
                if (cur.Parent is ListItem)
                {
                    List list = ((ListItem)cur.Parent).Parent as List;
                    if (list != null)
                    {
                        list.Margin = new Thickness(0d);
                        cur = list;
                    }
                }
                else
                {
                    // Clear the margin (let document decide the margins)
                    cur.ClearValue(Block.MarginProperty);
                }
    
                // Get next range
                curRange = new TextRange(cur.ContentStart, cur.ContentEnd);
            } while ((range.End.Paragraph == null || !curRange.Contains(range.End.Paragraph.ContentEnd)) && (cur = cur.NextBlock) != null);
        }
    }



    Geert van Horrik - CatenaLogic
    Visit my blog: http://blog.catenalogic.com

    Looking for a way to deploy your updates to all your clients? Try Updater!
    • Marked as answer by Zielek Friday, October 16, 2009 2:32 PM
    Friday, October 16, 2009 11:06 AM

All replies

  • Hi,

    I think this will work-

    <RichTextBox>
    <RichTextBox.Resources>
                    <Style TargetType="{x:Type Paragraph}">
                        <Setter Property="Margin" Value="0"/>
                    </Style>
                    <Style TargetType="{x:Type Table}">
                        <Setter Property="Margin" Value="0"/>
                    </Style>
                    <Style TargetType="{x:Type List}">
                        <Setter Property="Margin" Value="0"/>
                        <Style.Resources>
                                <Style TargetType="{x:Type ListItem}">
                                <Style.Resources>
                                    <Style TargetType="{x:Type Paragraph}">
                                        <Setter Property="Margin" Value="0"/>
                                    </Style>
                                </Style.Resources>
                            </Style>
                        </Style.Resources>
                    </Style>
     </RichTextBox.Resources>

    (this is caused by Margin settig it to 0 in style will make it single spaced default)

    • Marked as answer by Jim Zhou - MSFT Wednesday, October 14, 2009 10:01 AM
    • Unmarked as answer by Zielek Wednesday, October 14, 2009 1:40 PM
    Thursday, October 8, 2009 2:06 PM
  • This is a bug, and I wrote a small article about it (including a workaround). Also submitted this to Microsoft Connect, but nothing has happened yet.
    Geert van Horrik - CatenaLogic
    Visit my blog: http://blog.catenalogic.com

    Looking for a way to deploy your updates to all your clients? Try Updater!
    Friday, October 9, 2009 5:39 AM
  • Thanks for the replies. I'll try your workaround, Geert. I agree, MS should fix it! It's been a particularly irritating issue. :)

    Himanshu: I'm not sure your solution is applicable since I'm doing this in VS2005 and not VS2008. I don't know how to apply that script to the control.
    Wednesday, October 14, 2009 1:42 PM
  • I believe the issue you're referring to varies a bit from what I'm seeing. My issue is that the user will type in this:

    Heading 1:
    Text that should be located directly under header.
    Just a new line, not a new paragraph.

    And we just hit a new paragraph.

    Heading 2:
    Heading 2 content is displayed here.

    New paragraph.
    But this is just a new line.

    Heading 3:
    Heading 3 content.


    They'll then click on the Save button, which writes the RTF behind their text to a database field. When they open the record up again in the richtextbox or in Crystal Reports, they see:

    Heading 1:
    Text that should be located directly under header.
    Just a new line, not a new paragraph.
    And we just hit a new paragraph.
    Heading 2:
    Heading 2 content is displayed here.
    New paragraph.
    But this is just a new line.
    Heading 3:
    Heading 3 content.



    If I copy the RTF directly from the database and open it in MS Word or another RTF editor, it displays exactly the same... no paragraph spacing, even though it was there when the user entered the information. It's like the RTF didn't save the paragraphs correctly or something. I'm not really sure what's causing it.

    This is the function I wrote to get the RTF from the richtextbox. I save the result of this function to the database:

    Public Function GetRTFFromRTB(ByVal rtbBox As System.Windows.Controls.RichTextBox) As String
    
        Dim range As TextRange = New TextRange(rtbBox.Document.ContentStart, rtbBox.Document.ContentEnd)
        Dim memStream As MemoryStream = New MemoryStream
    
        range.Save(memStream, System.Windows.DataFormats.Rtf.ToString())
        Dim RTFText As String = ASCIIEncoding.Default.GetString(memStream.ToArray())
    
        memStream.Close()
        memStream = Nothing
        range = Nothing
    
        Return RTFText
    
    End Function
    
    • Edited by Zielek Wednesday, October 14, 2009 3:22 PM
    Wednesday, October 14, 2009 3:17 PM
  • I explained this in the blog article. The actual reason it fails is that the rich textbox uses \r\n (System.Environment.NewLine) as a new line constant. However, a new line is \n (soft return), \r\n is a new paragraph (hard return). That's what is going wrong.
    Geert van Horrik - CatenaLogic
    Visit my blog: http://blog.catenalogic.com

    Looking for a way to deploy your updates to all your clients? Try Updater!
    Wednesday, October 14, 2009 3:20 PM
  • I read the article and I tried implementing your solution but it doesn't seem to have an effect. Everything still shows as single-spaced, regardless of what the original spacing was.

    I can send you a copy of the form if you'd like to see it.

    Honestly the most preferable solution for me would be to have the paragraph spacing in this box work like a regular textbox. I'm only using this control for the integrated spelling check and the basic font options (bold/italic/underline). I'd rather it just single-space everything, whether the user hit enter or shift+enter. I just can't seem to figure out how to reliably do that. If I set the lineheight to a low value, the text displays incorrectly under Crystal Reports. For example, I tried setting the paragraph lineheight to 1. In the richtextbox, the spacing appeared perfect. When Crystal Reports displayed the RTF, all of the lines that the user entered appeared on top of each other.
    Thursday, October 15, 2009 7:21 PM
  • If you just want to remove the paragraph spacing at all from the rich text box, you might want to try this method below from the code I posted. Use it on the whole document like this new TextRange(rtb.ContentStart, rtb.ContentEnd);

    /// <summary>
    /// Clears the margins of a <see cref="TextRange"/>.
    /// </summary>
    /// <param name="range"><see cref="TextRange"/> to clear the margins of.</param>
    public static void ClearTextMargins(this TextRange range)
    {
        // Validate input
        if (range == null) return;
    
        // Loop all blocks in the range
        if (range.Start.Paragraph != null)
        {
            // Get the current range
            TextRange curRange = null;
            Block cur = range.Start.Paragraph;
    
            // Loop
            do
            {
                // In case of a list item, set special margin
                if (cur.Parent is ListItem)
                {
                    List list = ((ListItem)cur.Parent).Parent as List;
                    if (list != null)
                    {
                        list.Margin = new Thickness(0d);
                        cur = list;
                    }
                }
                else
                {
                    // Clear the margin (let document decide the margins)
                    cur.ClearValue(Block.MarginProperty);
                }
    
                // Get next range
                curRange = new TextRange(cur.ContentStart, cur.ContentEnd);
            } while ((range.End.Paragraph == null || !curRange.Contains(range.End.Paragraph.ContentEnd)) && (cur = cur.NextBlock) != null);
        }
    }



    Geert van Horrik - CatenaLogic
    Visit my blog: http://blog.catenalogic.com

    Looking for a way to deploy your updates to all your clients? Try Updater!
    • Marked as answer by Zielek Friday, October 16, 2009 2:32 PM
    Friday, October 16, 2009 11:06 AM
  • That's perfect. Thanks so much for all of your help!
    Friday, October 16, 2009 2:33 PM