WPF、インストールされているフォント一覧取得、Fonts.SystemFontFamiliesそのままでは不十分だった
結果
左がFonts.SystemFontFamiliesからそのまま取得できたフォントリストで303種、右はそれプラスひと手間かけて取得したフォントリストで376種
Arialっていうフォントで見ると左はArialとArial Unicode MSの2種類しか取得されていないけど、右ではArial BlackとArial Narrowも取得されている
次のBahnschriftってフォントに至っては左では1種だけど、実際には12種類もあった
コード
/// <summary> /// SystemFontFamiliesから日本語フォント名で並べ替えたフォント一覧を返す、1ファイルに別名のフォントがある場合も取得 /// </summary> /// <returns></returns> private SortedDictionary<string, FontFamily> GetFontFamilies() { //今のPCで使っている言語(日本語)のCulture取得 //var language = // System.Windows.Markup.XmlLanguage.GetLanguage( // CultureInfo.CurrentCulture.IetfLanguageTag); CultureInfo culture = CultureInfo.CurrentCulture;//日本 CultureInfo cultureUS = new("en-US");//英語?米国? List<string> uName = new();//フォント名の重複判定に使う Dictionary<string, FontFamily> tempDictionary = new(); foreach (var item in Fonts.SystemFontFamilies) { var typefaces = item.GetTypefaces(); foreach (var typeface in typefaces) { _ = typeface.TryGetGlyphTypeface(out GlyphTypeface gType); if (gType != null) { //フォント名取得はFamilyNamesではなく、Win32FamilyNamesを使う //FamilyNamesだと違うフォントなのに同じフォント名で取得されるものがあるので //Win32FamilyNamesを使う //日本語名がなければ英語名 string fontName = gType.Win32FamilyNames[culture] ?? gType.Win32FamilyNames[cultureUS]; //string fontName = gType.FamilyNames[culture] ?? gType.FamilyNames[cultureUS]; //フォント名で重複判定 var uri = gType.FontUri; if (uName.Contains(fontName) == false) { uName.Add(fontName); tempDictionary.Add(fontName, new(uri, fontName)); } } } } SortedDictionary<string, FontFamily> fontDictionary = new(tempDictionary); return fontDictionary; }
戻り値はKeyがフォント名、ValueがFontFamilyのSortedDictionary形式
Sortedなのでフォント名順で並んだ状態
Fonts.SystemFontFamiliesで取得したFontFamilyのGetTypefaces()でTypefaceを取得、
TypefaceのTryGetGlyphTypeface()でGlyphTypefaceを取得、
このGlyphTypefaceから本当のフォント名とUri(フォントファイルのパスみたいなの)が取得できるので、これを使って改めてFontFamilyを作成して、フォント名をKey、FontFamilyをValueにしたDictionaryに詰め込んで、最後にSortedDictionaryで並べ替えをして完成
もっと楽な方法がありそうだけど、今回はこうなった
本当のフォント名はWin32FamilyNamesで
フォント名に関係あるGlyphTypefaceのプロパティには2種類あって
- FamilyNames
- Win32FamilyNames
Arial Narrowフォントの場合でみてみると
FamilyNamesでは
Win32FamilyNamesだと
ってことでフォント名の取得にはWin32FamilyNamesプロパティからするほうがいい
で、このプロパティはKey、ValueのDictionary形式で、欲しいのはフォント名のValue、これを引き出すためのKeyの型はCultureInfo、この聞き慣れない型は国(言語)を識別するためのものらしくて、フォントにはそれぞれの国の言語に合わせて表示できるようにCultureInfoが使われているみたい。
using System.Globalization; CultureInfo culture = CultureInfo.CurrentCulture;//日本 CultureInfo cultureUS = new("en-US");//英語?米国?
基本は英語名で、日本語フォントだと英語名と日本語名の2種類が入っていることが多い感じだった
Uri、ファイルのパス
さっきのArialとArial Narrowだと、それぞれファイルのパスというかファイル名が違うからわかりやすい
Arialは
Arial Narrowは末尾にnが付いている
これはいい、これはわかる
1つのフォントファイルに複数のフォント
これがFontFamilyって呼ばれる所以ってことかしらねえ
他にはMeiryo.ttcを参照しているフォントだと
フォント名で分けると2種類だけど、Uriだと4種類、拡張子の後に#と数値が付いている、わけがわからん
Fontフォルダを見てみる
このファイルを開くと
9種類のフォントが入っていた、やや狭いってあるのがArial Narrowで極太ってのがArial Blackだった
Bahnschrift
15種類も入っている
フォント一覧表示のアプリ
ダウンロード先
作成、動作環境
- Windows 10 Home バージョン 21H1
- Visual Studio Community 2019
- WPF
- C#
- .NET 5
動作に必要なのは.NET 5がインストール済みのWindows、.NET Frameworkだけでは動かないはず
Windows 11なら.NET 5にインストールは不要かも
アプリのコード
MainWindow.xaml
<Window x:Class="_20211208_フォント一覧表示.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:_20211208_フォント一覧表示" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid UseLayoutRounding="True"> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <DockPanel Grid.Column="0" Margin="10"> <TextBlock DockPanel.Dock="Top" x:Name="MyTextBlock1" FontSize="20" Background="MediumAquamarine" Foreground="White" TextAlignment="Center"/> <ListBox Name="MyListBox1" FontSize="20"> <ListBox.ItemTemplate> <DataTemplate> <!--Fonts.SystemFontFamilysを直接ItemsSourceにする場合--> <!--<TextBlock Text="{Binding Source}" FontFamily="{Binding }"/>--> <TextBlock Text="{Binding Key}" FontFamily="{Binding Value}"/> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </DockPanel> <DockPanel Grid.Column="1" Margin="10"> <TextBlock DockPanel.Dock="Top" x:Name="MyTextBlock2" FontSize="20" Background="MediumOrchid" Foreground="White" TextAlignment="Center"/> <ListBox Name="MyListBox2" FontSize="20"> <ListBox.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Key}" FontFamily="{Binding Value}"/> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </DockPanel> </Grid> </Window>
このXAMLではListBoxのDataTemplateをいじってTextBlockのTextにKey、FontFamilyにValueを指定しているだけ、あとはC#のほうでこのListBoxのItemsSourceにフォントリストのDictionaryを渡せば、フォント名がそのフォントで表示される
こういうのはWPFは楽ちんねえ
MainWindow.xaml.cs
using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Media; using System.Globalization; using System.Windows.Markup; namespace _20211208_フォント一覧表示 { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); MyListBox1.ItemsSource = GetFontFamilies2(); MyTextBlock1.Text = "Fonts.SystemFontFamilies\nフォント数:" + MyListBox1.Items.Count.ToString(); //MyListBox1.ItemsSource = Fonts.SystemFontFamilies; MyListBox2.ItemsSource = GetFontFamilies(); MyTextBlock2.Text = "Fonts.SystemFontFamilies+ひと手間\nフォント数:" + MyListBox2.Items.Count.ToString(); } /// <summary> /// SystemFontFamiliesから日本語フォント名で並べ替えたフォント一覧を返す、1ファイルに別名のフォントがある場合も取得 /// </summary> /// <returns></returns> private SortedDictionary<string, FontFamily> GetFontFamilies() { //今のPCで使っている言語(日本語)のCulture取得 //var language = // System.Windows.Markup.XmlLanguage.GetLanguage( // CultureInfo.CurrentCulture.IetfLanguageTag); CultureInfo culture = CultureInfo.CurrentCulture;//日本 CultureInfo cultureUS = new("en-US");//英語?米国? List<string> uName = new();//フォント名の重複判定に使う Dictionary<string, FontFamily> tempDictionary = new(); foreach (var item in Fonts.SystemFontFamilies) { var typefaces = item.GetTypefaces(); foreach (var typeface in typefaces) { _ = typeface.TryGetGlyphTypeface(out GlyphTypeface gType); if (gType != null) { //フォント名取得はFamilyNamesではなく、Win32FamilyNamesを使う //FamilyNamesだと違うフォントなのに同じフォント名で取得されるものがあるので //Win32FamilyNamesを使う //日本語名がなければ英語名 string fontName = gType.Win32FamilyNames[culture] ?? gType.Win32FamilyNames[cultureUS]; //string fontName = gType.FamilyNames[culture] ?? gType.FamilyNames[cultureUS]; //フォント名で重複判定 var uri = gType.FontUri; if (uName.Contains(fontName) == false) { uName.Add(fontName); tempDictionary.Add(fontName, new(uri, fontName)); } } } } SortedDictionary<string, FontFamily> fontDictionary = new(tempDictionary); return fontDictionary; } /// <summary> /// SystemFontFamiliesから日本語フォント名で並べ替えたフォント一覧を返す /// </summary> /// <returns></returns> private SortedDictionary<string, FontFamily> GetFontFamilies2() { //今のPCで使っている言語(日本語)取得 XmlLanguage language = XmlLanguage.GetLanguage( CultureInfo.CurrentCulture.IetfLanguageTag); //英語のXmlLanguage取得 XmlLanguage[] lang0 = FontFamily.FamilyNames.Select(a => a.Key).ToArray(); List<string> uName = new();//フォント名の重複判定に使う Dictionary<string, FontFamily> tempDictionary = new(); foreach (var item in Fonts.SystemFontFamilies) { //フォント名取得、日本語名がなければ英語名 string name; if (item.FamilyNames.TryGetValue(language, out name) == false) { name = item.FamilyNames[lang0[0]];//[0]は英語 } //フォント名で重複判定 if (uName.Contains(name) == false) { uName.Add(name); tempDictionary.Add(name, item); } } SortedDictionary<string, FontFamily> fontDictionary = new(tempDictionary); return fontDictionary; } } }
SystemFontFamiliesからそのままのGetFontFamilies2()は、日本語フォント名取得と、それでの並び替えをしているから長くなってしまった、これももっといい方法がありそう。
日本語フォント名も並び替えも必要ないならSystemFontFamiliesをそのまま
MyListBox1.ItemsSource = Fonts.SystemFontFamilies;
こう渡して、XAMLのほうでのBindingを
<TextBlock Text="{Binding Source}" FontFamily="{Binding }"/>
たったこれだけでも
ある程度表示できる
参照したところ
今回の要になっているFontFamilyからGlyphTypefaceを取得する方法はここから
resanaplaza.com
ListBoxとのBindingはこちらから
感想、WinFormsと比較してみる
Arial BlackやArial Narrow他、WPFのSystemFontFamiliesそのままでは取得できないフォントもある
これがあったからWPFだと、なんか足りないなあって気づいた
このアプリは
ここを見ながら作っていた、フォント一覧取得もこのdobonさんのサイトにあるそのままの方法で苦もなくできたんだよねえ、WPFでも簡単だろうと思いつつ検索して、あちこちで紹介されていたのがSystemFontFamiliesを使った方法なんだけど、試したらなんか足りない、どうしたらいいのかといろいろ試してたどり着いたのが今回の回りくどい方法、しかもForEachの中にIfの中にIfとかで長くなった、LINQを使いこなせればかなり短くできるはずなんだけど難しくてわからん、でもできてよかったわ
インストールされていないフォントも表示させたかったんだけど、ちょっと調べたらWPFだとアプリに埋め込まないとできない感じで、そこまでするんだったらインストールしたほうが早いので諦めた
それにしてもインストールされているフォント一覧取得がここまでめんどくさいはずないと思うんだよねえ
Visual Studioは2022をインストールしたけどメモリ使用量が多すぎて、メモリ16GBのPCではムリで、2022のアイコンを横目に見ながら2019を使っているのは悲しいw
関連記事