从字体符号(Font Awesome)为 WPF 创建图像






4.94/5 (15投票s)
本文介绍了一个控件,可以非常方便地从字体符号创建Image控件,并特别支持Font Awesome。
引言
我当时正在做一个项目,需要使用很多图标。我从网上挑选它们,但总是有关于一致性和图像版权的问题——换句话说,我需要免费图标。我的一位同事向我推荐了Font Awesome(http://fortawesome.github.io/Font-Awesome/icons/),并说我应该使用这种字体来创建我的图标。我喜欢这个想法。我最终开始研究将Font Awesome整合到我的项目中,这实际上非常容易。我很容易找到一个名为ImageFromFont
的类(https://codeproject.org.cn/Tips/634540/Using-Font-Icons),并且它运行良好。但是,它派生自MarkupExtension
类,而不是DependencyObject
,因此不能拥有DependencyProperty
。我查看了代码并进行了一些思考,然后想到如果我创建一个继承自Image
的控件,并且可以拥有我想要的任何DependencyProperty
,我也可以做同样的事情。在这种情况下,我希望能够使用Foreground
属性指定字体颜色,并能够将其绑定到StaticResource
。
最初我是围绕 Font Awesome 字体构建的,但我对他们对新符号建议的接受度不高感到不满——我认为一些非常好的建议被拒绝了,例如添加行星和占星符号以及道路符号,这些都不应该被拒绝。我决定也许我应该让这个控件更灵活一些,并进行了一些重命名,以便将与 Font Awesome 的关联更具体地用于枚举和DependencyProperty
。然后我添加了一个名为Character
的新属性,类型为char
,这是实际用于生成字形的。现在,更改现在命名的FontAwesomeSymbol
只会更改Character
DependencyProperty
,这反过来又会强制更新图像。现在可以使用任何FontFamily
和Character
DependencyProperty
作为图像的源。如果使用FontAwesomeSymbol
DependencyProperty
,它仍然是透明的,并且 Font Awesome 的枚举值仍然可用。目前在重命名的FontAwesomeSymbols
枚举中提供了300多个枚举。
代码
我认为这段代码需要三个重要的属性:FontFamily
、Foreground
Brush
、Character
以及要显示的FontAwesomeSymbol
。后来我添加了一个Rotation
DependencyProperty
,允许图像按指定的度数旋转。我将所有这些都实现为DependencyProperty
,以便值可以是动态的。
public FontFamily FontFamily
{
get { return (FontFamily)GetValue(FontFamilyProperty); }
set { SetValue(FontFamilyProperty, value); }
}
public static readonly DependencyProperty FontFamilyProperty =
DependencyProperty.Register("FontFamily", typeof(FontFamily), typeof(FontSymbolImage),
new PropertyMetadata(null, UpdateImage));
public FontAwesomeSymbols FontAwesomeSymbol
{
get { return (FontAwesomeSymbols)GetValue(FontAwesomeSymbolProperty); }
set { SetValue(FontAwesomeSymbolProperty, value); }
}
public static readonly DependencyProperty FontAwesomeSymbolProperty =
DependencyProperty.Register("FontAwesomeSymbol", typeof(FontAwesomeSymbols), typeof(FontSymbolImage),
new PropertyMetadata(FontAwesomeSymbols.fa_smile_o, UpdateFontAwesome));
public char Character
{
get { return (char)GetValue(CharacterProperty); }
set { SetValue(CharacterProperty, value); }
}
public static readonly DependencyProperty CharacterProperty =
DependencyProperty.Register("Character", typeof(char), typeof(FontSymbolImage),
new PropertyMetadata(' ', UpdateImage));
public double Rotation
{
get { return (double)GetValue(RotationProperty); }
set { SetValue(RotationProperty, value); }
}
public static readonly DependencyProperty RotationProperty =
DependencyProperty.Register("Rotation", typeof(double), typeof(FontSymbolImage),
new PropertyMetadata(0.0, UpdateImage));
public Brush Foreground
{
get { return (Brush)GetValue(ForegroundProperty); }
set { SetValue(ForegroundProperty, value); }
}
public static readonly DependencyProperty ForegroundProperty =
DependencyProperty.Register("Foreground", typeof(Brush), typeof(FontSymbolImage),
new PropertyMetadata(new SolidColorBrush(Colors.Black), UpdateImage));
public FlipValues Flip
{
get { return (FlipValues)GetValue(FlipProperty); }
set { SetValue(FlipProperty, value); }
}
public static readonly DependencyProperty FlipProperty =
DependencyProperty.Register("Flip", typeof(FlipValues), typeof(FontSymbolImage),
new PropertyMetadata(FlipValues.None, UpdateFontAwesome));
FontAwesomeSymbol
属性是一个enum
,这样设置它的值更容易,不必总是查找与Font Awesome符号关联的数字,可以使用枚举的名称来帮助确定符号。这使得XAML
更容易,因为符号的名称可以很容易地与图像关联起来。
每个DependencyProperty
在值更改时都使用了相同的方法。
private static void UpdateImage(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var imageControl = (FontSymbolImage)d;
var glyphRunDrawing = CreateGlyph(imageControl.Character.ToString(), imageControl.FontFamily,
FontStyles.Normal, FontWeights.Normal, FontStretches.Normal, imageControl.Foreground);
if (glyphRunDrawing == null) return;
if (Math.Abs(imageControl.Rotation) < .1 && imageControl.Flip == FlipValues.None)
{
imageControl.Source = new DrawingImage(glyphRunDrawing);
}
else
{
var drawingGroup = new DrawingGroup();
drawingGroup.Children.Add(glyphRunDrawing);
TransformImage(drawingGroup, imageControl.Rotation, imageControl.Flip);
imageControl.Source = new DrawingImage(drawingGroup);
}
}
粗体代码是在Rotation
DependencyProperty
中添加的,并且翻转(Flip)DependencyProperty
功能已添加到项目中。
FontAwesomeSymbol
DependencyProperty
实际上会调用另一个方法UpdateFontAwesome
,该方法将枚举转换为适当的字符,然后更新Character
DependencyProperty
。
private static void UpdateFontAwesome(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var imageControl = (FontSymbolImage)d;
var index = ConvertToChar((FontAwesomeSymbols)e.NewValue);
imageControl.Character = index;
}
改变的Character
DependencyProperty
随后导致UpdateImage
事件处理程序被调用。
基本上,这个方法调用两个static
方法,然后设置Image
的Source。一个方法,ConvertToChar
,接受符号枚举并将其转换为Font Awesome字体中字符的ID。另一个将此符号转换为Drawing
类的实例,然后可以将其转换为Source
属性所需的DrawingImage
实例。
private static Drawing CreateGlyph(string text, FontFamily fontFamily, FontStyle fontStyle,
FontWeight fontWeight, FontStretch fontStretch, System.Windows.Media.Brush foreBrush)
{
if (fontFamily != null && !string.IsNullOrEmpty(text))
{
//first test in charge the police directly
Typeface typeface = new Typeface(fontFamily, fontStyle, fontWeight, fontStretch);
GlyphTypeface glyphTypeface;
if (!typeface.TryGetGlyphTypeface(out glyphTypeface))
{
//if it does not work (for design and fashion in some cases) is added the uri pack://application
typeface = new Typeface(new FontFamily(new Uri("pack://application:,,,"),
fontFamily.Source), fontStyle, fontWeight, fontStretch);
if (!typeface.TryGetGlyphTypeface(out glyphTypeface))
throw new InvalidOperationException("No glyphtypeface found");
}
//determination of the indices / sizes of the characters in the font
var glyphs = new ushort[text.Length];
var advanceWidths = new double[text.Length];
for (var n = 0; n < text.Length; n++)
{
var glyph = glyphs[n] = GetGlyph(text[n], glyphTypeface);
advanceWidths[n] = glyphTypeface.AdvanceWidths[glyph];
}
try
{
//creation Drawing Image object (compatible with ImageSource) from a GlyphRun
var glyphRun = new GlyphRun(glyphTypeface, 0, false, 1.0, glyphs,
new System.Windows.Point(0, 0), advanceWidths, null, null, null, null, null, null);
var glyphRunDrawing = new GlyphRunDrawing(foreBrush, glyphRun);
return glyphRunDrawing;
}
catch (Exception ex)
{
Debug.WriteLine("Error in generating Glyph Run: " + ex.Message);
}
}
return null;
}
private static char ConvertToChar(FontAwesomeSymbols symbolEnum) => (char)(int)symbolEnum;
此方法几乎是照搬自上面提到的ImageFromFont
类,但我进行了一些更改,包括返回Drawing
而不是DrawingSource
。我这样做是因为我希望最终增强这个项目,并且认为Drawing
是一个更灵活的类。
使用代码
除了获取对Font Awesome TrueType字体的引用之外,使用这段代码非常简单。
<Button Margin="50"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<fontAwesomeImageSample:FontSymbolImage Foreground="HotPink"
FontFamily="{StaticResource FontAwesomeTtf}"
FontAwesomeSymbol="fa_building_o"
Rotation="10" />
</Button>
Foreground
属性应该非常明显。FontFamily
则不那么明显,除非你经常处理必须包含在项目中的自定义字体。在这种情况下,我将字体添加到了项目的根目录,因为我不想依赖字体安装在我正在运行的系统上。此字体可能不会安装在你的根目录中,因此需要指定其路径。下图显示了我的解决方案的外观。你可以看到fontawesome.ttf文件位于解决方案的根目录中。
如果它安装在子目录中,那么你会得到类似这样的结果:
FontFamily="/HolosDisplay;component/Assets/Fonts/#fontawesome"
XAML中的最后一个条目FontAwesomeSymbol
是你在网站上找到的名称,但略有改动。在网站上,如果你查看符号的详细信息,你会看到与每个符号关联的内容,例如:fa-plus-circle
。在HTML中,它会像这样使用:
<i class="fa fa-plus-circle"></i>
我不得不使用一些东西来代替破折号,因为C#不允许名称中使用破折号,所以我使用了下划线。我所做的是创建了一个enum
,并为枚举的每个元素使用了与网站上使用的名称相近的名称。
public enum FontAwesomeSymbols
{
fa_user = 0xf007,
fa_eject = 0xf052,
fa_arrow_circle_left = 0xf0A8,
fa_arrow_circle_right = 0xf0A9,
.
.
.
fa_floppy_o = 0xf0C7,
fa_rotate_left = 0xf0E2,
fa_file_text_o = 0xf0F6,
fa_smile_o = 0xf118,
fa_user_plus = 0xf234,
}
我只在这个enum
中定义了可用符号的一小部分,也就是我项目所需的那些。enum
的数值是一个与符号关联的Unicode。这使得将枚举转换为用于生成符号的Unicode值变得非常容易。这个简单的方法就能做到这一点。
private static char Convert(FontAwesomeSymbols symbolEnum) { var symbolInteger = (int)symbolEnum; return (char)symbolInteger; }
旋转
作为一项增强功能,我添加了旋转图像的功能。以下XAML将为图像添加10度旋转。
<fontAwesomeImageSample: FontSymbolImage Foreground="HotPink" FontFamily="{StaticResource FontAwesome}" FontAwesomeSymbol="fa_building_o" Rotation="10"/>
建议
为了更容易引用Font Awesome字体,我建议将FontFamily
设置在一个中心位置,然后作为StaticResource
引用。如果它在App.xaml文件中,文件将包含以下内容:
<Application x:Class="FontAwesomeImageSample.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
<FontFamily x:Key="FontAwesome">#FontAwesome</FontFamily>
</Application.Resources>
</Application>
那么,任何使用这个FontAwesome控件的地方都将是这样的:
<fontAwesomeImageSample:FontAwesome Foreground="HotPink"
FontFamily="{StaticResource FontAwesome}"
Symbol="fa_arrow_circle_right" />
当字体不在根目录中,而是隐藏在某个资产目录或另一个项目中时,这尤其好用。
需要帮助
利用Font Awesome的网络支持可以做很多事情,如果有人感兴趣,我可能可以毫不费力地添加支持。我真正想做的一件事是能够添加一个符号来稍微修改图像。特别是,我希望能够添加一个加号。Font Awesome有一些带有修改的符号,例如新建和删除,但只有少数几个,这可能是我希望有专门图像的领域,因为我的图像相当大,它是为触摸设计的。问题是我需要能够像OpacityMask
那样做一些事情,然后添加加号符号(将来可能需要删除),这样符号就不会混在一起。我已经调查过了,但目前还没有明显的解决方案。如果能给我指明正确的方向,将会有很大帮助。
额外
项目中包含使用字体符号作为光标的代码。此代码位于FontSymbolCusror.cs
文件中。此代码在MainWindow
的构造函数中初始化。这在https://codeproject.org.cn/Articles/1087610/Creating-a-Cursor-from-a-Font-Symbol中有文档说明。
历史
- 2016年2月29日:初始版本
- 2016年3月4日:更新解决方案,增加更多字符
- 2016年3月11日:更新解决方案,增加旋转功能,并提高字符选择的灵活性。
- 2016年3月26日:编辑、类重命名和更多符号
- 2016年4月27日:更新至Font Awesome 4.6
- 2016年5月17日:更新代码,改进了等待光标实现
- 2016年5月20日:更新代码,仅光标使用新的
BaseWindow
控件