在传统桌面程序中,对图标的使用大多是直接嵌入JPG或者PNG的图片。在祖传的1366x768分辨率下,并没有什么问题。相对于手机硬件的突飞猛进,也侧面反映了PC行业的落寞和桌面程序开发的不思进取。用360卫士的群众并不能倒推PC行业的升级。反倒是水果公司双高的利润和口碑让人很是眼馋。加之某软跳出来教猪队友做硬件。现在倒是有些起色,1080p的屏幕已是标配,4k也算常见。那么传统桌面程序在升级过程中,就会遇到今天要讨论的,如何解决高分辨率下图标模糊的问题。 从本篇的标题可以看出,我们希望应用SVG矢量图来适应各种分辨率的情况。以WPF程序为例,首先要面对的问题是,WPF并不支持像嵌入JPG/PNG图标这样,直接使用SVG图标。大动干戈的引用第三方library通过自定义类型来支持SVG并不是本文的目的。这里我们要介绍如何通过字体文件,进而在WPF或UWP中使用SVG图标的方式。 <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图标仅仅需要一份文件。即可在程序中动态设定不同的颜色了。 <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图标。 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的图标联系起来。 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的时候可以增加点噱头…… |
|