17

How can I make RichTextBox with no Margin, Border, Padding etc. ? In another words to display content in the same way as TextBlock does it ? I have tried this:

<RichTextBox Margin="0" Padding="0" Grid.Row="0" BorderThickness="0" >
    <FlowDocument >
        <Paragraph>LLL</Paragraph>
    </FlowDocument>
</RichTextBox>
<TextBlock>LLL</TextBlock>

But the result produces is still not what I want:

enter image description here

There is still some space before document content (and also maybe after, on the top or bottom of the document...). How can I remove it ?


If you are interested why I need this: I trying to make H.B.'s answer to my question Create guitar chords editor in WPF to work with kerning and I don't want to have unnatural space between characters.


Edit

So it is not ControlTemplate at least not only that because following code will produce exactly the same result (as the one on the picture above):

<RichTextBox Margin="0" Padding="0" Grid.Row="0" BorderThickness="0">
    <RichTextBox.Template>
        <ControlTemplate>
            <ScrollViewer Padding="0" Margin="0" x:Name="PART_ContentHost"/>
        </ControlTemplate>
    </RichTextBox.Template>
    <FlowDocument PagePadding="0">
        <Paragraph Padding="0" Margin="0" >LLL</Paragraph>
    </FlowDocument>
</RichTextBox>

And I thought this will be question easy to answer... Interesting observation: when I have template set and I set PagePadding="0" on FlowDocument it displays layout that I want in the VisualStudio designer - until I run the demo. In the demo it is wrong again... And when I close the demo it is wrong again in the designer. This is a small bug of VS or is it actually set to the right layout for a while but then something changes value of PagePadding back to some wrong value ?


Edit#2

Daniel Rose's edited answer is also not working for me. This is XAML:

<FlowDocument PagePadding="{Binding PagePadding}">
    <Paragraph x:Name="paragraph" Padding="0" 
        TextIndent="0"  Margin="0,0,0,0" >hello</Paragraph>
</FlowDocument>

And this is in code:

public static DependencyProperty PagePaddingProperty =
            DependencyProperty.Register("PagePadding", typeof(Thickness),   typeof(EditableTextBlock),
            new PropertyMetadata(new Thickness(0)));

public Thickness PagePadding {
    get { return (Thickness)GetValue(PagePaddingProperty); }
    set { SetValue(PagePaddingProperty, value); }
}

No changes to the result. Space remains.


Edit#3

After adding Two-Way binding as Daniel Rose suggested in his las edit it works. Still I don't really think it is very clear (to have dependency property because I need to keep PagePadding at 0 value). I think it is a hack - bug workaround. If somebody has better solution please share it.

Obviously "changing PagePadding" of FlowDocument to 0,5 is a bug. If somebody has MSDN account it would be nice if they reported this bug.

Community
  • 1
  • 1
Rasto
  • 17,000
  • 40
  • 141
  • 232

4 Answers4

23

I know this is annoying as hell.

RichTextBox sets this PagePadding in it's CreateRenderScope(), ie when it gets attached to the visual tree. At this time all properties are usually already set and thus the PagePadding gets reset.

What I'm about to show you is a more general form of how you can do this using an attached property. In my own code I do this usually more tightly because I know that a) the flowdocument does not change (not having to worry about registering the same handler twice) and b) the padding does not change (having the eventhandler just be ((FlowDocument)s).PagePadding = new Thickness(0.0);. For this being SO though I'll provide a general solution that you can just plug in.

The Solution:

        <RichTextBox BorderThickness="0" Margin="0" Padding="0">
            <FlowDocument local:FlowDocumentPagePadding.PagePadding="0">
                <Paragraph>
                    <Run>text</Run>
                </Paragraph>
            </FlowDocument>
        </RichTextBox>

public static class FlowDocumentPagePadding
{
    public static Thickness GetPagePadding(DependencyObject obj)
    {
        return (Thickness)obj.GetValue(PagePaddingProperty);
    }
    public static void SetPagePadding(DependencyObject obj, Thickness value)
    {
        obj.SetValue(PagePaddingProperty, value);
    }
    public static readonly DependencyProperty PagePaddingProperty =
        DependencyProperty.RegisterAttached("PagePadding", typeof(Thickness), typeof(FlowDocumentPagePadding), new UIPropertyMetadata(new Thickness(double.NegativeInfinity),(o, args) =>
            {
                var fd = o as FlowDocument;
                if (fd == null) return;
                var dpd = DependencyPropertyDescriptor.FromProperty(FlowDocument.PagePaddingProperty, typeof(FlowDocument));
                dpd.RemoveValueChanged(fd, PaddingChanged);
                fd.PagePadding = (Thickness) args.NewValue;
                dpd.AddValueChanged(fd, PaddingChanged);
            }));
    public static void PaddingChanged(object s, EventArgs e)
    {
        ((FlowDocument)s).PagePadding = GetPagePadding((DependencyObject)s);
    }
}

original sourcecode commentary:

In the original source of RichTextBox.CreateRenderScope() the developers included this comment:

// Set a margin so that the BiDi Or Italic caret has room to render at the edges of content.
// Otherwise, anti-aliasing or italic causes the caret to be partially clipped.
renderScope.Document.PagePadding = new Thickness(CaretElement.CaretPaddingWidth, 0, CaretElement.CaretPaddingWidth, 0);

bug report

here is the bug report on Microsoft Connect

Markus Hütter
  • 7,487
  • 1
  • 33
  • 58
  • @Markus Hütter: 2 more notes: First, I guess this is kind of bug right? If you have MSDN account (I don't) did you consider sending bug report to Microsoft ? Maybe they would fix it in .NET 5. Second, I tent to not to accept even good answers very promptly. Not many people read accepted questions. I want to give you chance to get some more upvotes and others to provide some additional information or even offer their solutions. – Rasto May 02 '11 at 18:48
  • 1
    @drasto thank you for your kind comments. I also don't have a MSDN account. And yes from the outside this seems like a bug, but as they explicitely included this statement in CreateRenderScope I'd have to think this behavior was intended. But yes, I also think someone should report this as a bug. – Markus Hütter May 03 '11 at 08:12
  • @Markus Hütter: I'll comment your answer to the other question of mine soon but that one will need longer comment and unfortunately I'm very busy now - and the work in Guitar editor has to be postponed until I finish more urgent staff. So sorry ahnd thank you for your answers and again. – Rasto May 03 '11 at 19:19
  • 1
    @drasto I improved on the code a bit and added more information to my answer. Also this bug was already reported so I added a link to this question as a workaround on the bug report. – Markus Hütter May 04 '11 at 12:09
  • @Markus Hütter: I cannot upvote your answer twice. That is very unfortunate :((. – Rasto May 04 '11 at 23:38
  • Why not just set Margin="-5,0,0,0" on the content host? – Bodekaer Jan 15 '12 at 17:03
  • I was using this clever workaround for several months, **but unfortunately we discovered it leads to a memory leak**. The source of the leak is the call to dpd.AddValueChanged(...) since there is no corresponding cleanup call to RemoveValueChanged(...). You'd likely have to track the lifetime of the FlowDocument or listen for Loaded/Unloaded events to find the right time to call RemoveValueChanged(...). **I lazily just resorted to using Padding = new Thickness(-5,0,-5,0) on the RichTextBox for now**. The leak basically holds the entire visual tree owning the RichTextBox in memory :-( – Nate A Jan 18 '17 at 18:00
  • 1
    "Microsoft Connect Has Been Retired" and seems our bugs are gone now? – Evgeny Gorbovoy Apr 15 '18 at 20:18
3

The whole thing as I previously wrote doesn't work. For some reason the PagePadding is being overwritten as "5,0". However, when I used data-binding, it worked properly. So simply databind to a Thickness of 0. For it to work, you have to two-way databind:

<Window
    x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow"
    Height="350"
    Width="525">
    <StackPanel Orientation="Vertical">
        <RichTextBox BorderThickness="0" Margin="0" Padding="0" >
            <FlowDocument PagePadding="{Binding PagePadding, Mode=TwoWay}">
                <Paragraph>LLL</Paragraph>
            </FlowDocument>
        </RichTextBox>
        <TextBlock>LLL</TextBlock>
    </StackPanel>
</Window>

Code behind:

namespace WpfApplication1
{
    using System.ComponentModel;
    using System.Windows;

    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : INotifyPropertyChanged
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = this;
        }

        private Thickness pagePadding;

        public Thickness PagePadding
        {
            get
            {
                return this.pagePadding;
            }
            set
            {
                this.pagePadding = value;
                this.Changed("PagePadding");
            }
        }

        private void Changed(string name)
        {
            var handlers = this.PropertyChanged;
            if (handlers != null)
            {
                handlers.Invoke(this, new PropertyChangedEventArgs(name));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }
}
Daniel Rose
  • 16,331
  • 9
  • 59
  • 81
2

Try this. It works for me.... It's a lot less headache than the alternatives here...

<RichTextBox Padding="-5,0,-5,0">
   <FlowDocument />
</RichTextBox>
Steve
  • 21
  • 1
0

Actually it's not a bug. This behavior intended to improve display BiDi and Italic caret indicator.

Look at .Net 4.8 source, in the RichTextBox.cs file:

// Allocates the initial render scope for this control.
internal override FrameworkElement CreateRenderScope()
{
    FlowDocumentView renderScope = new FlowDocumentView();
    renderScope.Document = this.Document;

    // Set a margin so that the BiDi Or Italic caret has room to render at the edges of content.
    // Otherwise, anti-aliasing or italic causes the caret to be partially clipped.
    renderScope.Document.PagePadding = new Thickness(CaretElement.CaretPaddingWidth, 0, CaretElement.CaretPaddingWidth, 0);

    // We want current style to ignore all properties from theme style for renderScope.
    renderScope.OverridesDefaultStyle = true;

    return renderScope;
}

And the CaretElement.CaretPaddingWidth definition in the CaretElement.cs file:

// Caret padding width to ensure the visible caret for Bidi and Italic.
// Control(TextBox/RichTextBox) must have the enough padding to display
// BiDi and Italic caret indicator.
internal const double CaretPaddingWidth = 5.0;

For more validity let look at the following three controls, TextBlock, TextBox and RichTextBox:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <TextBlock FontStyle="Italic" FontSize="48">LLL in TextBlock</TextBlock>
    <TextBox Grid.Row="1" FontStyle="Italic" FontSize="48" BorderThickness="0">LLL in TextBox</TextBox>        
    <RichTextBox Grid.Row="2" FontStyle="Italic" FontSize="48"  BorderThickness="0" Height="Auto"  >
        <FlowDocument PagePadding="0" >
            <Paragraph TextIndent="0">LLL in RichTextBox</Paragraph>
        </FlowDocument>
    </RichTextBox>
</Grid>

These controls, when using the same font size/style, padding and margin are displaying like on screenshot below:

enter image description here

As easy to see the TextBox also has some additional spacing that intended by displaying the caret indicator. But because of in the RichTextBox the caret indicator has more visual effects, more space is reserved for it.

The solution that worked for me is simply set <RichTextBox Padding="-5,0,0,0">, like proposed in post by Steve.

Jackdaw
  • 2,472
  • 2
  • 10
  • 24