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
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
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
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
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.