简体   繁体   中英

How to format a RichTextBox with existing contents

I'm getting data from a web API that returns text, and formatting information. The formatting data only includes the type of formatting (bold, italic, etc.) and the range of that formatting. The main problem with this is, that two ranges can "collide" (for example the first 3 characters of a word are bold and italic but the last 3 characters are only italic). Example response

{
  "text" : "This is an example text",
  "inlineStyles" : [
    {
      "offsetFromStart" : 5,
      "length" : 10,
      "type" : "bold"
    }
    {
      "offsetFromStart" : 10,
      "length" : 10,
      "type" : "italic"
    }
  ]
}

I already tried doing this with a simple TextBlock and failed. And I also tried this with a RichTextBox but when I added a Span I couldn't insert it into its original position. I also tought about formatting each character with its own span or run but that would be very ugly and in general just a bad solution. (My main concern is speed..)

var tb = new RichTextBox();
var para = new Paragraph();
para.Inlines.Add("This is an example text") // Text parsed from the response

var startingPointer1 = para.ContentStart.GetPositionAtOffset(5);
var sp1 = new Span(startingPointer1, startingPointer1.GetPositionAtOffset(10));
sp1.FontWeight = FontWeights.Bold;

var startingPointer2 = para.ContentStart.GetPositionAtOffset(10);
var sp2 = new Span(startingPointer2 , startingPointer2 .GetPositionAtOffset(10));
sp2.FontStyle= FontStyles.Italic;

para.Inlines.Add(sp1);
para.Inlines.Add(sp2);
tb.Document.Blocks.Add(para);

This code appends it to the end and when combining multiple inline elements like in my example it doesn't work at all (because of the first problem.)

Example result: 示例图片

I don't think you can overlap Runs/Spans like this, you'll have to find all the breaking points in your text and format each text range separately. It's similar to HTML, where

<bold>some<italic> bold italic</bold> and other </italic> text.

is not valid. In your case, you'll have a bold from (5,10), bolditalic from (11, 15) etc.

It's probably useful to find some kind of Range class with methods to combine ranges, split, find overlaps, etc. A while ago I started with this .

EDIT: I don't exactly have an idea how to implement all this (last time I did something similar was almost 10 years ago), but you can try something like this:

  1. Create a List<Range<int>> . Initially it contains a single Range(0, length of text).
  2. Load the first style, create a new Range with start/end offset. Overlap (or whatever method is appropriate) this range with the range in the list. This should give you 3 ranges, something like (0, start of style), (start of style, end of style), (end of style, end of text) . Remove old range from the list and add new ones.
  3. Load the next, find overlaps, with the ranges in the list, delete the ones that are overlapped and add new ranges.
  4. This should give you a list of nonoverlapping ranges.
  5. Now, for the styles. You can create a kind of stylesheet class. This class can use the FontWeights, FontStyles and other enums, defined in System.Windows . Modify a list, so that it contains, for example, List<Tuple<int, Stylesheet>> . To calculate overlaps just use the first param in the Tuple.
  6. Before you remove old ranges from the list, combine the styles.
  7. This should give you a list of nonoverlapped regions, with the appropriate styles. Create TextRange s, apply styles

Other idea that might work:

  1. Again, create a stylesheet. Initially it should be normal weighy, normal style, default font size etc.
  2. Find the next offset from the input (the first one that is larger than the current), create a TextRange and apply a style.
  3. Find the next offset from the input, modify current (and only) style and apply.

If I remember correctly, inserting style definition in the text also counts as characters, so you might need to adjust offsets when you insert style tags in the final text. Also, I believe it is doable just using TextBlock .

As I said, I don't know if this works like described, but this might give you and idea.

My current solution is that I go through every character one by one and scan through the ranges detecting if the current character is in any of them and then assigning a span to the character. This is not ideal at all, but it gets the job done. I'll try to implement an actual algorithm for this later. Until then, if you have any information that could help, please comment.

If anyone needs sample code of my current implementation I'd happily share it with you. (Even though it's not really efficient at all)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM