August 2008 - Posts

Good Old GDI+ (or: Unblur Thy Text)

It's been a while since I've done anything with GDI+ (i.e. System.Drawing). System.Windows (i.e. WPF) is so much more powerful.

However, there's one area where it seems the good old GDI+ can still surpass it's shiny new successor: text.

There have been many complaints about text rendering in WPF. I wrote about some of them myself. You can find a lot of questions on the WPF forum (to name a few), but not a lot of answers.

The main issue is that small text is blurry and unreadable. When asked how to render aliased text in WPF, the answer was: it's impossible. However, I recently encountered a whitepaper on windowsclient.net called "Text Clarity in WPF", which suggests a way to do just that. It's worth a read, even though the aliased text solution provided there is not usable (they use FormattedText, convert it to a Geometry and render the Geometry aliased using RenderOptions.SetEdgeMode() - which indeed renders aliased text, but it's not quite legible.)

As you may have already guessed, my solution - GdiTextBlock - relies on GDI+. We render a bitmap that contains the text and display it. As you may know, rendering bitmaps in WPF also has it's problems, which is why I use the Bitmap control in order to prevent bitmaps from becoming blurry as well.

Also, in order to maintain compatibility with WPF's TextBlock, and to avoid forcing the consumers of the control to add a reference to the System.Drawing assembly, the properties and their types are all the same as TextBlock's (in fact, the dependency properties are registered using DependencyProperty.AddOwner(), which also ensures that value inheritance down the visual tree works.) CoerceValueCallback is used in many places since GDI+ doesn't support all the options available in WPF (e.g. FontStyles.Oblique, non-solid Foreground, TextAlignment.Justify.) While I've included a property called TextQuality, which allows you to set the TextRenderingHint, you'll see that only the default (SingleBitPerPixelGridFit) provides legible results.

One last note about performance - this solution is not quite optimal. It creates a bitmap every measure pass. I haven't done any real performance tests, so I can't tell you what the real penalty is. Use it at your own discretion.

Update: Fixed a small bug and added size and color selectors to demo app.

Another Update: Fixed a leak when using InteropBitmap. See this post.

Posted by aelij
Attachment: GdiTextBlock.zip
with 1 comment(s)
Filed under: ,

Writing Methods and Classes in LINQPad

LINQPad is a very useful code snippet IDE. I use it all the time to test small pieces of code. It's much more convenient than opening a new Visual Studio console application. It also formats the results very nicely.

What seems to be missing in LINQPad is a way to add methods or classes. After digging a bit with reflector, I came up with a simple way:

  • Choose "C# Statement(s)" in the Type drop-down.
  • Write the code that LINQPad should execute.
  • Put a closing curly-bracket ("}") at the end.
  • Write as many classes and methods as you like.
  • On the last class/method, omit closing curly-bracket.

LINQPad simply compiles the code you're writing there, wrapping it in a class and a single method, which it then executes. By adding the closing bracket, we're actually ending the method it defines, and start writing our own methods and classes. Since a closing bracket will be added at the end of the code block, we omit it from our code.

If you want your classes to be proper (not inner), you will need to put two closing curly-brackets, and then define your class. This is important if you want to define extension methods (which must reside in a static, non-inner class).

Posted by aelij with no comments
Filed under: ,
More Posts