See also: Windows 8 Theme Colours Reference.
Windows 8 doesn’t offer developers access to system theme colours, unlike its cousin, Windows Phone. Even for a version 1 product, this seems like a strange omission. Then again, we still don’t have a working public API for retrieving the Aero glass colour (or whatever we call it now that Aero’s gone) 6 years after the release of Windows Vista.
The functions that the system uses to retrieve colours are defined in UxTheme.dll. In particular, we’re interested in GetImmersiveColorSetCount (export #94), GetImmersiveColorFromColorSetEx (export #95), GetImmersiveColorTypeFromName (export #96), GetImmersiveUserColorSetPreference (export #98) and GetImmersiveColorNamedTypeByIndex (export #100). Relying on undocumented functions is a bad idea, and will cause your program to fail certification, so you won’t be able to use them in apps distributed through the Windows Store anyway.
For desktop developers who still want to use these functions, read on. Just assume that they’ll break in future versions of Windows (or even with patches to Windows 8).
There are 25 colour sets available in Windows 8, pictured above. Users can switch between these in the Metro control panel (under Personalise > Start screen). Additionally, when a high contrast theme is enabled, colours in the start screen are based on traditional window element colours, retrieved with the GetSysColor function. With the four high contrast themes included in Windows 8, this means there are effectively 29 different colour sets for a standard setup. Use the GetImmersiveColorSetCount function to retrieve the number of colour sets available and the GetImmersiveUserColorSetPreference function to retrieve the ID of the active colour set. The ID is also stored in the registry key HKCUSoftwareMicrosoftWindowsCurrentVersionExplorerAccent with the name ColorSet_Version3. It occurred to me that one could hard-code values from the 25 colour schemes and read this registry value, but Metro-style apps aren’t even allowed to access the registry – so much for that idea.
The GetImmersiveUserColorSetPreference functions takes two boolean parameters. The first should be set to true to force UxTheme to read the value stored in the registry (and update the system setting if what’s in the registry is different to what’s in memory). To be honest, I’m not entirely sure what the second parameter does – setting it to true will stop the function attempting to retrieve the user preference a second time if the first call returns –1. I think this is only relevant in the event that UxTheme doesn’t have permission to update the system setting with the value from the registry. In any case, you should be able to disregard these parameters in most scenarios (set them both to false).
The GetImmersiveColorNamedTypeByIndex function returns a (pointer to a) string containing the name of an element like ‘StartBackground’ or ‘StartDesktopTilesBackground’. There are 767 (0x2ff) names in total – unfortunately there seems to be no function to retrieve this number. To get the colour type (ID) from a name, we use the GetImmersiveColorTypeFromName function. We must prepend ‘Immersive’ to the string first, however, or the function will fail (‘StartBackground’ becomes ‘ImmersiveStartBackground’, for example). (In fact, any 9 characters at the start will do – ‘xxxxxxxxxStartBackground’ will work just as well.)
GetImmersiveColorTypeFromName returns a number between 0 and 766 (0x2fe). Only the first 108 types (0 to 107) correspond to colours that change according to the colour set – the remaining 659 are static (unless high contrast mode is enabled).
GetImmersiveColorFromColorSetEx takes four parameters. The first is the colour set ID (between 0 and 24 for Windows 8) and the second is the colour type (e.g. 0 for ‘StartBackground’). If the third parameter determines whether high contrast mode should be ignored – set it to 1 to retrieve the active colour set’s colours even when high contrast mode is enabled. The fourth parameter can be set to 1 to force UxTheme to check whether the system is in high contrast mode even with it already thinks it is (this check would otherwise only occur if high contrast mode had previously not been enabled).
Detecting User Preference Updates
Listen for WM_SETTINGCHANGE to detect when the user changes the colour scheme. lParam will point to a string with the value ‘ImmersiveColorSet’. When high contrast mode is enabled or disabled, lParam will point to a string with the value ‘WindowsThemeElement’.
P/Invoke Signatures
These P/Invoke signatures should be of help to C# developers wishing to use these functions in desktop programs (again, assume they could stop working at any time):
1 2 |
[DllImport(“uxtheme.dll”, EntryPoint = “#94”)] public static extern int GetImmersiveColorSetCount(); |
1 2 3 4 5 6 7 8 9 10 |
/// <summary> /// Retrieves an immersive colour from the specified colour set. /// </summary> /// <param name=”dwImmersiveColorSet”>Colour set index. Use <see cref=”GetImmersiveColorSetCount”/> to get the number of colour sets available.</param> /// <param name=”dwImmersiveColorType”>The colour type. Use <see cref=”GetImmersiveColorTypeFromName”/> to get the type from an element name.</param> /// <param name=”bIgnoreHighContrast”>Set this to true to return colours from the current colour set, even if a high contrast theme is being used.</param> /// <param name=”dwHighContrastCacheMode”>Set this to 1 to force UxTheme to check whether high contrast mode is enabled. If this is set to 0, UxTheme will only perform this check if high contrast mode is currently disabled.</param> /// <returns>Returns a colour (0xAABBGGRR).</returns> [DllImport(“uxtheme.dll”, EntryPoint = “#95”)] public static extern uint GetImmersiveColorFromColorSetEx(uint dwImmersiveColorSet, uint dwImmersiveColorType, bool bIgnoreHighContrast, uint dwHighContrastCacheMode); |
1 2 3 4 5 6 7 |
/// <summary> /// Retrieves an immersive colour type given its name. /// </summary> /// <param name=”pName”>Pointer to a string containing the name preprended with 9 characters (e.g. “Immersive” + name).</param> /// <returns>Colour type.</returns> [DllImport(“uxtheme.dll”, EntryPoint = “#96”)] public static extern uint GetImmersiveColorTypeFromName(IntPtr pName); |
1 2 3 4 5 6 7 8 |
/// <summary> /// Gets the user’s colour set preference (or default colour set if the user isn’t allowed to modify this setting according to group policy). /// </summary> /// <param name=”bForceCheckRegistry”>Forces update from registry (HKCUSoftwareMicrosoftWindowsCurrentVersionExplorerAccentColorSet_Version3).</param> /// <param name=”bSkipCheckOnFail”>Skip second check if first result is -1.</param> /// <returns>User colour set preference.</returns> [DllImport(“uxtheme.dll”, EntryPoint = “#98”)] public static extern int GetImmersiveUserColorSetPreference(bool bForceCheckRegistry, bool bSkipCheckOnFail); |
1 2 3 4 5 6 7 |
/// <summary> /// Retrieves names of colour types by index. /// </summary> /// <param name=”dwIndex”>Colour type index (0 to 766/0x2fe).</param> /// <returns>Pointer to a string containing the colour type’s name.</returns> [DllImport(“uxtheme.dll”, EntryPoint = “#100”)] public static extern IntPtr GetImmersiveColorNamedTypeByIndex(uint dwIndex); |
Sample Code (C#)
The following code retrieves the ‘StartSelectionBackground’ colour from the active colour set (assuming the above P/Invoke signatures).
1 2 3 4 5 6 7 8 9 10 11 |
int colourset = Colour.GetImmersiveUserColorSetPreference(false, false); IntPtr pElementName = Marshal.StringToHGlobalUni(“ImmersiveStartSelectionBackground”); uint type = Colour.GetImmersiveColorTypeFromName(pElementName); Marshal.FreeCoTaskMem(pElementName); uint colourdword = Colour.GetImmersiveColorFromColorSetEx((uint)colourset, type, false, 0); byte[] colourbytes = new byte[4]; colourbytes[0] = (byte)((0xFF000000 & colourdword) >> 24); // A colourbytes[1] = (byte)((0x00FF0000 & colourdword) >> 16); // B colourbytes[2] = (byte)((0x0000FF00 & colourdword) >> 8); // G colourbytes[3] = (byte)(0x000000FF & colourdword); // R Color colour = Color.FromArgb(colourbytes[0], colourbytes[3], colourbytes[2], colourbytes[1]); |
Leave a Reply