Simple Theming with WPF
June 5, 2015 Leave a comment
When one researches how to set themes with WPF, what is found is a host of articles describing templates, resources and the subsequent support information needed to add such styling into a WPF application. Here is a link to a very nice article on “The Code Project” that describes quite a bit on the subject in an organized fashion.
If one were to take a look at the article (Yes, it is somewhat dated but still quite relevant.) they would see a level of complexity that many may not want to invest in while developing their own WPF applications.
A lot of what WPF theming is has been taken from web development concepts such as with CSS. And given the flexibility of WPF, it is understandable why Microsoft chose to go this route.
Microsoft Blend is an entire tool devoted to this area of WPF. It is all quite a bit much if all you want to do is offer the user the capability of switching styles in a WPF application you have developed. And WPF theming is an area of expertise on its own since it allows WPF applications to be built by two parties; a designer similar to that of a web designer and the developer.
But to keep things simpler there is an easier way to manage such implementations, which also allows you to easily modularize such work within your own applications.
To begin with we have to understand that there are two types of basic display elements that would require theming or stylization; the window (master or popup) and a page, both of which are perfectly accessible to manage their appearance from within the WPF application. On each of these elements we of course have a variety of controls, again, all of which are accessible to change their styles.
Nothing that is presented here then is really anything different from what native WPF theming processes do; a change in stylization of the elements to either a window’s or a page’s elements.
So let’s begin…
The following implementations will all be made in each window and\or page in your application:
A “private” call from the loading window or page to a standard method in each of these entities to their supporting modules. Thus, for example, one of Black Falcon Software’s products we have an “About” popup window that makes the following method call…
Private Sub Set_ApplicationThemeColors() Dim loAboutThemes As New SourceControl.Library.Classes.Themes.AboutThemes(Me) Dim loStateProperties As New SourceControl.StateManagement.Classes.StateProperties() loAboutThemes.Set_ApplicationThemeColors(loStateProperties.THEME.ToUpper().Trim()) End Sub
Note that for each type of display element, window or page, we will have a “public” class that each will call into. Each “public” class will be instantiated with a reference to the window or page element itself (as shown in the above code snippet) so that each element can be immediately updated when the style modifications are processed.
In each case, though the supporting module names may be different, the called method name is the same with one parameter, the “THEME” itself, which denotes however you want to define your themes.
In this case, a theme code could be “DEFAULT”, or “BLACKWHITE”; the former a dark-blue on blue theme while the latter is just as it denotes, a black on white theme.
Also notice in the code-snippet above that a state-management property is used to hold the theme code for the entire application (See the article “Disciplined Application State for WPF”). This is loaded at application initiation and as such is available anywhere within the application.
The called method in the supporting class should appear something like the following…
Public Sub Set_ApplicationThemeColors(ByVal psTheme As String) Select Case (psTheme.ToUpper().Trim()) Case "DEFAULT": Set_DefaultTheme() Case "BLACKWHITE": Set_BlackWhiteTheme() End Select End Sub
The actual methods that do the styling for your window or page element are declared as “Private”, which follows fairly standard API development practices.
Private Sub Set_DefaultTheme() Dim loBorder As System.Windows.Controls.Border Dim loLabel As System.Windows.Controls.Label Dim loBrushConverter As New BrushConverter() coWindow.Background = DirectCast(loBrushConverter.ConvertFrom("#FFD2CEF0"), Brush) loBorder = DirectCast(coWindow.FindName("brdrBorder1"), System.Windows.Controls.Border) loBorder.BorderBrush = DirectCast(loBrushConverter.ConvertFrom("#B2B2DC"), Brush) loLabel = DirectCast(coWindow.FindName("lblSQLSourceControlForDevelopersCaption"), System.Windows.Controls.Label) loLabel.Background = DirectCast(loBrushConverter.ConvertFrom("#FFD2CEF0"), Brush) loLabel.Foreground = DirectCast(loBrushConverter.ConvertFrom("#FF260161"), Brush) loLabel = DirectCast(coWindow.FindName("lblVersionCaption"), System.Windows.Controls.Label) loLabel.Background = DirectCast(loBrushConverter.ConvertFrom("#FFD2CEF0"), Brush) loLabel.Foreground = Brushes.DarkBlue loLabel = DirectCast(coWindow.FindName("lblApplicationVersionNo"), System.Windows.Controls.Label) loLabel.Background = DirectCast(loBrushConverter.ConvertFrom("#FFD2CEF0"), Brush) loLabel.Foreground = Brushes.DarkBlue loLabel = DirectCast(coWindow.FindName("lblSupportContactCaption"), System.Windows.Controls.Label) loLabel.Background = DirectCast(loBrushConverter.ConvertFrom("#FFD2CEF0"), Brush) loLabel.Foreground = Brushes.DarkBlue loLabel = DirectCast(coWindow.FindName("lblCopyrightCaption"), System.Windows.Controls.Label) loLabel.Background = DirectCast(loBrushConverter.ConvertFrom("#FFD2CEF0"), Brush) loLabel.Foreground = Brushes.DarkBlue End Sub
In order to gain access to any of the controls in either a window or page you have to define what controls you need to access. In this code-snippet we require access to a border and label control, both of which can be found in the “System.Windows.Controls” namespace. This namespace is also where the majority of controls will be found for your definitions. If you require access to an actual graphic control such as a “rectangle”, you will also need the “System.Windows.Shapes” namespace.
Finally, if you require the use of Hex defined colors (as this code-snippet does) you will also need the “System.Windows.Media” name-space where the “BrushConverter” class can be found. If not, you can then simply use “Brushes” class (ie: Brushes.Black), for coloring your controls, which can be found in the “System.Drawing” namespace.
Notice in this code-snippet that the first style modification we apply is to the display element itself whether it be a window or a page. Also notice that the window or page is denoted as a class level declaration with the prefix of “co” (class object).
If the display element is a page than the first line in a theme method would look like the following for consistency purposes…
coPage.Background = DirectCast(loBrushConverter.ConvertFrom("#FFD2CEF0"), Brush)
In either case, the class level declaration is defined as a standard “Object”, which holds the referenced element passed to the class’ constructor…
Private coWindow As Object (Or) Private coPage As Object
Styling each control is rather straight forward since in each case all you have to do is gain a reference to each one by using the following technique…
DirectCast(coWindow.FindName("brdrBorder1"), System.Windows.Controls.Border) (Or) DirectCast(coPage.FindName("brdrBorder1"), System.Windows.Controls.Border)
Once you have a reference to the control you can modify any of its display properties…
loBorder.BorderBrush = DirectCast(loBrushConverter.ConvertFrom("#B2B2DC"), Brush)
By following such a methodology you can add as many styles or themes as you like to a WPF application with a consistent set of modules that are easily modified when necessary.
True, this does add to the overall code-base in an application but it is no less efficient than adding complex templates to the mix. It is also far easier to develop than template theming is as research into such a technique demonstrates.
This is also not to say that template theming should be avoided. If it is what you require than you should of course invest the time and effort into mastering it. And there are many predefined themes available on the Internet.
The technique shown in this article is for those who want and\or require their time invested into the development of an application without the additional cost of having to learn the more complex level of native WPF theming.