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

扩展图形 - C# 3.0 中的圆角矩形、字体度量等

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (54投票s)

2009年7月25日

Ms-PL

10分钟阅读

viewsIcon

166534

downloadIcon

21366

利用 C# 3.0 中的扩展方法为 Graphics 类添加缺失的功能。

图 1.1:在抗锯齿输出中选择圆角

该图展示了如何使用三个圆角矩形来实现这种效果。请注意,在当前版本的代码中,您可以选择要进行圆角处理的角。

目录

引言

我对 .NET 框架基类库中 System.Drawing.Graphics 类不完整性质的痴迷始于 CodeProject 上的这篇文章,该文章在 2003 年题为“扩展图形——C# 中圆角矩形的实现”。在查看了一些 Java 代码后,我选择了这个类。System.Drawing.Graphics 类无法完成其他语言 API 中类似类可以完成的某些事情。我的大部分注意力和精力都集中在我真正需要的一个功能上——即生成圆角矩形的能力。老实说,我成功地创建了一个可以满足我需求的临时类。

解决旧问题的新方法

在我最初的尝试六年之后,C# 在功能和可扩展性方面都有了发展。唉,我们仍然面临同样的困境,那就是当时我渴望的相同功能仍然没有适当的实现。我请求读者的帮助,让他们分享 GDI+ System.Drawing.Graphics 类中其他缺失功能的建议。在编写此代码时,我尝试根据这些建议尽可能多地包含缺失的功能。

事实上,我只需浏览互联网上的几个论坛,就能找到一些需要在六年前我编写的代码的更新版本中实现的其他功能。然而,这次 C# 3.0 为我提供了一种编写代码的绝佳新方法。我最终计划使用 C# 3.0 中我最喜欢的新功能——扩展方法。继续阅读以了解扩展方法是什么以及它们真正做了什么。

什么是扩展方法?

有时您希望向现有类(或类型)添加方法,这些类(或类型)是您无法直接访问的。例如,您可能想知道是否有办法扩展 .NET Framework 的基类,如 System.String(或者在我们的例子中,是 System.Drawing.Graphics 类)。那么您会惊讶地发现有一种方法可以做到这一点。这就是扩展方法派上用场的地方。

扩展方法是一种在不创建扩展类型的情况下为现有类型扩展方法的方式。例如,人们可以在不为该类型创建子类的情况下,向 .NET Framework 中已有的类添加方法。在本文中,我将尽力解释如何使用扩展方法来扩展现有 System.Drawing.Graphics 类的功能。由于本文并非关于扩展方法,我将避免深入讨论扩展方法。

扩展方法实战

我们将在本文中首先看一个扩展方法的例子。下面是一段代码片段,它告诉编译器将代码包含到 System.String 类中。如果您仔细注意,下面的代码有一些地方与您的普通代码不同。首先,请注意 static 类定义。然后注意第三行带有代码 this string。我将尝试解释当您运行此代码时会发生什么。

列表 1.1
static class StringExtension
{
    public static string Reverse(this string text)
    {
        // Logic for string reversal goes here
    }
}

一旦上述代码完成、构建并编译,只需将此文件包含到您的项目中,即可自动将创建的 Reverse() 方法添加到 System.String 类中。因此,该方法可以像这样与 System.String 类的每个对象一起调用。

列表 1.2
...
string text = "HELLO WORLD";
Console.WriteLine( text.Reverse() );     // This line should now print: DLROW OLLEH
...

请注意,当我调用 Reverse() 方法时,我没有像清单 1.1 中那样给出参数。原因非常简单明了。记住 this string 代码——事实证明,在扩展方法方面,该代码才是真正的关键。该代码本身告诉编译器将方法定义附加到某个类。在我们的例子中,它是 System.String 类。只有当您看到类似以下代码变为可能时,才能真正认识到扩展方法的真正潜力。

清单 1.3
...
string text = "ARUN".Reverse();          // This should put "NURA" in the variable 'text'.
...

旧方法与新方法

在此代码的先前版本中,我为 System.Drawing.Graphics 类创建了一个新的包装类,并将其命名为 ExtendedGraphics。作为包装类,它封装了继承父类的所有功能以及一些附加功能。下面是该过程如何工作的示例:

清单 1.4
...
System.Drawing.Graphics g = this.CreateGraphics();
ExtendedGraphics eg = new ExtendedGraphics(g);
eg.FillRoundedRectangle(brush, x, y, width, height, arcRadius);
...

您无法使用 System.Drawing.Graphics 类创建圆角矩形,因此您必须将其包装在 ExtendedGraphics 周围以提供缺失的功能。上述代码的唯一问题是实际创建了一个新对象。人们必须记住新类的确切方法调用,并且不得不不情愿地将该类添加到项目的 using 指令中。如果能够执行以下操作,会不会简单得多呢?

清单 1.5
...
System.Drawing.Graphics g = this.CreateGraphics();
g.FillRoundedRectangle(brush, x, y, width, height, arcRadius);
...
简单又甜蜜!你猜怎么着?!这个类的新实现正是为您做到了这一点。您只需将该类添加到您的项目中,然后就可以开始将 System.Drawing.Graphics 的现有基类库用于您的工作。无需创建花哨的包装类或额外的对象。

代码增强

有了扩展任何 .NET Framework 基类的可能性,我突然萌生了扩展当前 System.Drawing.Graphics 类的想法,有一天我就这样做了。当我完成最初的实现时,我对结果非常满意。新实现不仅速度更快,而且更简洁易读。通过不创建另一个对象包装器,而是使用已经优化过的类的扩展版本,减少了开销,这无疑增加了此版本的吸引力。

如何在项目中使用此代码?

下载上面的源代码 zip 文件,并将 GraphicsExtension.cs 文件解压到您的项目中。一旦文件包含在您的项目中,您就差不多完成了一半。要在您的项目中使用此类的功能,只需在需要此代码的每个代码文件顶部添加一个 using 指令,如下所示:

清单 1.6
using System;
using System.Drawing;
using Plasmoid.Extensions;               // this is how the directive should appear
...

一旦您按照说明添加了指令,System.Drawing.Graphics 文件的所有实例都将自动扩展为代码中的全新功能。无论您在代码中何处使用该类的对象,IntelliSense 都会为您检测新方法。但请记住,GraphicsExtension 并非此实现中唯一获得的类。您可以使用代码执行一些奇特的新功能。现在让我们来看看其中一些。

图 1.2:扩展图形测试套件包

如果您想在深入研究代码之前先尝试一下输出,请下载测试套件并熟悉本项目解决的问题周围的概念。

创建圆角矩形

创建圆角矩形从未如此简单。以下代码展示了如何创建一个所有角都圆角化的简单圆角矩形。

清单 1.8
...
System.Drawing.Graphics g = this.CreateGraphics();
g.FillRoundedRectangle(brush, x, y, width, height, arcRadius);
...
...其中 xy 是开始绘制矩形的坐标轴,widthheight 如其名称所示。brushSystem.Drawing.Brush 对象的一个实例,它将定义矩形填充的颜色,最后是 arcRadius,它是创建矩形所有圆角的弧的半径。您可能永远不需要记住这个方法,因为它总是会出现在 System.Drawing.Graphics 类的 IntelliSense 中。

就像 FillRoundedRectangle(..) 方法一样,您也可以使用另一个方法来创建圆角矩形的边框。以下是生成边框的代码,其中 gSystem.Drawing.Graphics 类的一个对象,penSystem.Drawing.Pen 类的一个对象。

清单 1.9
...
g.DrawRoundedRectangle(pen, x, y, width, height, arcRadius);
...

VER 1.0.0.2 新增功能 | 但是,如果您只想对矩形的某个选定角或多个角进行圆角处理,则可以使用 RectangleEdgeFilter 枚举来实现此目的。RectangleEdgeFilter 枚举只包含六个值:

  1. 无 = 0
  2. 左上 = 1
  3. 右上 = 2
  4. 左下 = 4
  5. 右下 = 8
  6. 全部 = 左上|右上|左下|右下

使用这些可以编写代码来产生部分圆角效果,即矩形只有部分边缘或角落会被圆角化。例如,如果我只对 TopLeftBottomRight 角落进行圆角处理,我将编写以下代码:

列表 2.0
...
g.FillRoundedRectangle(brush, x, y, width, height, arcRadius, RectangleEdgeFilter.TopLeft | RectangleEdgeFilter.BottomRight);
...

获取字体信息

VER 1.0.0.4 新增功能 | 版本 1.0.0.4 中新增的功能是包含了 FontMetrics 类,该类与 System.Drawing.Graphics 类协同工作,为您提供有关每个字体的关键信息。以下代码演示了获取字体某些重要信息是多么容易,其中 fontSystem.Drawing.Font 类的一个对象。

清单 2.1
...
FontMetrics fm = g.GetFontMetrics(font);
    fm.Height;                 // Gets a font's height
    fm.Ascent;                 // Gets a font's ascent
    fm.Descent;                // Gets a font's descent
    fm.InternalLeading;        // Gets a font's internal leading
    fm.ExternalLeading;        // Gets a font's external leading
    fm.AverageCharacterWidth;  // Gets a font's average character width
    fm.MaximumCharacterWidth;  // Gets a font's maximum character width
    fm.Weight;                 // Gets a font's weight
    fm.Overhang;               // Gets a font's overhang
    fm.DigitizedAspectX;       // Gets a font's digitized aspect (x-axis)
    fm.DigitizedAspectY;       // Gets a font's digitized aspect (y-axis)
...

掌握了这些重要信息后,我们当中的 UI 开发人员可以充分利用此方法,巧妙地应用此类别来确定字体边界和基于 DrawString(..) 输出的控件的布局文本的整体结构。

图 1.3:解释字体度量

此图详细展示了两行文本可能呈现的各种字体度量。如果您所做的事情与排版有任何关联,那么正确获取这些细节至关重要。为了获得完美的感觉和可读性,必须了解这些基本知识。FontMetrics 类测量的所有尺寸都以 em 为单位计算。

历史

  • 版本 1.0.0.1 (2009 年 7 月 20 日)
    • 首次发布,代码使用扩展方法,并从之前的 CodeProject 文章移植而来。
  • 版本 1.0.0.2 (2009 年 7 月 25 日)
    • 添加 RectangleEdgeFilter 枚举,以方便选择性地对边缘进行圆角处理。
    • 修改代码,使其在绘制和填充圆角矩形时始终适应抗锯齿输出。
  • 版本 1.0.0.3 (2009 年 7 月 26 日)
    • DrawRoundedRectangleFillRoundedRectangle 方法参数添加了对 RectangleRectangleF 对象的支持。
    • 通过消除例如循环内创建的对象,在某些地方提高了性能。
  • 版本 1.0.0.4 (2009 年 7 月 27 日)
    • 增加了 FontMetrics 类,用于测量字体高度、上升线、下降线、行距等。

结论

以上示例清楚地说明了使用扩展方法扩展现有 .NET 类(在本例中为 System.Drawing.Graphics 类)功能的好处。这显著减少了创建和管理单独继承对象所花费的时间。

© . All rights reserved.