1

I'm rendering text using FormattedText, but there does appear to be any way to perform per-char hit testing on the rendered output. It's read-only, so I basically only need selection, no editing.

I'd use RichTextBox or similar, but I need to output text based on control codes embed in the text itself, so they don't always nest, which makes building the right Inline elements very complex. I'm also a bit worried about performance with that solution; I have a large number of lines, and new lines are appended often.

I've looked at GlyphRun, it appears I could get hit-testing from it or a related class, but I'd be reimplementing a lot of functionality, and it seems like there should be a simpler way...

Does anyone know of a good way to implement this?

Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72
Eric
  • 851
  • 7
  • 16

4 Answers4

5

You can get the geometry of each character from a FormattedText object and use the bounds of each character to do your hit testing.

var geometry = (GeometryGroup)((GeometryGroup)text.BuildGeometry(new Point(0, 0))).Children[0];
foreach (var c in geometry.Children)
{
  if (c.Bounds.Contains(point))
    return index;
  index++;
}

In OnRender you can render these geometry objects instead of the formatted text.

justin.m.chase
  • 13,061
  • 8
  • 52
  • 100
  • You're a life saver for pointing this out. I was beginning to pull my hair out thinking of how to work around not having this. – John Mar 13 '14 at 20:25
  • I think there is an issue with this solution. There is no geometry for the space characters, so there are effectively 'holes' in the geometry, and the index is off by the number of spaces that have appeared before the hit point. – LineloDude May 28 '14 at 22:06
  • I think you just want to get the bounds of the geometry not use the actual geometry itself. – justin.m.chase May 29 '14 at 04:09
3

The best way is to design a good data structure for storing your text and which also considers hit-testing. One example could be to split the text into blocks (words, lines or paragraphs depending on what you need). Then each such block should have a bounding-box which should be recomputed in any formatting operations. Also consider caret positions in your design.

Once you have such facility it becomes very easy to do hit-testing, just use the bounding boxes. It will also help in subsequent operations like highlighting a particular portion of text.

Sesh
  • 5,993
  • 4
  • 30
  • 39
  • FormattedText already allows me to get boxes for ranges of text, but I need actual selection; so I'd probably need boxes for each char, and that'd quickly get messy. It's a possible solution, but not a very good one... – Eric Feb 12 '09 at 05:05
  • There is a fly-weight or some other design pattern that addresses similar problem (bbox for single char). Can you please check the Gamma book on design patterns? its there in it. – Sesh Feb 12 '09 at 05:14
1

I'm very late to the party--if the party is not over, and you don't need the actual character geometry, I found something like this useful:

 for (int i = 0; i < FormattedText.Text.Length; i++)
 {
            characterHighlightGeometry = FormattedText.BuildHighlightGeometry(new Point(), i, 1);
            CharacterHighlightGeometries.Children.Add(characterHighlightGeometry);
 }

BuildGeometry() only includes the actual path geometry of a character. BuildHighlightGeometry() generates the outer bounds of all characters--including spaces, so an index to a space can be located by:

 foreach (var c in CharacterHighlightGeometries.Children)
        {
            if (c.Bounds.Contains(centerpoint))
            {
                q = c;
                cpos = index;
                break;
            }
            index++;
        }

Hope this helps.

Alan Wayne
  • 5,122
  • 10
  • 52
  • 95
1

Completely agree with Sesh - the easiest way you're going to get away with not re-implementing a whole load of FormattedText functionality is going to be by splitting up the individual items you want to hit-test into their own controls/inlines.

Consider using a TextBlock and adding each word as it's own Inline ( or ), then either bind to the inline's IsMouseDirectlyOver property, our add delegates to the MouseEnter & MouseLeave events.

If you want to do pixel-level hit testing of the actual glyphs (i.e. is the mouse exactly in the dot of this 'i'), then you'll need to use GlyphRuns and do manual hit testing on the glyphs (read: hard work).

Mark
  • 9,320
  • 6
  • 57
  • 70