Table of Contents
About two months ago I unveiled a pet project of mine, named Podder. It is an audio podcast player (a.k.a. podcatcher) built in WPF. The application comes pre-loaded with a dozen or so podcasts, so you can start listening to episodes right away. If there are other podcasts that you would like to listen to, it is easy to add them into Podder at any time. Podder is free, fun, and easy to use.
The first article in this series explained that Podder supports what I call “structural skinning.” This article explores what structural skinning is, how I implemented it, and even shows structural skinning in action as we examine a truly fantastic Podder skin built by Grant Hinkson, a professional Visual Designer.
At the top of this article, you will find the Podder source code and binaries available for download. These are different from the file downloads on the first article in this series. Many improvements and features have gone into Podder since the first version, so be sure to download the latest and greatest bits.
Binaries
If you do not want to browse the application’s source code, then just download the binaries and run Podder.exe. You must have the .NET Framework 3.5 installed for Podder to run.
Source Code
The other option is to download the source code. You must have Visual Studio 2008 installed to open and build the Podder solution. If you want to build the project containing Grant’s skin, you must have the Infragistics NetAdvantage for WPF v7.2 (or later) installed. You can get a free trial of it here.
If you download the source code but do not have or want to install the Infragistics WPF controls, you will need to exclude the Podder.GrantHinkson project from the solution. To do that in Visual Studio 2008, open Solution Explorer, right-click on the Podder.GrantHinkson project, and select ‘Unload Project’ from the context menu. Keep in mind that if you do this, you will not be able to use Grant’s skin and will be stuck with my plain old boring UI.
Throughout this article, we explore what it means to support “structural skinning.” Briefly, it means that the application’s UI is completely replaceable at run time. Previous articles of mine have already discussed almost all of the concepts and techniques employed to implement structural skinning. If you have not already done so, I recommend you read ‘Creating a Skinned User Interface in WPF’ and then ‘Using MVC to Unit Test WPF Applications’. I consider those articles to be prerequisites of this article.
This article demonstrates the power of structural skinning by showing how a professional visual designer skinned Podder. Grant Hinkson, a colleague of mine at Infragistics, was the designer with whom I worked on this project. It was an eye-opening experience for me to work closely with an accomplished designer, which I discussed in this blog post. It was a pleasure to work with Grant, since he was very excited about the project and strived to do a fantastic job. Thanks a lot, Grant!
Screenshots can only convey so much. Now that CodeProject articles can contain Silverlight videos, I decided to show Podder in action. I would like to thank Karl Shifflett for taking the time to write his excellent article that explains how to create and deploy Silverlight videos in CodeProject articles.
Without further adieu, I give you Podder in action…
If you do not see the video embedded in the article or would like to watch this video at a larger size, please click here.
Working with Grant on Podder was an eye-opening experience for me. We view WPF in very different ways, and enjoy learning new ways to think about WPF from each other. Grant is an authority on WPF visual design. He has been doing visual design work on WPF controls and applications for years at Infragistics. He has given several presentations about the designer/developer workflow, which is a very important aspect of professional WPF development.
I also have the privilege of working with Craig Shoemaker, host of Polymorphic Podcast and the new Pixel8 podcast. Craig happily agreed to interview Grant and discuss what it was like working on Podder as a visual designer. The following discussion captures the perspective of an accomplished WPF visual designer, his thoughts about working on Podder, and WPF visual design in general. I highly recommend you check this out:
Craig Shoemaker and Grant Hinkson Discussing Podder and WPF Visual Design (duration 9:37)
I would like to thank Craig and Grant for taking the time to have this discussion, and for Craig to pull it all together into an MP3 file.
I think it is easiest to explain what structural skinning is by providing an analogy. WPF controls are said to be “look-less” because they do not have an intrinsic visual appearance. They depend on control templates to provide an “explanation” of how the control should be rendered. A template is like a cookie cutter that WPF uses to generate a tree of visual elements at run time. That tree of visual elements is what you see when you look at a control on the screen.
A control is responsible for exposing state via properties and providing behavior, such as raising an event in response to user interaction. Controls are not responsible for knowing what they should look like. All controls have a default template, which is why you can see a control on the screen even if you do not assign it a control template. If you want to alter the way a control looks, all you have to do is assign a custom control template to its Template property.
Take the idea of a look-less control, apply it to an entire application, and that is what I refer to as “structural skinning.” You might think of an application that supports structural skinning as a look-less application. It has a default appearance baked in, but you can easily replace the default UI with a completely new UI at run time or compile time. When I say, “replace the UI” I literally mean swap out all of the controls and elements in a Window and replace them with an entirely new visual tree. One, roughly accurate, way to think about it is that a structural skin is to a look-less application as a control template is to a look-less control.
Podder is a look-less application, which means that visual designers can create an endless array of imaginative skins to expose its data and behavior to the end-user. Grant, the visual designer who created Podder’s new skin, worked almost exclusively in Expression Blend and wrote almost no code to create his skin. When a system’s architecture supports structural skinning, the developers work in Visual Studio, and visual designers work in Blend. That is the way it should be, in my opinion.
Most applications do not need to swap out their user interfaces at run time. This is definitely an esoteric functional requirement for most line-of-business (LOB) applications. With that said, there are reasons to keep this concept in mind when designing and building applications in WPF. Let us take a moment to discuss when it does and does not make sense to use this technique.
Obviously, if you need to support changing the entire UI at run time, structural skinning is very useful. Common examples of this are software products that allow for high degrees of end-user customization. These are typically products in the entertainment and multimedia space, such as Windows Media Player.
On the other hand, suppose you are creating a business application under very tight time constraints. You might not have time for such niceties as a clean design, unit testing, visual designers, and sleep. In that situation, perhaps it is best to avoid structural skinning. It takes time, both up front and during the development process, to make an application look-less. If you are stuck doing slash-and-burn development, you probably do not have time to read this article, so this is a moot point.
Most applications do not require the ability to swap out their UI at run time, and their developers are not working under wretched sweatshop conditions (I hope). What do those applications stand to gain by having the structural skinning concept factored into their design? What are the benefits? What are the risks?
The single most important benefit of designing an application to be look-less is that it practically requires you to build a clean, modular, unit testable, loosely coupled architecture. In a truly look-less design, the application logic (Controller) should make no assumptions about the skin (View) hosted in the user interface. The skin binds to properties on data objects (Model), and communicates user interaction to the application logic via routed commands. This arrangement creates code that is easy to unit test, because testing the application logic and data model does not involve UI code. As I mentioned previously, you can learn much more about implementing Model-View-Controller in WPF by reading this article. Structural skinning depends heavily on the use of MVC.
Another benefit of creating look-less applications comes into play when you are working with visual designers. Developers and designers should have as few dependencies on each other as possible. If a developer wants to forge ahead and create a new Window in an application, but cannot collaborate with a visual designer beforehand, he/she can create a temporary skin for that Window. That skin might be useful for testing out the new Window’s features while debugging. Later on, after a visual designer has replaced it with the “real” skin, the developer can easily use the old dev skin (chock full of convenient debugging info) if necessary.
From a visual designer’s perspective, having the flexibility to apply a UI via skins makes it easy to prototype various layouts, interaction models, color schemes, etc. You can create several skins, test them out, compare them against each other in design meetings, and throw away the ones that do not work out. In addition, the creation of skins typically does not require much, if any, coding since all of the interaction logic already exists in the visual container that hosts a skin.
The biggest risk of designing for structural skinning is that it could overcomplicate an otherwise simple application. For example, if you are building a simple utility application for a small number of users, it is probably overkill to burden the application design with the concepts described herein. Additionally, if your development team is not familiar with WPF, MVC, or loosely coupled architectures, the learning curve might be prohibitively steep.
This section of the article reviews the essential techniques and classes that support structural skinning. We begin with a brief refresher on how to implement MVC and UI skinning in WPF. First, look at the following diagram, which summarizes how the various pieces fit together.
MVC
Structural skinning depends heavily on my technique for implementing Model-View-Controller (MVC) in WPF. Here is a very brief refresher on how that works.
The View is responsible for rendering data to the screen, and handling simple interaction logic (ex. changing colors when the mouse is over an element). The Model is responsible for storing the state of the application and executing domain logic. The Controller is responsible for performing application logic in response to user interaction, by wiring the View and Model together. Elements in the View bind to properties on Model objects and execute routed commands to inform the Controller of user interaction. Sometimes the View directly updates the Model via two-way data bindings. The Controller tells the Model to perform some processing in response to user input, and can request that the View’s visual container (i.e. a Window
) perform some UI-related task. For a much more thorough review of this topic, please read ‘Using MVC to Unit Test WPF Applications’.
UI Skinning
Structural skinning utilizes the skinning techniques described in my ‘Creating a Skinned User Interface in WPF’ article. Here is a very brief refresher on how that works.
Hierarchical resource resolution, merged resource dictionaries, and dynamic resource references can work together to create a simple and flexible system for skinning any user interface. You create Style
s that target various elements in a user interface, collectively known as a skin. You package all of those Style
s into a resource dictionary and give them pre-determined resource keys. You use those same resource keys as the keys of dynamic resource references applied to the Style
property of elements in your UI. When you merge one of those resource dictionaries into the Application’s resource collection, elements with dynamic resource references applied to their Style
property automatically use those Style
s. At that point, you have applied a skin to the UI.
Putting the Pieces Together
Now that we have reviewed the two pillars of look-less applications, we can summarize how it all works in one sentence:
Look-less application Views are dynamically loaded resources.
The remainder of this article explains what that sentence means. For the rest of this article, I use the terms “skin” and “structural skin” interchangeably. A skin contains one or more views, a la MVC.
From an application’s perspective, a skin can come from one of two places: either it is in the application itself, or it is loaded from an external source at run time. Podder has a default skin baked into its main assembly, and has the ability to load skins from arbitrary .NET DLLs.
When Podder starts up, it needs to create a list of all available skins for the user to select. It searches through a “Skins” directory, which must exist in the same directory as the Podder EXE, and looks for subdirectories that contain DLLs that match a certain naming convention. It loads each of those DLLs into a child AppDomain
and uses some reflection magic to extract information about the skin contained in that DLL. The skin DLLs are loaded in a child AppDomain
so that we can unload all of those external DLLs, thus reducing the Podder working set and improving performance.
Here is the code that discovers information about skins at start up:
public class SkinInfoCollection : ReadOnlyCollection<SkinInfo>
{
public SkinInfoCollection()
: base(new List<SkinInfo>())
{
base.Items.Add(SkinInfo.Default);
this.LoadExternalSkins();
(base.Items as List<SkinInfo>).Sort();
}
void LoadExternalSkins()
{
string location = Assembly.GetExecutingAssembly().Location;
string rootDir = Path.GetDirectoryName(location);
string skinsDir = Path.Combine(rootDir, "Skins");
if (Directory.Exists(skinsDir))
{
AppDomain childDomain = AppDomain.CreateDomain("LoadingZone");
try
{
string thisAssembly = Assembly.GetExecutingAssembly().FullName;
string typeName = typeof(SkinAssemblyLoader).FullName;
var loader =
childDomain.CreateInstanceAndUnwrap(thisAssembly, typeName)
as SkinAssemblyLoader;
string[] skinFolders = Directory.GetDirectories(skinsDir);
foreach (string skinFolder in skinFolders)
{
string[] resourceAssemblies =
Directory.GetFiles(skinFolder, "Podder.*.dll");
Debug.Assert(
resourceAssemblies.Length == 1,
"There are too many skin DLLs here: " + skinFolder);
foreach (string assm in resourceAssemblies)
{
var skinInfo = loader.GetSkinInfoForAssembly(assm);
if (skinInfo != null)
base.Items.Add(skinInfo);
}
}
}
finally
{
AppDomain.Unload(childDomain);
}
}
}
private class SkinAssemblyLoader : MarshalByRefObject
{
internal SkinInfo GetSkinInfoForAssembly(string assemblyFile)
{
Assembly assm = null;
try
{
assm = Assembly.LoadFrom(assemblyFile);
}
catch (Exception ex)
{
Debug.Fail("Error while loading resource assembly: " + ex.Message);
}
if (assm == null)
return null;
object[] attrs =
assm.GetCustomAttributes(typeof(PodderSkinAssemblyAttribute), false);
if (attrs == null || attrs.Length != 1)
return null;
var attr = attrs[0] as PodderSkinAssemblyAttribute;
return new SkinInfo(assemblyFile, attr.SkinName);
}
}
}
Applying a skin really boils down to merging a ResourceDictionary
into the Application’s Resources
collection. From the perspective of loading skins, a skin is just a ResourceDictionary
declared in a file called “Resources.xaml” that can be extracted from an assembly. The name “Resources.xaml” is an arbitrary identifier; I could have used any valid file name. Later we will see what those resource dictionaries contain, but for now, we focus on how to import a skin into the application at run time.
Podder applies a skin at two points in time: when it starts up, and when the user selects a skin in the UI. It automatically applies the default skin the first time Podder runs on a user’s machine, or if no external skins exist. After the first run of the application, it loads whatever skin the user selected when the application previously shut down.
Here is the class responsible for applying a skin:
public partial class App : Application
{
public App()
{
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
}
public static void ApplySkin(SkinInfo skinInfo)
{
if (skinInfo == null)
throw new ArgumentNullException("skinInfo");
var uiSettings = PodderDataSource.Instance.PersistedUISettings;
bool ignore =
App.Current.Resources.MergedDictionaries.Count != 0 &&
skinInfo.Equals(uiSettings.Skin);
if (ignore)
return;
ResourceDictionary skinResources = App.GetSkinResources(skinInfo);
if (skinResources == null)
skinResources = App.GetSkinResources(SkinInfo.Default);
if (0 < App.Current.Resources.Count)
App.Current.Resources.Clear();
App.Current.Resources.MergedDictionaries.Add(skinResources);
uiSettings.Skin = skinInfo;
}
static ResourceDictionary GetSkinResources(SkinInfo skinInfo)
{
ResourceDictionary skinDictionary = null;
if (skinInfo.IsDefault)
{
skinDictionary = new ResourceDictionary();
skinDictionary.Source =
new Uri("DefaultSkin/Resources.xaml", UriKind.Relative);
}
else
{
try
{
string path = Path.GetDirectoryName(skinInfo.AssemblyName);
string[] assemblies = Directory.GetFiles(path, "*.dll");
foreach(string assembly in assemblies)
Assembly.LoadFrom(assembly);
string assemblyName =
Path.GetFileNameWithoutExtension(skinInfo.AssemblyName);
string packUri = assemblyName + ";component/Resources.xaml";
Uri resourceLocator = new Uri(packUri, UriKind.Relative);
skinDictionary =
Application.LoadComponent(resourceLocator)
as ResourceDictionary;
}
catch (Exception ex)
{
Debug.Fail("Could not load skin: " + ex.Message);
}
}
return skinDictionary;
}
}
Simply merging some skin resources into an Application
’s resource collection is not going to make the skin appear in the UI. We must create a link between those resources and the UI container(s) displayed by the application. For example, we must use dynamic resource references to “bind” Podder’s main Window
to the skin loaded into the Application
’s main ResourceDictionary
. This is how the application’s Views are dynamically loaded via resource bindings.
Here is Podder’s main Window
XAML:
<ui:ConfigurableWindow
x:Class="Podder.UI.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:model="clr-namespace:Podder.Model;assembly=PodderLib"
xmlns:cmd="clr-namespace:Podder"
xmlns:ui="clr-namespace:Podder.UI"
Content="{DynamicResource VIEW_MainWindow}"
DataContext="{x:Static model:PodderDataSource.Instance}"
Icon="..\Images\app.ico"
Title="Podder"
>
<ui:ConfigurableWindow.CommandBindings>
-->
</ui:ConfigurableWindow.CommandBindings>
</ui:ConfigurableWindow>
The crucial point to observe is that the Window
’s Content
is given a dynamic resource reference, effectively binding it to a resource whose x:Key
is “VIEW_MainWindow”
. Whatever object is found in the resource hierarchy with that x:Key
will be displayed by the Window
. If no object with that x:Key
is found, the Window
will remain empty. In practice, a UserControl
, which acts as a View, is loaded into the Window
. That View binds to the Window
’s DataContext
(the Model) and executes routed commands, which are handled by the Window
’s list of CommandBinding
s and eventually dispatched to the Window
’s Controller.
At this point, we have seen the mechanics of how MVC and UI skinning work together, to create a system where Views are dynamically loaded and consumed as resources. Now let us briefly turn our attention to how a skin is set up.
As we saw earlier, Podder loads a structural skin by extracting a ResourceDictionary
from a file called Resources.xaml. That file is located in an assembly, and you compile it with the default Build Action of ‘Page’. If the skin is contained in a separate DLL from Podder (i.e. it is not the default skin), that file must exist at the root level of the project. Podder will not extract the file from a subdirectory of the project.
The ResourceDictionary
in Resources.xaml is a collection of resources that a skin needs to apply to Podder to achieve its visual theme. Some of those resources are the views applied to the UI containers, as seen in the previous section.
Here is a truncated version of the ResourceDictionary
used for the application’s default skin:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:model="clr-namespace:Podder.Model;assembly=PodderLib"
xmlns:podder="clr-namespace:Podder"
xmlns:ui="clr-namespace:Podder.UI"
xmlns:views="clr-namespace:Podder.DefaultSkin.Views"
>
-->
<views:MainWindowView x:Key="VIEW_MainWindow" />
-->
</ResourceDictionary>
The main Window
hosts and displays the MainWindowView
, which is a UserControl
. Since it is part of the default skin, other skins do not use it. They would have their own version of MainWindowView
, as a completely separate control. Here is what the default skin’s MainWindowView
looks like after it has been loaded into Podder and pumped full of data:
Here is the default skin’s MainWindowView
XAML:
<UserControl
x:Class="Podder.DefaultSkin.Views.MainWindowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:podder="clr-namespace:Podder"
xmlns:views="clr-namespace:Podder.DefaultSkin.Views"
Background="{DynamicResource PrimaryViewBackBrush}"
>
<UserControl.Style>
<Style>
<Style.Triggers>
-->
<DataTrigger
Binding="{Binding Path=IsRetrievingEpisodeList}"
Value="True"
>
<Setter
Property="FrameworkElement.Cursor"
Value="Wait"
/>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Style>
<Grid Margin="4">
<Grid.Resources>
<Style TargetType="GroupBox">
<Setter
Property="Background"
Value="{DynamicResource GroupBoxBackBrush}"
/>
<Setter
Property="IsEnabled"
Value="{Binding Path=NetworkConnection.IsAvailable}"
/>
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<GroupBox
x:Name="skinsGroupBox"
Grid.Row="0"
Header="{podder:ResourceString MainWindowView_SkinsHeader}"
>
<ComboBox
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=Skins}"
Width="200"
/>
</GroupBox>
<GroupBox
Grid.Row="1"
Header="{podder:ResourceString MainWindowView_PodcastsHeader}"
>
<views:MainWindowView_Podcasts />
</GroupBox>
<GroupBox
Grid.Row="2"
Header="{podder:ResourceString MainWindowView_EpisodesHeader}"
>
<views:MainWindowView_Episodes />
</GroupBox>
<GroupBox
Grid.Row="3"
Header="{podder:ResourceString MainWindowView_ControlPanelHeader}"
>
<views:MainWindowView_ControlPanel />
</GroupBox>
</Grid>
</UserControl>
MainWindowView
contains several smaller UserControl
s. This keeps each piece of the UI in a small, simple, separate file.
Podder is an audio podcast player application that can display an arbitrary user interface, called a structural skin. It has a default skin and, as of this writing, a new skin created by Grant Hinkson, an accomplished Visual Designer. Programs that allow for structural skinning are “look-less applications,” similar to the idea of look-less controls in WPF. The application offers a data model and a set of services via routed commands, which a skin can display and consume in a pluggable fashion. Structural skinning uses the Model-View-Controller and UI skinning techniques presented in other articles of mine here on CodeProject.
- March 5, 2008 – Created the article