Introduction
I needed a simple ComboBox
to select a FontFamily
in a WPF application (I don't care about the font-weight).
After some searching, I found Pete O'Hanlon's article describing what I wanted.
So why another (short!) article? The first commenter in the article suggested this:
<ListBox ItemsSource="{Binding Source={x:Static Member=Fonts.SystemFontFamilies}}">
<ListBox.ItemTemplate>
<DataTemplate>
<Label FontFamily="{Binding .}" Content="{Binding Source}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
as an alternative, which made me think about combining the two as a pure XAML solution that you can cut and paste (as Pete's code has a tiny bit of code-behind).
In order to create the XAML solution, I found out a few interesting things that I thought I would share in a real example (as I'm still getting to grips with the many facets of WPF).
Show Me the XAML!
Here it is, in its entirety:
<ComboBox
xmlns:ComponentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase"
ItemTemplate="{DynamicResource FontTemplate}">
<ComboBox.Resources>
<CollectionViewSource x:Key="myFonts" Source="{Binding Source={x:Static Fonts.SystemFontFamilies}}">
<CollectionViewSource.SortDescriptions>
<ComponentModel:SortDescription PropertyName="Source" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
<Style x:Key="FontStyle">
<Setter Property="Control.FontFamily" Value="{Binding Source}" />
<Setter Property="Control.FontSize" Value="16" />
</Style>
<DataTemplate x:Key="FontTemplate">
<StackPanel VirtualizingStackPanel.IsVirtualizing="True">
<TextBlock Style="{StaticResource FontStyle}"
Text="{Binding Source}"
ToolTip="{Binding Source}" />
</StackPanel>
</DataTemplate>
</ComboBox.Resources>
<ComboBox.ItemsSource>
<Binding Source="{StaticResource myFonts}" />
</ComboBox.ItemsSource>
</ComboBox>
You should be able to cut 'n' paste this directly into your code. You would then bind the ComboBox
's SelectedValue
to a property of your choice.
The SelectedValue
is of type System.Windows.Media.FontFamily
.
What is Going On?
There are several things going on that we need to describe. Beware! More verbose XAML!
A Sorted List of Fonts
Skipping directly to the ComboBox.Resources
section: we get the full collection of system fonts. However, by default, they only come
partially sorted (by FamilyName
), so we sort them into our own collection called myFonts
. We do this by importing the ComponentModel
namespace
via this XAML markup:
xmlns:ComponentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase"
and then create our own collections sorted by the Source
property (which is the font family name):
<CollectionViewSource x:Key="myFonts" Source="{Binding Source={x:Static Fonts.SystemFontFamilies}}">
<CollectionViewSource.SortDescriptions>
<ComponentModel:SortDescription PropertyName="Source" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
Data Template
We declare a simple template that renders the fonts in their own type face, and provides a tooltip, within the ComboBox
.
Static Resources
Lastly we bind the ComboBox.ItemsSource
to our sorted collection of fonts, myFonts
, using the long-hand XAML binding.
Why do we do this last, and not directly as a ComboBox
attribute?
The ItemsSource
attribute requires that it is bound to a static resource. Suppose we do this:
<ComboBox
xmlns:ComponentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase"
ItemTemplate="{DynamicResource FontTemplate}"
ItemsSource="{Binding Source={StaticResource myFonts}}">
We get an exception thrown:
"Cannot find resource named 'myFonts'. Resource names are case sensitive."
as myFonts
has not yet been declared.
We could of course move our font collection to the UserControl
/Window
/Application
Resources
section,
however in this example we only have one font combo box, so it is nice to have it within the ComboBox.Resources
section.
You might also try setting the ItemsSource
to reference myFonts
dynamically, via:
<ComboBox
xmlns:ComponentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase"
ItemTemplate="{DynamicResource FontTemplate}"
ItemsSource="{Binding Source={DynamicResource myFonts}}">
This also fails with the exception:
"A 'DynamicResourceExtension' cannot be set on the 'Source' property of type 'Binding'. A 'DynamicResourceExtension' can only be set on
a DependencyProperty of a DependencyObject."
So in answer to our question: as XAML has a 'one-pass compiler', a StaticResource
has to be declared lexically before it is referenced: if we declare
the binding last, then we can create our sorted list of fonts StaticResource
, within ComboBox.Resources
, and then bind to it within
the XAML of the ComboBox
, hence this piece of XAML:
<ComboBox.ItemsSource>
<Binding Source="{StaticResource myFonts}" />
</ComboBox.ItemsSource>
Using this XAML Snippet
As mentioned above, if you intend on using this XAML (and using the font combobox multiple times), move the sorted font collection:
<CollectionViewSource x:Key="myFonts" Source="{Binding Source={x:Static Fonts.SystemFontFamilies}}">
...
</CollectionViewSource>
into your Application/Window/UserControl Resources
section, and put this attribute:
xmlns:ComponentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase"
into the corresponding XAML document root.
Tip o' the hat to Pete for the original XAML.
A Final Word on Safe Font Usage
Never, ever, choose Comic Sans. Ever.