Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF: How to make RichTextBox look like TextBlock?

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.

like image 639
Rasto Avatar asked Apr 28 '11 14:04

Rasto


2 Answers

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

like image 200
Markus Hütter Avatar answered Oct 17 '22 16:10

Markus Hütter


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;
    }
}
like image 3
Daniel Rose Avatar answered Oct 17 '22 14:10

Daniel Rose