A while back I answered this question on stackoverflow and have been meaning to elaborate on my answer ever since with a more comprehensive blog post. The question is about how to highlight some text in the UI when the user enters some search text. I thought I’d extend the concept into a clean, generic solution as far as I could and share it here.
My requirements are pretty straightforward:
- Provide a search box in which the user can enter some text.
- Highlight all matching text in the window, regardless of where it appears in the visual tree.
I was able to pull this off to an extent I am satisfied with. See the screenshot to the left. The user can enter some text in the top-right search text box, and then hit enter (or click the magnifying glass). Then all matching text is highlighted anywhere in the visual tree.
After an initial approach that relied on reflection in order to highlight content elements (ugh!), Marlon Grech kindly passed my question about how to do this more cleanly onto Dr. WPF and Jamie Rodriguez, who pointed me in the right direction (thanks guys!).
The basic approach I ended up using is:
- Traverse the visual tree recursively, looking for IContentHosts and DocumentViewerBases. For DocumentViewerBases, extract the IContentHosts corresponding to each page.
- For each IContentHost, search through its HostedElements collection to find any Runs.
- For each Run, inspect its Text to determine whether the search term matches.
- If the search term matches, use some TextPointer trickery to determine the bounding rectangle of the text.
- Use a Canvas to lay out all semi-transparent rectangles on top of the application window, thus highlighting the matches.
There are a few problems that I’m aware of:
- If the matching text spans multiple lines, the bounding rectangle isn’t correctly calculated. You could use IContentHost.GetRectangles to calculate all bounding rectangles correctly, but I just haven’t bothered to do so for the purposes of this post.
- It doesn’t work for all content element hosts, such as FlowDocumentScrollViewer, because they don’t inherit from DocumentViewerBase. I suspect it’s probably not too hard to add support for them, but again I haven’t bothered to do so for this example.
- It’s quite slow when the search term is short and frequently matched (as an example, try searching for “a”).
So the approach isn’t perfect, but this was an experiment more than anything else. I think a real application would want search support more intrinsic rather than relying on a generic mechanism like this. That said, there are bound to be some practical applications of the general technique used here.
PS. You may have noticed a lack of TMNT-related material in my sample this post. Unfortunately, my Windows Home Server recently died, which has prevented me from continuing to watch the series and has let my obsession waver somewhat. A replacement server is due next week, so expect more green reptile-based content soon!