,I explained how we can easily switch between interface themes at runtime using a simple property and even databinding. That was OK for a single element, or a single window. But what about if we have an application with a lot of windows? Not that hard, just set the property on every window. Yes, that would work, but it has some disadvantages. For example, we change the theme on one window, then we must implement some logic to change it to all other windows that are open, otherwise you are left with windows on the same application with different themes, and that's just ugly. It also can introduce a lot of bugs. And, it is also very boring.
Using a global theme for the application
The idea is that we want to have a theme that should be applied on all of the windows of the application and that can be switched easily at a central location. So I continued working on my ThemeSelector class. I introcuded a property called Global Dictionary, which would be used for all elemenst registered with the theme selector. I also added another attachable property, which is boolean and tells the theme selector whether the element should use the global theme or not.
Let's take a look at the new code:
That's all we need. When the ApplyGlobalTheme is set to true on an element, it starts using the global theme. If this is set to false, the element is switched back to the local theme set with the ThemeSelector, or to no theme at all. When the global theme is changed, the code searches all elements that use it and apply the new one. That's not something very complicated but it makes our lives a lot easier. The ApplyTheme(..) method is the same method I used on my previous post, you can look it up there to understand it better. But just in case, I will post it here:
#region Global Theme
private static ListelementsWithGlobalTheme = new List ();
private static Uri globalThemeDictionary = null;
public static Uri GlobalThemeDictionary
{
get { return globalThemeDictionary; }
set
{
globalThemeDictionary = value;
// apply to all elements registered to use the global theme
foreach (FrameworkElement element in elementsWithGlobalTheme)
{
if (GetApplyGlobalTheme(element))
ApplyTheme(element, globalThemeDictionary);
}
}
}
public static readonly DependencyProperty ApplyGlobalThemeProperty =
DependencyProperty.RegisterAttached("ApplyGlobalTheme", typeof(bool),
typeof(MkThemeSelector),
new UIPropertyMetadata(false, ApplyGlobalThemeChanged));
public static bool GetApplyGlobalTheme(DependencyObject obj)
{
return (bool)obj.GetValue(ApplyGlobalThemeProperty);
}
public static void SetApplyGlobalTheme(DependencyObject obj, bool value)
{
obj.SetValue(ApplyGlobalThemeProperty, value);
}
private static void ApplyGlobalThemeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (obj is FrameworkElement)
{
FrameworkElement element = obj as FrameworkElement;
if ((bool)e.NewValue) // if property is changed to 'true', then add to the list of elements and apply theme
{
if (!elementsWithGlobalTheme.Contains(element))
elementsWithGlobalTheme.Add(element);
// apply the theme
ApplyTheme(element, GlobalThemeDictionary);
}
else
{
if (elementsWithGlobalTheme.Contains(element))
elementsWithGlobalTheme.Remove(element);
// apply the local theme instead of the global
ApplyTheme(element, GetCurrentThemeDictionary(element));
}
}
}
#endregion
private static void ApplyTheme(FrameworkElement targetElement, Uri dictionaryUri)
{
if (targetElement == null) return;
try
{
ThemeResourceDictionary themeDictionary = null;
if (dictionaryUri != null)
{
themeDictionary = new ThemeResourceDictionary();
themeDictionary.Source = dictionaryUri;
// add the new dictionary to the collection of merged dictionaries of the target object
targetElement.Resources.MergedDictionaries.Insert(0, themeDictionary);
}
// find if the target element already has a theme applied
ListexistingDictionaries =
(from dictionary in targetElement.Resources.MergedDictionaries.OfType()
select dictionary).ToList();
// remove the existing dictionaries
foreach (ThemeResourceDictionary thDictionary in existingDictionaries)
{
if (themeDictionary == thDictionary) continue; // don't remove the newly added dictionary
targetElement.Resources.MergedDictionaries.Remove(thDictionary);
}
}
finally { }
}
Using the Theme Selector with the global theme
Changing the global theme is easy. Just one line:
MkThemeSelector.GlobalThemeDictionary = new Uri("/ThemeSelector;component/Themes/ShinyRed.xaml",
UriKind.Relative);
And that's it. All of the elements that use the global theme will be switched. But how to tell the element that it uses the global theme? Just set the ApplyGlobalTheme attached property to true.
Let's see some screenshots.
This is a two-windows application with the no theme applied.
Let's see what happens when we change the global application theme using the Theme Selector.

Great. Both windows switched their themes. With just only one line of code.
Download Sample
The sample application can be downloaded here.










