在傳統桌面程式中,對圖示的使用大多是直接嵌入JPG或者PNG的圖片,在祖傳的1366x768解析度下,并沒有什么問題,相對于手機硬體的突飛猛進,也側面反映了PC行業的落寞和桌面程式開發的不思進取,用360衛士的群眾并不能倒推PC行業的升級,反倒是水果公司雙高的利潤和口碑讓人很是眼饞,加之某軟跳出來教豬隊友做硬體,現在倒是有些起色,1080p的螢屏已是標配,4k也算常見,那么傳統桌面程式在升級程序中,就會遇到今天要討論的,如何解決高解析度下圖示模糊的問題,
一種解決方案是按最高的解析度提供圖片,這種適合較大的圖片,比如背景啥的,另一種就是今天要討論的,針對當前流行的、扁平化圖示的解決方案,

從本篇的標題可以看出,我們希望應用SVG矢量圖來適應各種解析度的情況,以WPF程式為例,首先要面對的問題是,WPF并不支持像嵌入JPG/PNG圖示這樣,直接使用SVG圖示,大動干戈的參考第三方library通過自定義型別來支持SVG并不是本文的目的,這里我們要介紹如何通過字體檔案,進而在WPF或UWP中使用SVG圖示的方式,
雖然WPF不支持直接使用SVG檔案,但是Windows是支持矢量字體的,而我們的目的就是要將圖示以字體的形式在WPF程式中顯示,具體使用的字體TrueType,則是由微軟和蘋果共同開發的字體型別標準,該字體檔案的擴展名是.ttf,
https://en.wikipedia.org/wiki/TrueType
接下來我們依然是通過Sample工程來說明,首先給出GitHub的地址:
https://github.com/manupstairs/WpfAppForFontIcon
首先我們打開WpfAppWithPNGs工程,圖示的使用代碼如下:
<Image Grid.Row="0" Grid.Column="0" Width="32" Height="32" Source="Resources/Airplane_Off.png" ></Image> <Image Grid.Row="0" Grid.Column="1" Width="64" Height="64" Source="Resources/Airplane_On.png" ></Image> <Image Grid.Row="0" Grid.Column="2" Width="96" Height="96" Source="Resources/Bluetooth_Off.png" ></Image> <Image Grid.Row="0" Grid.Column="3" Width="128" Height="128" Source="Resources/Bluetooth_On.png" ></Image>

這里主要有兩個問題,因為我們默認提供的是32x32的圖示,因此除了第一列Width和Height設定為32的圖示,其他的圖示都存在模糊的問題,第二個問題是針對圖示的每一種顏色,都需要對應提供不同的圖示檔案(圖中的例子需要有灰色和藍色兩份檔案),相對的SVG圖示僅僅需要一份檔案,即可在程式中動態設定不同的顏色了,
這里先給出最終WPF專案中,對SVG圖示的參考的代碼,然后我們再進行詳細解釋,對應的工程名為WpfAppWithFontIcons,
<TextBlock Grid.Row="0" Grid.Column="0" Text="{x:Static local:FontIcons.airplane_mode_circ}" Foreground="Gray" FontSize="32" ></TextBlock> <TextBlock Grid.Row="0" Grid.Column="1" Text="{x:Static local:FontIcons.airplane_mode_circ}" Foreground="{StaticResource dellBlue}" FontSize="64" ></TextBlock> <TextBlock Grid.Row="0" Grid.Column="2" Text="{x:Static local:FontIcons.bluetooth_inactive}" Foreground="Orange" FontSize="96" ></TextBlock> <TextBlock Grid.Row="0" Grid.Column="3" Text="{x:Static local:FontIcons.bluetooth_inactive}" Foreground="Brown" FontSize="128" ></TextBlock>
代碼最大的不同應該是由<Image/>標簽更改為<TextBlock/>標簽,這是因為我們是通過ttf字體檔案,曲線救國的方式來使用SVG圖示,
具體的步驟如下:
準備SVG圖示檔案,將這些檔案打包成一整個ttf字體檔案,打包的方式有很多種,通常我使用的是IcoMoon的免費解決方案,地址如下:
https://icomoon.io/app/#/select
通過這個網站選擇SVG圖示檔案上傳,打包生成一個zip檔案,解壓后檔案夾結構如下圖:

ttf檔案在fonts檔案夾中,實際使用時,需要作為資源檔案,添加到WPF工程中,點擊圖中的demo.html會打開一個本地網頁,可用于查找ttf檔案中包含的SVG圖示,以及對應的unicode,實際我們是通過對unicode的參考來顯示SVG圖示的,

完整的project結構如下圖,Fonts檔案夾是手動添加用來放置ttf檔案,ttf檔案名字都是根據專案需要來取,并不固定,

ttf字體檔案需要以<FontFamily/>的形式添加到專案的<Resources/>節點中,然后再通過<Style/>指定給<TextBlock/>,當然不在<Resources/>節點定義Style,而是在每個<TextBlock/>中指定FontFamily屬性也是可以的,有關XAML的語法細節,回字的四種寫法什么的,這里略過不提,
<Window.Resources> <FontFamily x:Key="Fonticon">/Fonts/rcc-fonticon-ribbon-v2.ttf#rcc-fonticon-ribbon-v2</FontFamily> <Style TargetType="TextBlock"> <Setter Property="FontFamily" Value="{StaticResource Fonticon}" ></Setter> </Style> <SolidColorBrush x:Key="dellBlue">#007DB8</SolidColorBrush> </Window.Resources>
這里說明一下“/Fonts/rcc-fonticon-ribbon-v2.ttf#rcc-fonticon-ribbon-v2”值的定義,#前面的是檔案路徑,#后面的是font name,查看的方法是雙擊ttf檔案,參考下圖,

在定義好FontFamily之后,我們并不推薦直接將unicode寫到XAML或.cs檔案中,因為在XAML中,你需要如下撰寫:
<TextBlock Grid.Row="0" Grid.Column="0" Text="" Foreground="Gray" FontSize="32" ></TextBlock>
而在C#代碼中,又需要以下面這種格式:
textBlockAirplane.Text = "\ue900";
兩種不統一的格式會在將來修改時帶來極大的困難,特別是圖示被多處參考時,全域的查找替換根本就是噩夢,此外,毫無意義的unicode值的可讀性根本等于0,正常人類無法將"","\ue900"和Airplane的圖示聯系起來,
我推薦的做法是生成一個FontIcons Class,以string型別屬性的形式暴露出來,這樣可以獲得IDE智能語法提示的支持,更新時也僅需修改這個Class,Find All Reference更是方便無比,同時無論在XAML檔案,還是C#代碼中,我們看到的都是統一的“FontIcons.airplane_mode_circ”,
public static class FontIcons { public static string airplane_mode_circ { get; } = "\ue900"; public static string bluetooth_inactive { get; } = "\ue901"; public static string brightness { get; } = "\ue902"; public static string brightness_inactive { get; } = "\ue903"; public static string browse_inactive { get; } = "\ue904"; public static string camera { get; } = "\ue905"; }
那么我們是不是需要手工來撰寫FontIcons Class呢?大哥我們是能把午飯(我不愛喝咖啡)轉換成Code的生物啊!當然是寫個小工具來自動生成了,在Sample庫中,參考IcoMoonReader工程,只需將IcoMoon生成的.svg檔案(icomoon.zip解壓后的fonts檔案夾里)丟在IconMoonReader.exe同級目錄,即可生成相應代碼,

其實只有一個方法啦,使用時需要注意具體的檔案名是否正確,
using (var stream = new FileStream("rcc-fonticon-ribbon-v2.svg", FileMode.Open)) { using (var reader = new StreamReader(stream)) { var pattern = "unicode(\\S)*\\sglyph-name(\\S)*\""; var input = reader.ReadToEnd(); foreach (Match match in Regex.Matches(input, pattern)) { pattern = "\"\\S*\""; var list = new List<string>(); foreach (var result in Regex.Matches(match.Value, pattern)) { list.Insert(0, result.ToString()); } var name = list[0].Replace("\"", "").Replace("-","_"); var code = list[1].Replace("&#x", "\\u").Replace(";", ""); Console.WriteLine($"public static string {name} {{ get; }} = {code};"); } } }
把生成的C#字串定義貼到具體工程的FontIcons Class(名字隨意),

這樣一個優秀的解決方案如果僅支持WPF,那又談何遷移到MS Store呢?實際上這套機制放到UWP工程中也是可以的,雖然UWP可以通過SvgImageSource屬性原生支持SVG了,但我們的這套方案在圖示的應用方面毫不遜色,甚至可以說更為方便,具體的例子可以參考AppWithFontIcon工程,在這個UWP的工程中,除了放ttf檔案的位置我換到了現成的Assets檔案夾,幾乎沒有改變,

<TextBlock Grid.Row="0" Grid.Column="0" Text="{x:Bind local:FontIcons.airplane_mode_circ}" Foreground="Gray" FontSize="32" ></TextBlock> <TextBlock Grid.Row="0" Grid.Column="1" Text="{x:Bind local:FontIcons.airplane_mode_circ}" Foreground="{StaticResource dellBlue}" FontSize="64" ></TextBlock> <TextBlock Grid.Row="0" Grid.Column="2" Text="{x:Bind local:FontIcons.bluetooth_inactive}" Foreground="Orange" FontSize="96" ></TextBlock> <TextBlock Grid.Row="0" Grid.Column="3" Text="{x:Bind local:FontIcons.bluetooth_inactive}" Foreground="Brown" FontSize="{x:Bind DynamicFontSize(),Mode=OneWay,FallbackValue=https://www.cnblogs.com/manupstairs/p/128}" ></TextBlock>
因為UWP沒有了x:static關鍵字,所以我換成了x:Bind,換成x:Bind之后甚至可以動態的回應值的變化,比如我在這里把FontSize做了一個x:bind到DynamicFontSize()方法,讓字體隨著界面改變,動態的變大變小,雖然并沒有什么卵用……但是Demo的時候可以增加點噱頭……
本篇到此結束,照例貼上Github地址:
https://github.com/manupstairs/WpfAppForFontIcon
感謝耐著性子看到這里的同學!
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/1701.html
標籤:UWP
