I now present one way of implementing your own colour themes in an application. (With apologies to Americans… I spell colour in English, not American, except for properties in source code, where I consider color to be correct by convention.) I don’t know if there is a standard way of doing colour themes, and I didn’t bother to search online to see if there is one. I had a very clear idea in my head as to how I’d like colour themes implemented, so I just went ahead and did that.
Again, the source, for this and a whole bunch of other stuff, is here: RomyView.zip
Today I’ll do this backwards. I’ll show some screenshots of the end result, and then explain how I got there. The application has three colour themes:
Actually, monochrome is not a serious theme. I added it to make fun of the Visual Studio 2012 UI. My monochrome theme is designed to intentionally blur the colours together by virtue of their poor contrast. It also sports an ALL CAPS menu.
Here is the main form, the Browser (as in file browser) in light, dark and monochrome theme. (You can click each one to view the full-size image.) Notice the tool strip button that resembles a monitor? Clicking it opens a drop-down menu where you can select a different theme. Double-clicking it toggles between the themes.
Here is the video converter dialog, as it appears for the three different themes:
And here is an example of what most of my dialogs look like. In this case, it is a conversion progress dialog. All my progress dialogs are modeless and can be minimized, and you can do as many concurrent video conversions as you want. If you toggle the theme, the new colours are applied to all of them immediately.
And finally, here’s a close-up of my thumbnail control. In each case, we see a selected thumb, a normal (unselected) thumb, and a hot-tracked thumb.
Note that although the light theme thumbnail is designed to look similar to a Windows thumbnail, it isn’t one. It’s my own control derived from ContainerControl. Thus the thumb control contains a fair amount of code to do its painting, using different gradients depending on the theme and its current state (selected; hot-tracked; grayed). It also measures the text and decides on how much space to allow for it, and it, and it fetches its own image, either from my custom cache, or forces the cache to get the image. It’s by far the most complex control in the solution, and there could be many of them on the container at once. I had to take this into account in my theme implementation, but more on that in due course.
The themes implemerntation
The idea is that for a predefined set of types, each has one or more colour properties that change when the theme changes. The simplest way to achieve this is to define an interface for each type that can be themed. Here are my theme interfaces:
I then created three types, each of which implements all of these interfaces, called LightThemeColors, DarkThemeColors and MonoThemeColors.
All forms in the application derive from one ancestor, which is Romy.Controls.BaseForm. This base form then contains private fields of the three above types (declared as static and readonly because they do not change). The base form itself implements the IThemeableControl interface, thus it raises the ThemeChanged event whenever the theme is changed. Since it is the common base for all forms in the application, none of the other forms need to implement any of the themes interfaces. Instead, it calls a virtual method, called LoadTheme, from the event handler.
The work done to change the colours by the base form class, is done here. It applies new theme colours for the form itself, as well as any thumbnails, then calls a private method, FindAndRecolorControls, that searches for all controls of the types defined by the theme interfaces, and applies the new colours there too. Thus derived forms inherit this theme implementation, and don’t need to do anything at all if the default theme implementation works for them.
Thanks to inheritance, all that any other form in the application needs to do to be able to respond to the theme changing (if it needs to customize the way it handles themes), is override the LoadTheme method. The base form also exposes a dynamic typed property, called ThemeColors, which simply maps to one of the LightThemeColors, DarkThemeColors or MonoThemeColors fields, whichever is appropriate for the theme. This works out to be very convenient and easy to use from any of the derived forms. To get the right colours for any of the types for which theme colours are defined, since the ThemeColors property maps to a type that implements all of the theme interfaces, the code that needs to know the colours when the theme changes can simply type-cast it to whatever interface they need. (In fact, in the code above, in LoadTheme, when ThumbnailColors is referenced, ThumbnailColors is just a property that has already type-cast ThemeColors to IThumbnailThemeColors.)
I mentioned that I needed to do something different for the thumbnails… Rather than having to manipulate properties on potentially thousands of thumbnails at runtime, I created a helper class, called the ThumbnailThemer. This type exists as a singleton. The actual colour properties are implemented as properties of a static type owned by the thumbnail type. When the theme changes, the ThumbnailThemer sets them just once; then every thumbnail instance picks up the new colours.
And that is the gist of my themes design. There is a little more to it. I was already using a custom-derived ToolStripProfessionalRenderer, for a number of reasons (mostly that I detest the way ToolStrips, StatusStrips and so on are rendered by default), so when I wrote the mono theme, I hacked some code into it, to make the menus ALL CAPS, and to make menus as hideous as possible when this theme is active.
As an example of derived forms customizing this behavior when loading a theme… I was also already using the AForge.Net image manipulation library elsewhere in the application, so now it is also used by derived forms, which override LoadTheme, to modify certain control’s images on the fly when the theme changes (as well as in the video player for dynamic mouse-over affects). It is very easy to use that library to change image’s hue, saturation, contrast etc. And I have found it very effective to modify images this way on the fly rather than storing unnecessary images in resources. (There are loads of images stored in resources already, without creating different versions of the same images for mouse-over affects, and so on. I see that as wasteful.)
Enough! It’s Friday afternoon. I’m tired. For more details, read the source code.