LMD 2007 - Theme Engine
This document is related to LMD 2007 packages only. In LMD 2009 series theme engine was replaced by a much more flexible approach, please check: LMD 2009 Compiler switches
This text describes use of standalone theme engine in LMD 2k7 packages. The engine allows an application to load and apply visual styles defined by msstyles file and some customizations such as controlling how readonly & disable states are rendered. The engine makes it possible to use visual styles in applications running under older OS (Windows 95-98, Windows 2000).
For detailed description check corresponding section in Compiler switches document.
Theme engine consists of one unit from rtl packages (
LMDXPStyles.pas) and eleven units from rtlx package.
LMDXPStyles.pasdefines theme related constants, types and pointers to theme engine routines.
LMDThemes.pasdefines facade class TLMDThemeEngine and helper class TLMDThemeEngineController. If LMDDISABLE_LMDTHEMEENGINE is undefined, i.e. using theme engine is allowed, then an instance of TLMDThemeEngine class is created in initialization section of the unit. Creating this instance initializes theme engine and reassigns procedural variables of LMDXPStyles unit so that calls to system theme routines are redirected to standalone theme engine routines.
LMDFormThemeProvider.pasdefines TLMDFormThemeProvider class.
LMDThemesGeneral.pasholds ancestor classes TTheme, TThemePart and TThemeManager which define theme tree-like framework.
LMDThemesGraphics.pasdefines several drawing routines.
LMDThemesMain.pasholds all necessary theme related routines that are actually called if standalone theme engine is used.
LMDThemesWindowsXP.pasdefines classes TWindowsXPTheme, TWindowsXPThemePart and TWindowsXPThemeManager (descendants of TTheme, TThemePart and TThemeManager) which implement details specific to XP theming: parsing msstyles file, general drawing routines, metrics routines. These classes are initialized when msstyles file is parsed and hold all necessary data to render XP themed controls.
LMDThemesWindowsXPConsts.passpecifies XP theme constants.
LMDThemesWindowsXPParts.pasdefines classes that specify themed rendering details for controls.
LMDThemesWindowsXPTypes.pasdefines XP theme specific enumeration types.
LMDThemesWindowsXPUtils.pasholds helper converting routines.
The theme engine framework is defined by three main classes TThemeManager, TTheme and TThemePart (LMDThemesGeneral unit). ThemeManager mantains a list of Themes, each of which is actually a root of tree of theme parts.
- TThemeManager - abstract generic class, corresponds to a generic visual style.
- TTheme - generic class, corresponds to a generic control theme.
- TThemePart - abstract generic class, corresponds to a generic theme for a control part.
One can access theme engine by means of two classes, TLMDThemeEngine and TLMDThemeEngineController:
- TLMDThemeEngine - a facade class, provides a single access point to theme engine interface. A single instance of this class is created when LMDThemes unit is initialized. One can access theme engine properties and methods via LMDThemeEngine instance at runtime. By several reasons this class, though is a descendant of TLMDCustomComponent, is not placed on component palette. Main reasons are:
- If theme engine is turned on, one single instance of TLMDThemeEngine must be created at runtime even if no instances were placed on the form at design time.
- If instances of TLMDThemeEngine were allowed to be created at design time, then standard notification mechanism would have had to be implemented, which is unnecessary.
- TLMDThemeEngineController - a simple helper class that allows to access theme engine settings at design time. All get- and set- methods of this class actually deal with corresponding properties in LMDThemeEngine. There can be created arbitrary number of instances of this class. Changing property in any of of them would change property with the same name in the instance of TLMDThemeEngine (though one instance would suffice). At design time, theme engine properties can be accessed via LMDThemeEngineController instance.
Windows XP visual styles are implemented by TWindowsXPThemeManager, TWindowsXPTheme and TWindowsXPThemePart classes along with a bundle of TWindowsXPThemePart's descendants (e.g. TWindowsXPThemeCheckBox, TWindowsXPThemeComboBoxDropDown etc) each of which implements specific rendering details for corresponding themed control.
TLMDFormThemeProvider allows to apply current theme to the owner form on which it resides. Also, it provides alternative mechanism for theme changes notification: each form theme provider registers itself by LMDThemeEngine.RegisterFormThemeProvider method. Thus, LMDThemeEngine maintains a list of all for theme providers.
Theme engine properties
- Enabled - setting its value to false deactivates current theme and resets LMDXPStyles's procedural variables to system API.
- ThemeCount - allows to get number of loaded themes.
- CustomReadOnlyColor - specifies background color for themed text edit control in read-only state, when ReadOnlyColorMode is scmCustom;
- CustomDisabledColor - specifies background color for themed text edit control in disabled state, when DisabledColorMode is scmCustom;
- ReadOnlyColorMode, DisabledColorMode - state color mode properties. Specify how background color for themed edit text controls is changed when the control state is changed. Possible values are scmTheme, scmNoChange, scmCustom.
|Color is determined by theme settings.|
|Color doesn't change.|
|Color is defined by LMDThemeEngine properties CustomReadOnlyColor, CustomDisabledColor.|
- FTPNotification - specifies how notification about theme changes are delivered to controls. If FTPNotification is true then notifications are sent from theme engine to form theme providers and further to controls that support ILMDThemes interface. If FTPNotification is false, then notifications about theme changes are delivered by means of WM_THEMECHANGED event (LMDApplication.DoThemeChanged is called).
- FormThemeProvidersEnabled - turns off form theme providers without changing their Enabled property. That is, changing FormThemeProvidersEnabled to false and then again to true leaves the set of enabled form theme providers intact.
- FormThemeProviderOptions - global form theme provider options. Form theme providers read options from here if their UseGlobalOptions is set to true. Currently, FormThemeProviderOptions is a set of flags:
|Setting this flag to false makes form's non-client area themed with current system theme.|
|Specifies whether system button glyph is shown in the form caption. Has no effect if ftpThemedNCArea is false.|
- StoreLoadedThemes - specifies whether loaded themes are added to the list of available themes or substitute the first theme in this list. Setting this property to false unloads all unactive themes. If StoreLoadedThemes is false, the list of available themes holds no more than one item.
Developing theme engine aware controls
Generally speaking, if a control was designed to be theme aware, that is, if it renders itself correclty using calls to system routines such as OpenThemeData, DrawThemeBackground, DrawThemeText, then it is supposed to be correctly working with standalone theme engine. One note: the control must correctly reinitialize theme handles on receiving WM_THEMECHANGED message.
Loading and activating theme
LMDThemeEngine maintains a list of all loaded themes. This is why methods ActivateTheme and UnloadTheme are overloaded. One can access themes by handles, by index, and by file name.
Following methods serve to activate/deactivate and load/unload themes: <delphi> function ActivateTheme(ThemeManagerHandle: TThemeManagerHandle; ColorScheme: WideString): HResult; overload; function ActivateTheme(FileName: string; ColorScheme: WideString = ): HResult; overload; function ActivateTheme(AIndex: integer; ColorScheme: WideString = ): HResult; overload; procedure DeactivateTheme; function LoadTheme(const FileName: WideString; AIndex: integer = -1): TThemeManagerHandle; function UnloadTheme(ThemeManagerHandle: TThemeManagerHandle): HResult; overload; function UnloadTheme(AIndex: integer): HResult; overload; </delphi>
The first overloaded version of ActivateTheme is the base one; other two versions eventually call the first one. There are several ways to activate theme. The simplest is to use ActivateTheme with FileName: <delphi> LMDThemeEngine.ActivateTheme('C:\WINDOWS\Resources\Themes\Luna\luna.msstyles'); </delphi>
This overloaded version looks for the theme with specified file name in the list of loaded themes and if the theme is found, activates it with default color scheme name (if ColorScheme is omitted). If no such theme is loaded, then theme engine loads and activates it.
Also, loading and activating theme can be fulfilled by following code:
<delphi> ThemeManagerHandle: TThemeManagerHandle; ... ThemeManagerHandle := LMDThemeEngine.LoadTheme(AThemeFileName); LMDThemeEngine.ActivateTheme(ThemeManagerHandle, AColorScheme); </delphi>
If StoreLoadedThemes is set to true, loading theme increases number of available themes by one, unless LoadTheme is called with AIndex parameter set to allowable non-negative value. In latter case, the theme engine frees existing theme at a specified index and puts link to the newly loaded theme at AIndex position of theme list. Note, that if themes are available in OS, then the first theme that is loaded has index 0 in the list of available themes and actually is current system theme. So, calling the third version of ActivateTheme with one parameter 0 activates current system theme if OS provides any and if this theme was not substituted by application.
Calling ActivateTheme(0, 'NormalColor') is ambiguous, but compiler is happy with it since 0 can be cast to TThemeManagerHandle. This can cause error of interpreting integer index as theme handle. To avoid this, a third version of ActivateTheme is being called with specified param ColorScheme, then integer type must be specified explicitly: ActivateTheme(integer(0), 'NormalColor').
Deactivating and unloading themes
Deactivating current theme activates theme with index 0 in the list of available themes. So, if there is only one theme available, and it is active, then it cannot be deactivated. In order to make application unthemed in Wind9x/Win2k, disable theme engine or use appropriate compiler switches described above. On earlier systems where themes are unavailable deactivating current theme makes application unthemed.
Themes can be unloaded arbitrarily. Unloading current theme deactivates it and then frees resources used by it. All loaded themes are unloaded automatically when LMDThemeEngine is destroyed.
Enable/disable theme engine at runtime?
Use LMDThemeEngine.Enabled property.
How to use default theme at runtime?
When LMDThemeEngine is initialized in WinXP or higher systems, it automatically loads current system theme. Also, theme engine can be simply disabled. In this case theme api hook is removed, and application uses calls to system theme engine.
How to know whether theme file was succesfully loaded/activated?
Check function result. If LoadTheme succeeds, it return handle to the theme manager. If LoadTheme fails, it returns 0. If ActivateTheme fails, it returns E_FAIL exit code, otherwise it returns S_OK exit code.
How to detect whether theme display is activated in Windows XP?
Use LMDThemeEngine.IsSystemThemeEnabled function to detect whether themes are currently enabled in the system.