Recently I was faced with a task to draw multicolor string with wrapping (considering given width) on the control in WinForms
application. The first idea came to me was to perform custom drawing by overriding OnPaint method and using facilities provided by PaintEventArgs.Graphics property. So the assumption was that if I have a text and a list of regions (in the Start Index
:Length
format) with their respective colors in that text, I can simply draw multicolor string in the following way:
- Calculate box size for a given width using Graphics.MeasureString method.
- Combining StringFormat.SetMeasurableCharacterRanges and Graphics.MeasureCharacterRanges get coordinates of each colored region. (Note: Graphics.MeasureCharacterRanges can measure maximum 32 ranges, otherwise you will get
OverflowException
, so if you need to measure more than 32 ranges you should split this call in multiple passes). - Draw the same text with different colors in multiple passes using regions above for clipping.
And it worked, and the performance was acceptable, but... text had a different look in comparison with other controls, it was rendered uglier (but still acceptable though). If you don't care about this fact ("Hey, bro, you've got your text, what do you want?"), you can skip the rest of the post. But if you want an explanation, here it is. WinForms
is a pretty old framework which came with .NET
as a replacement of C++
-based MFC
. From the beginning it was packaged with GDI+
engine responsible for graphics. But GDI+
had poor performance and was not good choice for text rendering (especially for Unicode). So the decision was to use GDI
engine for text rendering, but Microsoft couldn't break backward compatibility and just replace, so this engine was added as alternative and the choice which one to use can be made by calling Application.SetCompatibleTextRenderingDefault (more on the change can be read here). MSDN
says, that Graphics class is a wrapper for GDI+
and TextRenderer is a wrapper for GDI
. But does TextRenderer
provides the same abilities for working with text as Graphics
? No, it lacks analogue of Graphics.MeasureCharacterRanges method. Moreover, it is using TextFormatFlags instead of StringFormat and no direct conversion between them exists. Fail. And don't try to perform measurement with Graphics
and draw with TextRenderer
since Graphics
measurement results will differ from TextRenderer
measurement. Ok, we already got a lot of pain, but how to overcome this issue if we want to draw multicolor string fast? Unfortunately, there is no an easy way. If you want to do it really fast, you have to dive into Win32 API
. GDI
resides mostly in gdi32.dll
and provides lot of methods which can be accessed using P/Invoke with an assistance of http://www.pinvoke.net/. Combining that you can write you own measurement and drawing mechanism, taking into account language specifics, but the downside of this solution is that you have to spend a lot of time.
Useful links:
- http://blogs.msdn.com/b/jfoscoding/archive/2005/10/13/480632.aspx
- http://stackoverflow.com/questions/1203087/why-is-graphics-measurestring-returning-a-higher-than-expected-number
- http://theartofdev.com/2013/08/12/the-wonders-of-text-rendering-and-gdi/
- http://theartofdev.com/2013/08/12/using-native-gdi-for-text-rendering-in-c/
- http://theartofdev.com/2014/04/21/text-rendering-methods-comparison-or-gdi-vs-gdi-revised/
- https://msdn.microsoft.com/en-us/magazine/cc751527.aspx