迁移桌面程序到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(, result.ToString());
}
var name = list[].Replace("\"", "").Replace("-","_");
var code = list[].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
感谢耐着性子看到这里的同学!
迁移桌面程序到MS Store(11)——应用SVG图标的更多相关文章
- 迁移桌面程序到MS Store(5)——.NET Standard
接下来的几篇,我想讨论下迁移桌面程序到MS Store,可以采用的比较常见.通用性比较强的实施步骤和分层架构. 通常商业项目一般都是不断的迭代,不太可能突然停止更新现有的桌面版本,然后花很长时间从头来 ...
- 迁移桌面程序到MS Store(1)——通过Visual Studio创建Packaging工程
之前跑去做了一年多的iOS开发,被XCode恶心得不行.做人呢,最重要的是开心.所以我就炒了公司鱿鱼,挪了个窝回头去做Windows开发了. UWP什么的很久没有正儿八经写了,国内的需求 ...
- 迁移桌面程序到MS Store(8)——通过APPX下载Win32Component
在上一篇<迁移桌面程序到MS Store(7)——APPX + Service>中,我们提到将desktop application拆分成UI Client+Service两部分.其中UI ...
- 迁移桌面程序到MS Store(9)——APPX With Desktop Extension
在<迁移桌面程序到MS Store(8)——通过APPX下载Win32Component>中我们讨论了通过APPX来下载Service部分的安装包.但是纯UWP的客户端并不能自动运行下载的 ...
- 迁移桌面程序到MS Store(10)——在Windows S Mode运行
首先简单介绍Windows 10 S Mode,Windows在该模式下,只能跑MS Store里的软件,不能通过其他方式安装.好处是安全有保障,杜绝一切国产流氓软件.就像iOS一样,APP进商店都需 ...
- 迁移桌面程序到MS Store(12)——WPF使用UWP InkToolbar和InkCanvas
我们在<迁移桌面程序到MS Store(4)——桌面程序调用Win10 API>提到了对Win10 API的调用,但仍存在无法在WPF中使用UWP控件的问题,虽然都是XAML控件,但却是两 ...
- 迁移桌面程序到MS Store(13)——动态检查Win10 API是否可用
假设我们现有一个WPF程序,需要支持1903以前的Windows 10版本.同时在1903以后的版本上,额外多出一个Ink的功能.那么我们就可以通过ApiInformation.IsApiContra ...
- 迁移桌面程序到MS Store(14)——APPX嵌入WCF Service以Admin权限运行
Windows10 1809版本开始,微软又对UWP开放了新的Capability:AllowElevation. 通过这个新的Capability,UWP APP能够在运行时向用户请求Admin权限 ...
- 迁移桌面程序到MS Store(2)——Desktop App Converter
迁移传统桌面程序到MS Store的另一种方式是使用Desktop App Converter工具.虽然本篇标题包含了Desktop App Converter(以下简称DAC),实际上我是来劝你别用 ...
- 迁移桌面程序到MS Store(3)——开机自启动
迁移桌面程序的时候,有可能你会遇到这么个需求——开机自启动.Windows传统桌面程序的传统陋习.不论什么奇葩软件都想要开机自启动,默认就给你打开,一开机哐哐哐什么雷,什么企鹅都蹦出来,也不管你用不用 ...
随机推荐
- 购买https证书以及nginx配置https
文章来源 运维公会:购买https证书以及nginx配置https 1.https的作用 https的全名是安全超文本传输协议,是在http的基础上增加了ssl加密协议.在信息传输的过程中,信息有可能 ...
- python编程基础之六
运算符和表达式 +,-,*,/, 加减乘除 %, 模运算 **, 幂运算 // 整除 运算优先级方面:** >正负号(+,-)>//,%>*,/>+,- 模运算有一 ...
- ubuntu14.04 安装tensorflow始末
基于ubuntu14.04 干净的系统一步步遇到的坑记录下来: 怀着平静学习的心情,问题总的能解决的! 1. 首先看了下当前python版本 python --version Python 2.7.6 ...
- DataTableHelper.cs 将DataTable转换为List,将List转换为DataTable的实现类
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...
- 绕过CDN方法整理
来自文章链接:https://zhuanlan.zhihu.com/p/33440472 0x01 判断ip是否为网站真实ip 1. Nslookup: Win下使用nslookup命令进行查询,若返 ...
- 爬虫4:pdf页面+pdfminer模块+demo
本文介绍下pdf页面的爬取,需要借助pdfminer模块 demo一般流程: 1)设置url url = 'http://www.------' + '.PDF' 2)requests模块获取url ...
- [NOIp2014] luogu P1351 联合权值
哎我博 4 了. 题目描述 无向连通图 GGG 有 nnn 个点,n−1n−1n−1 条边.点从 111 到 nnn 依次编号,编号为 iii 的点的权值为 WiW_iWi,每条边的长度均为 111 ...
- Go 零基础 30 min 入门
不知不觉用 Go 开发也两年多了. 筹备点经验汇总, 方便后面的同学能快速上手. 提纲 1. Go 安装 2. Go ide 搭建 3. Go modules 模块管 ...
- enable_shared_from_this
头文件<memory> enable_shared_from_this是一个模板类. 使用场景:需要把自己类对象作为参数传给其他函数时,就需要传递一个指向自身的share_ptr. str ...
- 概念理解:boost::asio::io_service
IO模型 io_service对象是asio框架中的调度器,所有异步io事件都是通过它来分发处理的(io对象的构造函数中都需要传入一个io_service对象). asio::io_service i ...