扩展图形 - C# 中圆角矩形的实现






4.52/5 (34投票s)
C# 中圆角矩形的实现,可用于创建 XP 风格的按钮和圆角边框。
引言
在经历了数小时的编码实践和难以设计的 Java 应用程序后,我被引诱去尝试 Microsoft .Net Framework,并发现 C# 是比 Java 更好的选择。制作 Windows 程序更加容易,而且我可以看到 Windows 平台上无限的程序可能性。
这次转变的后续影响让我思考如何为组件和窗体创建图形,并且我不知何故(请注意:在 CodeProject 和 Framework 文档上搜索了数小时)得出结论,在我的程序中使用 Graphics 是必需的。同时,CodeProject 上充斥着关于创建 XP 风格按钮和窗体的文章。更具体地说,是使用圆角路径和精美设计的用户控件。那时我决定创建一个类似的带有圆角矩形的按钮。“太简单了,”我想。
准备工作
我坐下来自己创建按钮。我脑海中浮现出在 Java 中如何实现这一点的模糊印象。我认为最合适的实现方式是这样的(哦!我多么喜欢 Java)。
import java.awt.*;
// A simple implementation of a drawing with Rounded Rectangle in Java.
// Notice that the Graphics class has methods:
// fillRoundRect and drawRoundRect,
// both having six arguments, the last two being width
// and height of the round
// curves or arcs.
public class RoundButton extends Canvas
{
public RoundButton()
{
// Initialization code comes here
this.setSize(100, 20);
this.repaint();
}
public void paint(Graphics g)
{
// Drawing code comes here
g.setColor(new Color(200, 200, 200));
g.fillRoundRect(2, 2, this.getWidth()-4,
this.getHeight()-4, 5, 5);
g.setColor(new Color(60, 60, 60));
g.drawRoundRect(2, 2, this.getWidth()-4,
this.getHeight()-4, 5, 5);
}
}
恐怖
然而,当我进一步尝试在 C# 中实现相同效果时,我突然感到难以置信。我一直赞不绝口的 C# Graphics 类缺少一个圆角矩形的方法。什么!?在这种情况下创建圆角矩形将是多么繁琐。于是,我退回原点:搜索 CodeProject。虽然我找到了大量关于如何实现此功能的代码,但所有代码都围绕着某些用户控件或组件构建。我想要一个很可能继承 Graphics
类的所有方法和属性,并包含附加和扩展功能的类。但还有一个问题:Graphics
类是 abstract
类并且/或者 sealed
类(我不知道为什么我讨厌这个词)。
答案
在 Google [新窗口] 的搜索结果中,我偶然发现了一个名为“*Drawing Rectangle but with rounded corners...*”[新窗口] 的论坛,它在那里等待着一个绝望的灵魂发现它在 Mathew Reynold's .NET 247's Newsgroup [新窗口] 上的存在。一位我在此特别提及的人,Tim Overbay,提供了一个小例程,用于在 VB.Net 中创建这种圆角矩形。这正是我继续我自己的实现的起点。我复制了代码,将其转换为 C#,就这样。最终结果:一个我现在亲切地称之为 ExtendedGraphics
类的类。完整代码如下。
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
// A simple extension to the Graphics class for extended
// graphic routines, such,
// as for creating rounded rectangles.
// Because, Graphics class is an abstract class,
// that is why it can not be inherited. Although,
// I have provided a simple constructor
// that builds the ExtendedGraphics object around a
// previously created Graphics object.
// Please contact: aaronreginald@yahoo.com for the most
// recent implementations of
// this class.
namespace System.Drawing.Extended
{
/// <SUMMARY>
/// Inherited child for the class Graphics encapsulating
/// additional functionality for curves and rounded rectangles.
/// </SUMMARY>
public class ExtendedGraphics
{
private Graphics mGraphics;
public Graphics Graphics
{
get{ return this.mGraphics; }
set{ this.mGraphics = value; }
}
public ExtendedGraphics(Graphics graphics)
{
this.Graphics = graphics;
}
#region Fills a Rounded Rectangle with integers.
public void FillRoundRectangle(System.Drawing.Brush brush,
int x, int y,
int width, int height, int radius)
{
float fx = Convert.ToSingle(x);
float fy = Convert.ToSingle(y);
float fwidth = Convert.ToSingle(width);
float fheight = Convert.ToSingle(height);
float fradius = Convert.ToSingle(radius);
this.FillRoundRectangle(brush, fx, fy,
fwidth, fheight, fradius);
}
#endregion
#region Fills a Rounded Rectangle with continuous numbers.
public void FillRoundRectangle(System.Drawing.Brush brush,
float x, float y,
float width, float height, float radius)
{
RectangleF rectangle = new RectangleF(x, y, width, height);
GraphicsPath path = this.GetRoundedRect(rectangle, radius);
this.Graphics.FillPath(brush, path);
}
#endregion
#region Draws a Rounded Rectangle border with integers.
public void DrawRoundRectangle(System.Drawing.Pen pen, int x, int y,
int width, int height, int radius)
{
float fx = Convert.ToSingle(x);
float fy = Convert.ToSingle(y);
float fwidth = Convert.ToSingle(width);
float fheight = Convert.ToSingle(height);
float fradius = Convert.ToSingle(radius);
this.DrawRoundRectangle(pen, fx, fy, fwidth, fheight, fradius);
}
#endregion
#region Draws a Rounded Rectangle border with continuous numbers.
public void DrawRoundRectangle(System.Drawing.Pen pen,
float x, float y,
float width, float height, float radius)
{
RectangleF rectangle = new RectangleF(x, y, width, height);
GraphicsPath path = this.GetRoundedRect(rectangle, radius);
this.Graphics.DrawPath(pen, path);
}
#endregion
#region Get the desired Rounded Rectangle path.
private GraphicsPath GetRoundedRect(RectangleF baseRect,
float radius)
{
// if corner radius is less than or equal to zero,
// return the original rectangle
if( radius<=0.0F )
{
GraphicsPath mPath = new GraphicsPath();
mPath.AddRectangle(baseRect);
mPath.CloseFigure();
return mPath;
}
// if the corner radius is greater than or equal to
// half the width, or height (whichever is shorter)
// then return a capsule instead of a lozenge
if( radius>=(Math.Min(baseRect.Width, baseRect.Height))/2.0)
return GetCapsule( baseRect );
// create the arc for the rectangle sides and declare
// a graphics path object for the drawing
float diameter = radius * 2.0F;
SizeF sizeF = new SizeF( diameter, diameter );
RectangleF arc = new RectangleF( baseRect.Location, sizeF );
GraphicsPath path = new System.Drawing.Drawing2D.GraphicsPath();
// top left arc
path.AddArc( arc, 180, 90 );
// top right arc
arc.X = baseRect.Right-diameter;
path.AddArc( arc, 270, 90 );
// bottom right arc
arc.Y = baseRect.Bottom-diameter;
path.AddArc( arc, 0, 90 );
// bottom left arc
arc.X = baseRect.Left;
path.AddArc( arc, 90, 90 );
path.CloseFigure();
return path;
}
#endregion
#region Gets the desired Capsular path.
private GraphicsPath GetCapsule( RectangleF baseRect )
{
float diameter;
RectangleF arc;
GraphicsPath path = new System.Drawing.Drawing2D.GraphicsPath();
try
{
if( baseRect.Width>baseRect.Height )
{
// return horizontal capsule
diameter = baseRect.Height;
SizeF sizeF = new SizeF(diameter, diameter);
arc = new RectangleF( baseRect.Location, sizeF );
path.AddArc( arc, 90, 180);
arc.X = baseRect.Right-diameter;
path.AddArc( arc, 270, 180);
}
else if( baseRect.Width < baseRect.Height )
{
// return vertical capsule
diameter = baseRect.Width;
SizeF sizeF = new SizeF(diameter, diameter);
arc = new RectangleF( baseRect.Location, sizeF );
path.AddArc( arc, 180, 180 );
arc.Y = baseRect.Bottom-diameter;
path.AddArc( arc, 0, 180 );
}
else
{
// return circle
path.AddEllipse( baseRect );
}
}
catch(Exception ex)
{
path.AddEllipse( baseRect );
}
finally
{
path.CloseFigure();
}
return path;
}
#endregion
}
}
代码做什么?
上面的代码是一个简单的类,可以在您的项目中用于额外的绘图例程。我这样做是因为 Graphics 类是 sealed 的,无法继承,这是我遇到的一个更严厉的惩罚。否则,我本可以将我的类直接继承自 Graphics
类。
该类中有两个私有方法和四个公共方法。两个私有方法,即 GetRoundedRect(...)
和 GetCapsule(...)
方法最为重要。这两个方法都绘制四条线段,用圆角弧线连接起来形成一个圆角矩形。如果矩形的高度或宽度小于指定的圆弧直径,则该方法将为用户提供一个通过 GetCapsule(...)
方法获得的胶囊形状,而不是矩形形状。如果宽度和高度都小于圆弧直径并且尺寸相同,则从该方法获得一个圆形椭圆。
为了使该类更有用和更熟悉,该类中添加了四个方法,这些方法模仿了 Graphics
类中存在的方法。
下一步...
将本文视为一系列文章中的第一篇,因为我将在我接下来的关于如何创建按钮和有效用户界面的教程中使用这个相同的类。任何能给我关于如何优化上述代码的建议或意见的人,我将不胜感激。我正在开发一个适用于偶数边多边形的实现,并将在不久的将来将这些代码片段和方法包含在此类中。