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

在 WPF 应用程序中从字体符号创建光标

starIconstarIconstarIconstarIconstarIcon

5.00/5 (17投票s)

2016 年 3 月 23 日

CPOL

3分钟阅读

viewsIcon

35604

downloadIcon

784

本文提供了从字体中的字符创建光标的代码。

引言

我需要在我的应用程序中创建一个大的光标,并且由于我已经在用一个符号字体,我决定使用字体中的一个符号作为光标。

注意

代码已经进行了大量更新,以修复自初始版本以来光标出现的问题。光标中的图像没有正确居中,因此光标的实际大小与调用中指定的值不符。沙漏现在也有三个图像,沙子流下,然后旋转90度。 

代码

我最初尝试使用我以前用于从字体生成按钮等图像的代码,该代码包含在代码中,并用于生成示例中的图像。但这不起作用,我不得不搜索互联网并进行了很多尝试。不幸的是,似乎有几个用于绘图的 Microsoft 库,而且我认为创建不同的库时并没有考虑太多。下面的代码最终奏效了。

GlyphRun 的创建

这段代码类似于用于创建示例中所示按钮的图像的代码,但略有不同,因为它似乎我需要的对象与 WPF 中图像所需的略有不同。这段代码的作用是将字体字符转换为位图。

  public static GlyphRun GetGlyphRun(double size, FontFamily fontFamily, string text)
  {
   Typeface typeface = new Typeface(fontFamily, FontStyles.Normal,
    FontWeights.Normal, FontStretches.Normal);
   GlyphTypeface glyphTypeface;
   if (!typeface.TryGetGlyphTypeface(out glyphTypeface))
    throw new InvalidOperationException("No glyphtypeface found");
   ushort[] glyphIndexes = new ushort[text.Length];
   double[] advanceWidths = new double[text.Length];

   for (int n = 0; n < text.Length; n++)
   {
    advanceWidths[n] = glyphTypeface.AdvanceWidths[glyphIndexes[n]
     = GetGlyph(text[n], glyphTypeface)];
   }
   var centerX = (1 - advanceWidths[0]) * size / 2;
   Point origin = new Point(centerX, size * .85);

   GlyphRun glyphRun = new GlyphRun(glyphTypeface, 0, false, size,
     glyphIndexes, origin, advanceWidths, null, null, null, null,
     null, null);

   return glyphRun;
  }

此方法使用另一种方法来获取字符,并处理当字体没有与该位置关联的字符时抛出的异常,用空格符号替换该字符

  private static ushort GetGlyph(char text, GlyphTypeface glyphTypeface)
  {
   try { return glyphTypeface.CharacterToGlyphMap[text]; }
   catch { return 42; }
  }

在创建 GlyphRun 时,GetGlyphRun 方法使用 FontSyleFontWeightFontStretch 的默认值。它实际上会接受一个 string 并使用所有字符创建一个 GlyphRun

创建内存流对象

下一个方法用于创建 Cursor objectCursor 需要特定的二进制格式,因此使用 MemoryStream 来创建二进制 object (有关结构,请参阅 https://en.wikipedia.org/wiki/ICO_(file_format)))。这也可以使用 unsafe 代码完成,但 MemoryStream 方法无需使用 unsafe 关键字即可工作。

  private static Cursor CreateCursorObject(int size, double xHotPointRatio, double yHotPointRatio,
      BitmapSource rtb)
  {
   using (var ms1 = new MemoryStream())
   {
    var penc = new PngBitmapEncoder();
    penc.Frames.Add(BitmapFrame.Create(rtb));
    penc.Save(ms1);

    var pngBytes = ms1.ToArray();
    var byteCount = pngBytes.GetLength(0);

    //.cur format spec <a href="http://en.wikipedia.org/wiki/ICO_(file_format"><font color="#0066cc">http://en.wikipedia.org/wiki/ICO_(file_format</font></a>)
    using (var stream = new MemoryStream())
    {
     //ICONDIR Structure
     stream.Write(BitConverter.GetBytes((Int16) 0), 0, 2); //Reserved must be zero; 2 bytes
     stream.Write(BitConverter.GetBytes((Int16) 2), 0, 2); //image type 1 = ico 2 = cur; 2 bytes
     stream.Write(BitConverter.GetBytes((Int16) 1), 0, 2); //number of images; 2 bytes
     //ICONDIRENTRY structure
     stream.WriteByte(32); //image width in pixels
     stream.WriteByte(32); //image height in pixels

     stream.WriteByte(0); //Number of Colors. Should be 0 if the image doesn't use a color palette
     stream.WriteByte(0); //reserved must be 0

     stream.Write(BitConverter.GetBytes((Int16) (size*xHotPointRatio)), 0, 2);
     //2 bytes. In CUR format: Specifies the number of pixels from the left.
     stream.Write(BitConverter.GetBytes((Int16) (size*yHotPointRatio)), 0, 2);
     //2 bytes. In CUR format: Specifies the number of pixels from the top.

     //Specifies the size of the image's data in bytes
     stream.Write(BitConverter.GetBytes(byteCount), 0, 4);
     stream.Write(BitConverter.GetBytes((Int32) 22), 0, 4);
     //Specifies the offset of BMP or PNG data from the beginning of the ICO/CUR file
     stream.Write(pngBytes, 0, byteCount); //write the png data.
     stream.Seek(0, SeekOrigin.Begin);
     return new System.Windows.Input.Cursor(stream);
    }
   }
  }

翻转和旋转的变换

当存在水平或垂直翻转或旋转时,将调用 TransformImage

  private static void TransformImage(DrawingGroup drawingGroup, double angle, FlipValues flip)
  {
   if (flip == FlipValues.None && Math.Abs(angle) < .1) return;
   if (flip == FlipValues.None)
    drawingGroup.Transform = new RotateTransform(angle);
   if (Math.Abs(angle) < .1)
    drawingGroup.Transform = new ScaleTransform(flip == FlipValues.Vertical ? -1 : 1, flip == FlipValues.Horizontal ? -1 : 1);
   else
   {
    var transformGroup = new TransformGroup();
    transformGroup.Children.Add(new ScaleTransform(flip == FlipValues.Vertical ? -1 : 1, flip == FlipValues.Horizontal ? -1 : 1));
    transformGroup.Children.Add(new RotateTransform(angle));
    drawingGroup.Transform = transformGroup;
   }
  }

基本公共方法调用

这两个方法由 CreateCursor 使用,以返回使用 FontFamily 中指定符号的 Cursor object。 这是用于创建光标 object 的公共方法。

  public static System.Windows.Input.Cursor CreateCursor(int size, double xHotPointRatio,
   double yHotPointRatio, FontFamily fontFamily, string symbol, Brush brush, double rotationAngle = 0)
  {
   var vis = new DrawingVisual();
   using (var dc = vis.RenderOpen())
   {
    dc.DrawGlyphRun(brush, GetGlyphRun(size, fontFamily, symbol));
    dc.Close();
   }/*CreateGlyphRun(symbol, fontFamily, FontStyles.Normal, FontWeights.Normal, FontStretches.Normal)*/
   if (Math.Abs(rotationAngle) > .1) 
     vis.Transform = new RotateTransform(rotationAngle, size / 2, size / 2);
   var renderTargetBitmap = new RenderTargetBitmap(size, size, 96, 96, PixelFormats.Pbgra32);
   renderTargetBitmap.Render(vis);

   return CreateCursorObject(size, xHotPointRatio, yHotPointRatio, renderTargetBitmap);
  }

Using the Code

WPF 中,您可能希望在初始化 Window 时设置光标

  public MainWindow()
  {
   InitializeComponent();
   Mouse.OverrideCursor = FontSymbolCursor.CreateCursor(100, .5, .03, "arial",
    'A'.ToString, System.Windows.Media.Brushes.Black);
  }

BaseWindow 类

在示例中,MainWindow 继承自 BaseWindow 类。

<fontAwesomeImageSample:BaseWindow x:Class="FontAwesomeImageSample.MainWindow"
        xmlns="<a href="http://schemas.microsoft.com/winfx/2006/xaml/presentation">http://schemas.microsoft.com/winfx/2006/xaml/presentation</a>"
        xmlns:x="<a href="http://schemas.microsoft.com/winfx/2006/xaml">http://schemas.microsoft.com/winfx/2006/xaml</a>"
        xmlns:d="<a href="http://schemas.microsoft.com/expression/blend/2008">http://schemas.microsoft.com/expression/blend/2008</a>"
        xmlns:fontAwesomeImageSample="clr-namespace:FontAwesomeImageSample"
        xmlns:mc="<a href="http://schemas.openxmlformats.org/markup-compatibility/2006">http://schemas.openxmlformats.org/markup-compatibility/2006</a>"
        Title="Font Awesome Icon Image & Cursor"
        Width="525"
        Height="350"
        mc:Ignorable="d">
 <Grid>
  <Button Margin="50"
          HorizontalAlignment="Center"
          VerticalAlignment="Center"
          Click="ButtonBase_OnClick">
   <fontAwesomeImageSample:FontSymbolImage Foreground="HotPink"
                                           FontFamily="{StaticResource FontAwesomeTtf}"
                       Flip="Horizontal"
                       Rotation="10"
                                           FontAwesomeSymbol="fa_bar_chart_o" />
  </Button>
 </Grid>
</fontAwesomeImageSample:BaseWindow>

BaseWindow 类具有 IsBusy DependencyProperty 并创建箭头光标和几个忙碌光标。当 IsBusytrue 变为 false 时,Cursor 从其正常的箭头变为沙漏,沙漏会清空然后旋转。为了完成动画,使用了 DispatchTimer。当 IsBusy DependencyProperty 设置为 true 时,它将启动,当它设置为 false 时停止

 public class BaseWindow : Window
 {
  private const int CursorSize = 32;
  private readonly DispatcherTimer _updateTimer;
  private readonly System.Windows.Input.Cursor _normalCursor;
  private readonly System.Windows.Input.Cursor _busyCursor;
  private readonly System.Windows.Input.Cursor[] _busyCursors;
  private int _busyCursorNumber;  public BaseWindow()
  {
   _updateTimer = new DispatcherTimer { Interval = new TimeSpan(0, 0, 1) };
   _updateTimer.Tick += UpdateBusyCursor;
   System.Windows.Input.Mouse.OverrideCursor = _normalCursor 
        = FontSymbolCursor.CreateCursor(CursorSize, .2, 0, "FontAwesome",
          FontSymbolImage.FontAwesomeSymbols.fa_mouse_pointer, System.Windows.Media.Brushes.Black);
   _busyCursors = new[] {
     FontSymbolCursor.CreateCursor(CursorSize, .5, .5, "FontAwesome",
       FontSymbolImage.FontAwesomeSymbols.fa_hourglass_start, System.Windows.Media.Brushes.Black),
     FontSymbolCursor.CreateCursor(CursorSize, .5, .5, "FontAwesome",
       FontSymbolImage.FontAwesomeSymbols.fa_hourglass_half, System.Windows.Media.Brushes.Black),
     FontSymbolCursor.CreateCursor(CursorSize, .5, .5, "FontAwesome",
       FontSymbolImage.FontAwesomeSymbols.fa_hourglass_end, System.Windows.Media.Brushes.Black),
     FontSymbolCursor.CreateCursor(CursorSize, .5, .5, "FontAwesome",
       FontSymbolImage.FontAwesomeSymbols.fa_hourglass_end, System.Windows.Media.Brushes.Black, 90.0)};
  }

  public static readonly DependencyProperty IsBusyProperty =
    DependencyProperty.Register("IsBusy", typeof(bool), typeof(BaseWindow), 
    new PropertyMetadata(false, PropertyChangedCallback));
  public bool IsBusy { 
    get { return (bool)GetValue(IsBusyProperty); } 
    set { SetValue(IsBusyProperty, value); } 
  }

  private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
  {
   var window = (BaseWindow)d;
   if (window.IsBusy)
   {
    window._busyCursorNumber = 0;
    window._updateTimer.Start();
    System.Windows.Input.Mouse.OverrideCursor = window._busyCursors[0];
   }
   else
   {
    window._updateTimer.Stop();
    System.Windows.Input.Mouse.OverrideCursor = window._normalCursor;
   }
  }

  private void UpdateBusyCursor(object sender, EventArgs e)
  {
   _busyCursorNumber = ++_busyCursorNumber % _busyCursors.Length;
   System.Windows.Input.Mouse.OverrideCursor = _busyCursors[_busyCursorNumber];
  }
 }

示例是一个简单的表单,其中包含一个包含 Font Awesome 字符的单个大 Button。如果单击此 Button,光标将变为旋转沙漏两秒钟。

上面光标的实际 XAML 是

  <Button Margin="50"
          HorizontalAlignment="Center"
          VerticalAlignment="Center"
          Click="ButtonBase_OnClick">
   <fontAwesomeImageSample:FontSymbolImage Foreground="HotPink"
                                           FontFamily="{StaticResource FontAwesomeTtf}"
                       			   Flip="Horizontal"
                       			   Rotation="10"
                                           FontAwesomeSymbol="fa_bar_chart_o" />
  </Button>

额外

该示例包括从字体符号创建 WPF Image 的代码。这在 从 WPF 的字体符号(Font Awesome)创建图像 中有记载

历史

  • 2016/03/23:初始版本
  • 2016/03/26:代码更新
  • 2016/04/14:完整的代码 Awesome 4.5 枚举
  • 2016/04/27:Font Awesome 4.6 更新
  • 2015/05/12:更新示例以在单击按钮时更改光标
  • 2016/05/17:使用改进的等待光标实现更新代码
  • 2016/05/20:使用新的 BaseWindow 控件更新代码
© . All rights reserved.