In several of my applications, I need to be able to line up text, be it blocks of text using different fonts, or text containers of differing heights. As far as I'm aware, there isn't a way of doing this natively in .NET, however with a little platform invoke we can get the information we need to do it ourselves.
Although there's a lot of information available (as you can see
in the demonstration program), for the most part I tend to use
tmAscent value which returns the pixels above the
base line of characters.
A quick note on leaks
I don't know how relevant clean up is in modern versions of Windows, but in older versions of Windows it used to be very important to clean up behind you. If you get a handle to something, release it when you're done. If you create a GDI object, delete it when you're done. If you select GDI objects into a DC, store and restore the original objects when you're done. Not doing these actions used to be a good source of leaks. I don't use GDI anywhere near as much as I used to years ago as a VB6 developer, but I assume the principles still apply even in the latest versions of Windows.
GetTextMetrics is a Win32 GDI API call, it requires a
device context, which is basically a bunch of graphical objects
such as pens, brushes - and fonts. Generally you would use the
CreateDC API calls, but fortunately the .NET
Graphics object is essentially a wrapper around a device
context, so we can use this.
A DC can only have one object of a specific type activate at a
time. For example, in order to draw a line, you need to tell the
DC the handle of the pen to draw with. When you do this, Windows
will tell you the handle of the pen that was originally in the
DC. After you have finished drawing your line, it is up to you
to both restore the state of the DC, and to destroy your pen.
The GDI calls
DeleteObject can do
The following helper functions can be used to get the font ascent, either for the specified
Control or for a
I haven't tested the performance of using
Control.CreateGraphicsversus directly creating a DC. If you are calling this functionality a lot it may be worth caching the values or avoiding
CreateGraphicsand trying pure Win32 API calls.
In the above code you can see how we first get the handle of the
underlying device context by calling
GetDC. This essentially
locks the device context, as in the same way that only a single
GDI object of each type can be associated with a GDI, only one
thread can use the DC at a time. (It's little more complicated
than that, but this will suffice for this post).
Next, we convert the managed .NET
Font into an unmanaged
You are responsible for deleting the handle returned by
Once we have our font handle, we set that to be the current font
of the device context using
SelectObject, which returns the
existing font handle - we store this for later.
Now we can call
GetTextMetrics passing in the handle of the
DC, and a
TEXTMETRIC instance to populate. Note that the
GetTextMetrics call could fail, and if so the function call
will return false. In this demonstration code, I'm not checking
for success or failure and assuming the call will always
Once we've called
GetTextMetrics, it's time to reverse some of
the steps we did earlier.
Note the use of a finally block, so even if a crash occurs during processing, our clean up operations will still get called
First we restore the original font handle that we obtained from
the first call to
Now it's safe to delete our
HFONT - so we do that with
It's important to do these steps in order - deleting the handle to a GDI object that is currently associated with a device context isn't a great idea!
Finally, we release the DC handle we created earlier via
And that's pretty much all there is to it - we've got our font ascent, cleaned up everything behind us and can now get on with the whatever purpose we needed that value for!
What about the other information?
The example code above focuses on the
tmAscent value as this
is mostly what I use. However, you could adapt the function to
TEXTMETRICW structure directly, or to populate a
more .NET friendly object using .NET naming conventions and
converting things like
tmPitchAndFamily to friendly enums etc.
- 2016-07-09 - First published
- 2020-11-21 - Updated formatting
Like what you're reading? Perhaps you like to buy us a coffee?