65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (15投票s)

2016年2月29日

CPOL

7分钟阅读

viewsIcon

38294

downloadIcon

645

本文介绍了一个控件,可以非常方便地从字体符号创建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,这反过来又会强制更新图像。现在可以使用任何FontFamilyCharacter DependencyProperty作为图像的源。如果使用FontAwesomeSymbol DependencyProperty,它仍然是透明的,并且 Font Awesome 的枚举值仍然可用。目前在重命名的FontAwesomeSymbols枚举中提供了300多个枚举。

代码

我认为这段代码需要三个重要的属性:FontFamilyForeground BrushCharacter以及要显示的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控件
© . All rights reserved.