Saturday, October 27, 2007

WPF: A fast font drop down list

Writing a drop down list for fonts is easy in WPF. Just create a data template that binds the text and the font, and bind the ComboBox against Fonts.SystemFontFamilies:

        <ComboBox x:Name="fontComboSlow">

            <ComboBox.ItemTemplate>

                <DataTemplate>

                    <TextBlock Text="{Binding}" FontFamily="{Binding}" FontSize="15" Height="20"/>

                </DataTemplate>

            </ComboBox.ItemTemplate>

        </ComboBox>

 

and

fontComboSlow.ItemsSource = Fonts.SystemFontFamilies;

gives you a nice drop down list with font preview:

image_thumb6

However there is one caveat: The first time you run this and click on the drop down arrow, you will have to wait for some annoying time. The exact time depends on your hardware, and on how many fonts you have installed. If you click on the drop down arrow again, it is fast. If you close the program, and run the program again, it is still fast.

Why is it still fast if you run the program the second time? If you go to the task manager, and show the processes of all users, you can find that WPF is running its own process, called the "Windows Presentation Foundation Font Cache Service":

image_thumb5 

If you kill this process, and run the software again, the first click on the drop down arrow is slow again. I have been looking for a solution to this for some time. Recently, the WPF SDK blog had a post about improving the performance of the ComboBox, recommending a VirtualizingStackPanel as ItemsPanel for the ComboBox in such cases. They also explain the delay:

This reason for the delay is that by default, the ComboBox uses a StackPanel, and a visual that represents each item is created at the same time, which is processor-intensive and memory-intensive.

Sure enough, if you change the XAML for the font drop down list to utilize the VirtualStackPanel as recommended:

        <ComboBox x:Name="fontComboFast">

            <ComboBox.ItemsPanel>

                <ItemsPanelTemplate>

                    <VirtualizingStackPanel />

                </ItemsPanelTemplate>

            </ComboBox.ItemsPanel>

            <ComboBox.ItemTemplate>

                <DataTemplate>

                    <TextBlock Text="{Binding}" FontFamily="{Binding}" FontSize="15" Height="20"/>

                </DataTemplate>

            </ComboBox.ItemTemplate>

        </ComboBox>

the annoying delay is gone. If you scroll fast through the list of fonts, you can instead notice sometimes a very brief delay for a fraction of a second, when the system is loading a new font that was not shown so far.

You can download a sample program, where you can compare the two approaches.