Read-only Run elements in a WPF RichTextBox? Read-only Run elements in a WPF RichTextBox? wpf wpf

Read-only Run elements in a WPF RichTextBox?


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.


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>