I'm working on a little WPF 4.5 sample app. As part of that, I
needed to create a drop-down list of Font Families. Here's what the
ComboBox currently looks like:
The Data Source
I wanted control over the fonts including sorting and,
potentially, filtering the results. I didn't want to use static
binding from XAML. So, in my viewmodel, I added a collection of
FontFamily objects which is loaded by a LoadSystemFonts
function.
private ObservableCollection<FontFamily> _systemFonts = new ObservableCollection<FontFamily>();
public ObservableCollection<FontFamily> SystemFonts
{
get { return _systemFonts; }
}
public void LoadSystemFonts()
{
_systemFonts.Clear();
var fonts = Fonts.SystemFontFamilies.OrderBy(f => f.ToString());
foreach (var f in fonts)
_systemFonts.Add(f);
}
I'm not yet doing any filtering here, but I could. Your own
application may have similar requirements.
Virtualizing for Performance
I have more than a few fonts on my system -- most of us do.
Rendering out individual lines of a ComboBox each with its own font
is a bit taxing. So, if you want a ComboBox that will open without
an annoying lag, you need to virtualize the list it uses. This is
done by adding an ItemsPanelTemplate which uses the
VirtualizingStackPanel panel as shown in this markup:
<ComboBox x:Name="FontFamilySelector"
ItemsSource="{Binding SystemFonts}"
HorizontalAlignment="Left"
Margin="20,187,0,0"
VerticalAlignment="Top"
Width="255">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Width="450" />
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid Height="24">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
VerticalAlignment="Center"
Margin="3"
Text="{Binding}" />
<TextBlock Grid.Column="1"
Margin="3"
VerticalAlignment="Center"
Text="ABCabc123!@#$"
FontFamily="{Binding}" />
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
In order to use Virtualization in this case, I had to set a
hard-coded width on the VirtualizingStackpanel. If you try to size
based on content, the panel will resize wildly as items are
rendered. Virtualization means layout is deferred for items, so
anything relying on up-front calculations (like "Auto" sizing in a
Grid) will not behave as expected.
Note also how, because I'm binding to a WPF FontFamily object,
I'm able to set the FontFamily on the TextBlock to simply {Binding}
in order to get it to render in the current font.
Smooth Scrolling and Item Clipping in WPF 4.5
By default, the scrolling unit of the VirtualizingStackPanel is
going to be the whole unit. That is, you won't have partial items
displayed and won't have smooth scrolling. WPF 4.5 adds the ability
to control this so you can now set the scroll unit to be Pixel or
Item by using the VirtualizingPanel.ScrollUnit
attached property on the ComboBox. This provides some refinement to
the UI by making the scrolling smoother, and by allowing items to
be partially clipped at the top and bottom.
It may not seem like much, but scolling by pixels rather than
units makes the scrolling more of a "scrolling" visual as opposed
to an "item swapping" visual. The effect is subtle but can be
important, especially in cases where the items in the drop-down
list are all similar.
Final Version
The final version of this code will be included in the small WPF
4.5 sample application. However, as you can see here, it's pretty
simple to create a good font drop-down in WPF.