遷移桌面程式到MS Store(11)——應用SVG圖示
在傳統桌面程式中,對圖示的使用大多是直接嵌入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=128}" ></TextBlock>
因為UWP沒有了x:static關鍵字,所以我換成了x:Bind。換成x:Bind之後甚至可以動態的響應值的變化。比如我在這裡把FontSize做了一個x:bind到DynamicFontSize()方法,讓字型隨著介面改變,動態的變大變小。雖然並沒有什麼卵用……但是Demo的時候可以增加點噱頭……
本篇到此結束,照例貼上Github地址:
https://github.com/manupstairs/WpfAppForFontIcon
感謝耐著性子看到這裡的同學!