lapthorn.net *this.that(&theOther);

XAML-Only Font ComboBox

This is a repost of my CodeProject article

Comments can be found there.

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).

Font chooser in use

Font chooser menu

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 intending 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.

A Final Word on Safe Font Usage.

Never, ever, choose Comic Sans. Ever.

© 2017   lapthorn.net