Windows XP Visual Styles (Themes) [How To / RANT]
Everyone who knows me will usually accuses me as being part of the 'collective'. They would have you believe that I think Microsoft can do no wrong. Well, here's my first official rant about how Microsoft can do something very, very, very wrong.
OK, great, they added EnableVisualStyles to .NET 1.1. Cool. But it has a couple of bugs (imagelists come to mind). Fine, I can work around those. Then comes the fact that visual styles take effect on the tab control... but wait, it doesn't paint the inner portion of the tab with the slight gradient that you would expect. There's no built in way to correct this; unless you consider that you can sort of fake it by just setting the backcolor of the tab area to white. That's not too bad, but now for the problem. What if someone turns off themes?
I can use IsThemeActive from uxtheme.dll that would tell me whether or not visual styles is enabled. But prior to that, I need to test that the OS version is greater than 5.1 and do a LoadLibrary to test that the uxtheme.dll exists. Not a problem; pretty simple really. So great, I can determine if Windows is themed. But, what if I didn't use EnableVisualStyles()? My application is still using the previous common control library. Then comes in the IsAppThemed() method in uxtheme.dll. Sounds like it's the one I want... but it appears to always return true when testing under WindowsXP that has themes enabled and no EnableVisualStyles() being used. If the application is not using visual styles, why does this function return true? Grrrr.
So what exactly does EnableVisualStyles do? Is there a way to determine from what it does whether visual styles are enabled for the running application? Digging out Reflector and looking at the IL for EnableVisualStyles, it pretty much translates to the following VB.NET code:
Public Shared Sub EnableVisualStyles()
useVisualStyles = True
Further inspection finds that useVisualStyles is a private variable. I'm guessing that the other methods use this private shared variable to then react accordingly when drawing the controls.
So Microsoft kinda forgot to implement the correct drawing behavior of the tab control. I can forgive that. But WHY is there no way to see if your application has called upon EnableVisualStyles? How hard would it have been to make a read-only property that you could look at? Why is this a problem. Well, what if you make a reusable control that you want to have drawn differently depending on whether or not visual styles was enabled. This control has no way of knowing whether EnableVisualStyles was called upon. Grrr... sometimes developers (yes, even Microsoft) can be so short sighted.
As it stands, I still can't seem to find a solution for this. As it stands, here's a fragment that will do most of the work. Of course, you still have to know yourself if EnableVisualStyles() (or the .manifest file exists); knowing this as a limitation, here's the code:
Private Declare Function LoadLibrary Lib "kernel32.dll" Alias "LoadLibraryA" (ByVal path As String) As IntPtr
Private Declare Function GetProcAddress Lib "kernel32.dll" (ByVal library As IntPtr, ByVal procName As String) As IntPtr
Private Declare Function FreeLibrary Lib "kernel32.dll" (ByVal library As IntPtr) As Boolean
Private Declare Function IsThemeActive Lib "uxtheme.dll" () AsBoolean
Private Declare Function IsAppThemed Lib "uxtheme.dll" () AsBoolean
Private Function XPThemesEnabled() As Boolean
Dim os As OperatingSystem = System.Environment.OSVersion
If os.Platform = PlatformID.Win32NT _
AndAlso (((os.Version.Major = 5) AndAlso (os.Version.Minor >= 1)) _
OrElse (os.Version.Major > 5)) Then
Dim uxTheme As IntPtr = LoadLibrary("uxtheme.dll")
If Not uxTheme.Equals(IntPtr.Zero) Then
Dim handle As IntPtr = GetProcAddress(uxTheme, "IsThemeActive")
If handle.Equals(IntPtr.Zero) Then
' an error occurred, use GetLastError
If IsThemeActive() Then
' an error occurred, use GetLastError