Read-only Run elements in a WPF RichTextBox?
Asked Answered
G

2

11

I may be completely imagining this, but I could have sworn there was a way to make individual Run (or Parapgraph) elements in a RichTextBox read-only. I also could have sworn I tried a method for doing this out myself a few weeks ago and was satisfied with the results - I vaguely remember it looked something like this:

<RichTextBox x:Name="richTextBox"
             AcceptsTab="True"
             AcceptsReturn="True"
             FontFamily="Courier New"
             FontSize="14">
    <FlowDocument>
        <Paragraph>
            <Run IsReadOnly="True">I wish this was read-only!</Run>
        </Paragraph>
    </FlowDocument>
</RichTextBox>

Now, a few weeks later, I go to try to make Run elements read-only in a RichTextBox only to find it doesn't seem to be possible.

This post on the MSDN forums seems to confirm that.

Did I completely imagine this? Or is there a way to do what I want to do?

Gigantism answered 5/7, 2009 at 17:40 Comment(0)
G
9

Alright, I've come up with a solution that works for my case - but may not work for everyone who wants something like this. It's messy, but it does the job.

I'm not going to accept my own answer for a few days, just in case someone else has a better way of accomplishing this.

Here we go, first, the XAML:

<Window x:Class="WpfApplication1.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1"
        Height="500"
        Width="600">
    <DockPanel LastChildFill="True">
        <RichTextBox x:Name="rtb"
                     FontFamily="Courier New"
                     FontSize="14"
                     PreviewKeyDown="rtb_PreviewKeyDown">
            <FlowDocument>
                <Paragraph>
                    <InlineUIContainer Unloaded="InlineUIContainer_Unloaded">
                        <TextBlock FontFamily="Courier New" FontSize="14">This line of text is not editable.</TextBlock>
                    </InlineUIContainer>
                    <Run Foreground="Blue">But this is editable.</Run>
                </Paragraph>
            </FlowDocument>
        </RichTextBox>
    </DockPanel>
</Window>

And the code behind:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;

namespace WpfApplication1
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        private void InlineUIContainer_Unloaded(object sender, RoutedEventArgs e)
        {
            (sender as InlineUIContainer).Unloaded -= new RoutedEventHandler(InlineUIContainer_Unloaded);

            TextBlock tb = new TextBlock();
            tb.FontFamily = new FontFamily("Courier New");
            tb.FontSize = 14;
            tb.Text = "This line of text is not editable.";

            TextPointer tp = rtb.CaretPosition.GetInsertionPosition(LogicalDirection.Forward);
            InlineUIContainer iuic = new InlineUIContainer(tb, tp);
            iuic.Unloaded += new RoutedEventHandler(InlineUIContainer_Unloaded);
        }

        private void rtb_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                var newPointer = rtb.Selection.Start.InsertLineBreak();
                rtb.Selection.Select(newPointer, newPointer);

                e.Handled = true;
            }
        }
    }
}

My solution relies on the fact that when an InlineUIContainer is removed from the UI, it's Unloaded() method is called. At that point, I simply reinsert the deleted InlineUIContainer at the current caret position.

As with any hack, there are a bunch of disadvantages. The disadvantages I'm finding are the following:

  • The text I want to be read-only needs to be wrapped in a InlineUIContainer. That is a little limiting for this solution.
  • I have to capture the 'Enter' key and insert line breaks manually, otherwise, InlineUIContainer.Unloaded() keeps firing everytime the Enter key is pressed. Not fun, but it works for my case.

It's not a great solution, but I think it will work for me. Like I said, I'm not going to mark this as an answer to my own question yet - hopefully someone else will have a better way of doing this.

Gigantism answered 5/7, 2009 at 19:35 Comment(2)
Very interesting it will help me add "widgets" if you will into the text editor. I'm making a very nice windows like Wiki editor. So for adding images, categories, and keywords I'm using small objects that decorate the rich text object. I wish the editor was a little more like the visual studio editor. This way I could add "Adornments" but... this definitively helped me! Thanks.Predestine
Glad it could help - be warned, though - I vaguely remember it being possible to delete read-only text with the backspace key. I'm not sure after all of this time, but I'd test it thoroughly to ensure the widgets remain read-only.Gigantism
O
0

This can be achieved by handling two event: 1) OnMouseDown 2) OnPreviewKeyDown

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Input;

namespace WpfApplication2
{
public class MultiPartTextBox : TextBox
{
    private string _prefix;
    private string _content;

    public string Prefix
    {
        get { return _prefix; }
        set { _prefix = value;
        Text = _prefix;
        }
    }

    public string Content
    {
        get { return _content; }
        set { 
            _content = value;
            Text = _prefix + _content;
        }
    }

    public MultiPartTextBox() { _prefix = string.Empty; }

    protected override void OnMouseDown(MouseButtonEventArgs e)
    {

        base.OnMouseDown(e);
        this.SelectionStart = _prefix.Length;
        this.SelectionLength = 0;
    }

    //tab In
    protected override void OnGotFocus(RoutedEventArgs e)
    {
        this.SelectionStart = _prefix.Length;
        this.SelectionLength = 0;
        base.OnGotFocus(e);
    }

    protected override void OnPreviewKeyDown(KeyEventArgs e)
    {
        if (e.Key == Key.Back 
            || e.Key == Key.Delete
            || e.Key==Key.Left)
        {
            if (CaretIndex <= _prefix.Length)
            {
                e.Handled = true;
                return;
            }
        }
        base.OnPreviewKeyDown(e);
    }
  }
  }

IN Xaml we have to handle it in following way:

   xmlns:uc="clr-namespace:WpfApplication2"

       <uc:MultiPartTextBox Height="30" HorizontalAlignment="Left" 
             Margin="80,94,0,0" x:Name="multiPartTxt1" VerticalAlignment="Top" 
             Width="224" Prefix="NON-EDITABLE" CaretIndex="4" >            
       </uc:MultiPartTextBox>
Oneiromancy answered 8/12, 2012 at 5:13 Comment(1)
You forgot about finger touches.Bullheaded

© 2022 - 2024 — McMap. All rights reserved.