Well, I can offer a somewhat hacky way to do this.
First, note that UI elements can be placed in a FlowDocument . So this does something like this:
<RichTextBox> <FlowDocument> <Paragraph> <InlineUIContainer> <TextBlock>This is your label: </TextBlock> </InlineUIContainer> <Run>And this is the editable text.</Run> </Paragraph> </FlowDocument> </RichTextBox>
Now the problem is that the user does not edit InlineUIContainer . These are really two problems.
The first problem is the user's choice of user. To do this, you need to handle the SelectionChanged event. In the case, find the first InlineUIContainer in the RTB document, and if Selection.Start before that, change it.
private void RichTextBox_SelectionChanged(object sender, RoutedEventArgs e) { RichTextBox rtb = (RichTextBox) sender; if (rtb == null) return; InlineUIContainer c = rtb.Document .Blocks .Where(x => x is Paragraph) .Cast<Paragraph>() .SelectMany(x => x.Inlines) .Where(x => x is InlineUIContainer) .Cast<InlineUIContainer>() .FirstOrDefault(); if (c == null) return; if (rtb.Selection.Start.CompareTo(c.ElementEnd) < 0) { rtb.Selection.Select(c.ElementEnd, rtb.Selection.End); } }
Probably an easier way to formulate this LINQ query, but I kind of. And this is not 100% excellent; if you select inside the text and drag left over the TextBlock , it will lose the selection. I am sure that this can be fixed. But it works very well. It even handles the case when the user moves with arrows.
It just so happens that you are almost all the way. Another thing that can ruin you if the user positions the cursor at the very beginning of the text and presses BACKSPACE.
Processing requires something like this: compare the caret position with the end of the first InlineUIElement and cancel BACKSPACE (marking the event as processed) if the caret is in this position:
private void RichTextBox_PreviewKeyDown(object sender, KeyEventArgs e) { if (e.Key != Key.Back) { return; } RichTextBox rtb = (RichTextBox)sender; if (rtb == null) return; InlineUIContainer c = rtb.Document .Blocks .Where(x => x is Paragraph) .Cast<Paragraph>() .SelectMany(x => x.Inlines) .Where(x => x is InlineUIContainer) .Cast<InlineUIContainer>() .FirstOrDefault(); if (c == null) return; if (rtb.CaretPosition.CompareTo(c.ElementEnd.GetInsertionPosition(LogicalDirection.Forward)) <= 0) { e.Handled = true; } }