For several days, I looked for hooks into the Windows Presentation Foundation (WPF) System.Windows.Controls.TextBox control to display shadowed text:
WPF does not make the shadow effect easy. Changing the Foreground property can change the text color to any brush (even to a video or an image brush), but it does not achieve the shadow effect.
Using Snoop, I looked at the visual composition of a TextBox:
The TextBoxView control shown in Snoop does not have a public API. Lester Lobo says in this WPF forum post, “It is an internal lightweight class handling the rendering of content in the textbox.” If you re-templated a TextBox to try to replace the TextBoxView, you would have to implement all of the text-management functions provided by a TextBox—no small undertaking.
I then found a blog entry by Ken Johnson on CodeProject.com describing a clever hack for tricking a TextBox into doing what I wanted. His article is called CodeBox 2, and it uses the following algorithm:
- Derive a class from TextBox.
- Set the Foreground property to Brushes.Transparent. This hides the TextBox’s built-in text rendering.
- Override OnRender().
- Create a FormattedText object with the TextBox.Text string and set some properties to make it align and wrap just like the TextBoxView would normally do.
- Call DrawingContext.DrawText() to output the FormattedText such that it draws in the exact same position as the TextBoxView would normally do. To achieve the shadowed text effect, first use DrawingContext.DrawText() to draw the shadow offset a couple of units from the primary text, and then call it again to draw the primary text.
- What you now see is your shadowed text in a fully-functional TextBox. If you highlight text, you are actually interacting with the transparent text that is drawn in the TextBox.
It may be a hack, but it’s a clever hack, and aside from some scrolling and margin adjustments described well in Ken Johnson’s article and code, you can implement it relatively painlessly.
WPF’s in-depth text formatting features pay a price in CPU performance. This custom TextBox pays an even bigger price. First, drawing transparent text likely goes through all the formatting and rendering steps for no obvious visual effect. The text formatting likely provides some hit-testing and/or control sizing (layout) features even when formatting invisible text. Second, in addition to drawing transparent text, constructing a new FormattedText object and calling DrawingContext.DrawText() twice (once for the shadow and once for the primary text) adds overhead.
While I have not tested performance of my shadowed-text custom TextBox as shown in the screenshots above, I imagine that it will perform well enough on mid-level computers even with large amounts of text. However, if your application used these shadowed text effects in many places, the accumulation of CPU overhead would likely add up to significant performance problems.
I would appreciate any comments about other ways to draw shadowed text in a TextBox or to gain hooks into TextBox’s inner workings.