Dynamically Switching the FontFamily in a Windows Phone App
May 31, 2013A while ago, I built a HangMan Windows Phone app. Apart from its main functionality it also features the ability of dynamically changing its style at runtime. This style mainly consists of a background image, colors and a FontFamily. Following screenshots show these styles in action:
Dynamically switching images and colors at runtime using data binding is a common and trivial task in a Windows Phone application. I expected the same would apply for the FontFamily property but as it turned out: it did not. Unlike WPF, Silverlight for Windows Phone does not support the DynamicResource binding declaration. This means that once certain styles (e.g. FontFamily) are set using StaticResource on an element, they will persist until a new style is applied or the property itself is overwritten.
The Problem
This resulted in an interesting task: How can we change the FontFamily property of all TextBlock elements in our application in a clean and generic way? Providing x:Name attributes to all TextBlocks and switching their FontFamily property in code-behind would of course be solution, but one that was out of the question for me.
- First, it does not scale well with the application and creates a maintenance nightmare when adding, removing or renaming text elements.
- Also, this approach simply would not play well with third party components, where we cannot just change code.
- Finally, being a strong believer in the Model-View-ViewModel (MVVM) design paradigm, I always try to go for a solution involving no code-behind. Experience has shown that such solutions also tend to be more robust, reusable and maintainable than brute-force code-behind approaches.
VisualTreeHelper To The Rescue
Since we are looking for a generic and source-code agnostic solution for this problem, the VisualTreeHelper class comes in handy. Creating a C# extension method on the DependencyObject class allows as to retrieve recursively all descendent elements of a given root element at runtime.
public static IEnumerable<DependencyObject> Descendants(this DependencyObject root)
{
var count = VisualTreeHelper.GetChildrenCount(root);
for (var i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(root, i);
yield return child;
foreach (var descendent in Descendants(child))
{
yield return descendent;
}
}
}
This extension method combined with the powers of LINQ and the OfType extension, allows for a nice and clean one-liner, retrieving all descendent TextBlock elements on a given page:
Application.Current.RootVisual.Descendants().OfType<TextBlock>()
Looks like all that’s missing now is a simple ForEach and we can set the FontFamily property of these elements.
Application.Current.RootVisual.Descendants().OfType<TextBlock>().ForEach(t => t.FontFamily = fontFamily);
That was easy … right?
Theoretically, we are done now, having figured out an elegant and generic way of switching the FontFamily property for all specified elements on a page. But usually such “greedy” approaches operating on all elements of a page have some undesired side effects. In the HangMan application that is exactly what happened. Let’s have a look at the screenshots above again, or rather let’s have a closer look at the AdDuplex ad control at the bottom of the page.
Quite obviously, we also changed the TextBlock style within this ad control. While I considered this behavior OK in the case of the HangMan app, this might not always be the behavior we are looking for. We could add some manual filtering logic to the code which sets the FontFamily and excludes certain elements there, but again we are going for the clean and generic solution and improve the Descendants extension method we implemented before.
public static IEnumerable<DependencyObject> Descendants
( this DependencyObject root,
IEnumerable<Type> excludedTypes = null
)
{
excludedTypes = excludedTypes ?? new List<Type>();
var count = VisualTreeHelper.GetChildrenCount(root);
for (var i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(root, i);
if (!excludedTypes.Contains(child.GetType()))
{
yield return child;
foreach (var descendent in Descendants(child, excludedTypes))
{
yield return descendent;
}
}
}
}
Now we are able to exclude certain element types from further traversal by the VisualTreeHelper class.
Application.Current.RootVisual.Descendants(new[] { typeof(AdControl) }).
OfType<TextBlock>().
ForEach(t => t.FontFamily = fontFamily);
This achieves the desired result now:
But isn't’ that still code-behind?
Right, we didn’t clarify yet where this code will be called or which part of our code is responsible for that. Being good MVVM citizens that we are, the solution we are aiming for is a Behavior class, which provides best conditions for future reusability of these lines of code.
public class SetFontFamilyBehavior : Behavior<FrameworkElement>
{
public FontFamily FontFamily { get; set; }
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += (o, e) =>
{
Application.Current.RootVisual.Descendants(new[] { typeof(AdControl) }).
OfType<TextBlock>().ForEach(t => t.FontFamily = Settings.FontFamily);
};
}
}
If we add this Behavior to the XAML of our Page class, all TextElements on it will automatically get the expected FontFamily.
<i:Interaction.Behaviors>
<u:SetViewModelBehavior Key="Main" />
<vm:SetFontFamilyBehavior />
</i:Interaction.Behaviors>