In a post earlier this year, I investigated how to retrieve information about theme fonts in Windows. Briefly, the Visual Styles APIs can be used when visual styles are enabled, but values need to be hard-coded (to some extent) otherwise.
Andrew Powell commented on my previous post noting difficulties in implementing the GetThemeFont function in managed code. In this post, I’ll demonstrate how to implement the relevant functions in a simple WPF project. In particular, I’ll focus on displaying information about the ‘main instruction’ text style as seen in Task Dialogs.
Read on for details.
Part 1: Fallback Values
As noted last time, the Visual Styles APIs don’t work when visual styles are disabled.
The first step is to call the OpenThemeData function, which takes a window handle and a class name and returns a handle to theme data. If no match to the specified class name is found, NULL is returned. The class we will reference is TEXTSTYLE, which includes the TEXT_MAININSTRUCTION part. The parts and states for standard controls can be found here.
We need to use PInvoke to call OpenThemeData (and the associated CloseThemeData). As usual, PInvoke.net is very useful for getting PInvoke signatures.
1 2 |
[DllImport(“uxtheme.dll”, ExactSpelling = true, CharSet = CharSet.Unicode)] public static extern IntPtr OpenThemeData(IntPtr hWnd, String classList); |
1 2 |
[DllImport(“uxtheme.dll”, ExactSpelling = true)] public extern static Int32 CloseThemeData(IntPtr hTheme); |
1 |
IntPtr hTheme = OpenThemeData(IntPtr.Zero, “TEXTSTYLE”); |
If hTheme is NULL (that is, IntPtr.Zero), we can assume that visual styles are disabled (and in the event that this is not the case, we still won’t be able to use GetThemeFont and GetThemeColor). The fallback values that we should use are defined in AeroStyle.xml, which can be found in the Windows SDK.
1 2 3 4 5 6 7 8 9 10 11 |
<class name=“TextStyle”> <part name=“MainInstruction”> <caption> <TextColor>0 51 153</TextColor> <Font>Segoe UI, 12, Quality:ClearType</Font> <ClassicValue type=“TextColor”>WindowText</ClassicValue> <ClassicValue type=“Font”>CaptionFont</ClassicValue> </caption> </part> … </class> |
Fortunately, the SystemFonts and SystemColors classes in the System.Windows namespace allow easy access to these values:
1 2 3 4 5 6 7 8 9 10 11 12 |
FontFamily fontfamily; double fontsize; FontWeight fontweight; Brush foreground; if (hTheme == IntPtr.Zero) { fontfamily = SystemFonts.CaptionFontFamily; fontsize = SystemFonts.CaptionFontSize; fontweight = SystemFonts.CaptionFontWeight; foreground = SystemColors.WindowTextBrush; } else { ... } |
Part 2: GetThemeFont and GetThemeColor
In the case where visual styles are enabled, hTheme will hold a handle to theme data that we will pass to the GetThemeFont and GetThemeColor functions.
Firstly, the PInvoke signatures:
1 2 |
[DllImport(“uxtheme”, ExactSpelling = true, CharSet = CharSet.Unicode)] public extern static Int32 GetThemeFont(IntPtr hTheme, IntPtr hdc, int iPartId, int iStateId, int iPropId, out LOGFONT pFont); |
1 2 |
[DllImport(“uxtheme”, ExactSpelling = true)] public extern static Int32 GetThemeColor(IntPtr hTheme, int iPartId, int iStateId, int iPropId, out COLORREF pColor); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public struct LOGFONT { public const int LF_FACESIZE = 32; public int lfHeight; public int lfWidth; public int lfEscapement; public int lfOrientation; public int lfWeight; public byte lfItalic; public byte lfUnderline; public byte lfStrikeOut; public byte lfCharSet; public byte lfOutPrecision; public byte lfClipPrecision; public byte lfQuality; public byte lfPitchAndFamily; [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = LF_FACESIZE)] public string lfFaceName; } |
1 2 3 4 5 6 7 |
[StructLayout(LayoutKind.Sequential)] public struct COLORREF { public byte R; public byte G; public byte B; } |
Let’s start with GetThemeColor:
1 2 3 4 5 6 7 |
// from vsstyle.h int TEXT_MAININSTRUCTION = 1; // from vssym32.h int TMT_TEXTCOLOR = 3803; COLORREF pColor; GetThemeColor(hTheme, TEXT_MAININSTRUCTION, 0, TMT_TEXTCOLOR, out pColor); foreground = new SolidColorBrush(Color.FromRgb(pColor.R, pColor.G, pColor.B)); |
Now for GetThemeFont:
1 2 |
// from vssym32.h int TMT_FONT = 210; |
1 2 3 4 5 |
LOGFONT pFont; GetThemeFont(hTheme, IntPtr.Zero, TEXT_MAININSTRUCTION, 0, TMT_FONT, out pFont); fontfamily = new FontFamily(pFont.lfFaceName); fontsize = Math.Abs(pFont.lfHeight); fontweight = FontWeight.FromOpenTypeWeight(pFont.lfWeight); |
At this point, we’re finished, though we need to call CloseThemeData, preferably in a finally block.
Update (2011-10-25): Something I missed in the original implementation was that the lfHeight value represents the font height in pixels, which is a problem as WPF uses device-independent units (DIUs) for its measurements (something I’ve written about before). To properly support non-default DPI settings, the value in pixels should be converted to DIUs. I’ve added code to the sample project below that does this.
Download Sample
You can download a working sample here (updated 2011-10-25):
14,489 bytes; SHA-1: B01722E889780D94CDB26E31F115F4B2DF2E7111
Leave a Reply