A couple of ideas for you
Idea 1: Do the font fetching entirely on the background thread.
The fonts are actually loaded into the system font cache (inter-process) and then part of the font information is copied into the thread-specific font cache. It is possible that filling the system font cache would result in a good enough increase in speed. This could be done by a low priority background thread that starts running the instant your app is started. So by the time the user drops down the font list the system font cache should be fully populated.
Idea 2: Cache the rendered font geometry yourself
Instead of using TextBlocks, use ContentPresenter objects in your ComboBox's DataTemplate with the content bound to a PriorityBinding. The lower priority would produce a TextBlock using the default font, and the higher priority would be an IsAsync binding that would create a GlyphRun with the appropriate parameters, call BuildGeometry() on it, and return the Geometry inside a Path object. The created Geometry objects can be cached and returned again for future accesses to the same font.
The result of this will be that items will initially appear in the default font, and render into the styled font as soon as the fonts can be loaded and their geometry created. Note that this can be combined with code that prefills your cache in a separate thread.
The code for Idea 2 would look something like this:
<ComboBox ItemsSource="{Binding MyFontObjects}">
<ComboBox.ItemTemplate>
<ContentPresenter>
<ContentPresenter.Content>
<PriorityBinding>
<Binding IsAsync="True" Path="BuildStyledFontName" />
<Binding Path="BuildTextBlock" />
</PriorityBinding>
... close all tags ...
Where MyFontObjets would be a IEnumerable of objects something like this:
public class MyFontObject
{
public FontFamily Font { get; set; }
public object BuildTextBlock
{
get { return new TextBlock { Text = GetFamilyName(Font) } }
}
public object BuildStyledFontName
{
get
{
return new Path { Data = GetStyledFontGeometryUsingCache() };
}
}
private Geometry GetStyledFontGeometryUsingCache()
{
Geometry geo;
lock(_fontGeometryCache)
if(_fontGeometryCache.TryGetValue(Font, out geo) return geo;
lock(_fontGeometryBuildLock)
{
lock(_fontGeometryCache)
if(_fontGeometryCache.TryGetValue(Font, out geo) return geo;
geo = BuildStyledFontGeometry();
lock(_fontGeometryCache)
_fontGeometryCache[Font] = geo;
}
}
static object _fontGeometryCache = new Dictionary<FontFamily, Geometry>();
static object _fontGeometryBuildLock = new object();
private Geometry BuildStyledFontGeometry()
{
var run = new GlyphRun
{
Characters = GetFamilyName(Font),
GlyphTypeface = GetGlyphTypeface(Font),
}
return run.BuildGeometry();
}
... GetFamilyName ...
... GetGlyphTypeface ...
// Call from low priority background thread spawned at app startup
publc static void PrefillCache()
{
foreach(FontFamily font in Fonts.SystemFontFamilies)
new MyFontObject { Font = font }.GetStyledFontGeometryUsingCache();
}
}
Note that the Geometry objects in the cache could be saved to disk by converting them to PathGeometry and thence to strings in the PathGeometry mini-language. This would allow the font geometry cache to be filled using a single file read & parse, so the only time you would see any delay was when you first ran the app, or when you ran it with a large number of new fonts.