当我在做一些界面设计的时候,我发现我需要一种类似在字体周边加上边框的效果。不幸的是,不但.NET没有提供这种给字体镶上边框的效果,而且,我也没法在网上找到任何一个免费的实现。于是,我决定自己做一个。 背景 开始,我打算通过在屏幕上绘制两次不同大小的文字来实现它。后来,我就把这篇文章发表到CodeProject上,并期望能有谁提出一个更好的解决 办法。结果也真是如此,fwsouthern提出了很好的建议。主要的思想还是一样的,不过不再采用重叠文字的方法了,转而,采用 GraphicPath, Brush以及其它效果,当然,后一种方法明显好多了。 我对于GDI+没有什么经验,不知道如何直接在屏幕上绘制图像,于是学习了Bob Powell的入门方法。 创建代码 现在,开始编码了,首先,我创建了一个继承于System.Windows.Forms.Control的组件,然后,重写了其OnPaint方法,添加了几个新的属性,以使其更有点label-like的感觉。 创建组件属性 因为,我非常希望它用起来像一个label,所以,很自然地实现了如Text, TextAlign和AutoSize等属性。另外,我还需要控制边框的风格,于是又添加了BorderColor和BorderSize属性。 答案当然是否定的了! 实现最关键的方法:重载OnPaint 就如我之前说的,重载Onpaint几乎是解决我的问题的唯一的办法。然而,直接继承了System.Windows.Forms.Control,我们不得不人工添加大部分的绘图逻辑:包括绘制文字,文字的位置,处理因AutoSize属性产生的自动调整。 让我们看看如何做到这些吧: 代码使用 添加这个组件到你的项目中,打开Form designer,拖拽BorderLabel到你的Form中。设置一些其它的属性,OK! 
//Properties
//-----------------------------------------------------
///
/// The text associated with the control.
///
[Browsable(true)]
[Category("Appearance")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[Description("The text associated with the control.")]
public override string Text
{
get { return this.text; }
set
{
this.text = value;
this.Invalidate();
}
}
///
/// The border's thickness
///
[Browsable(true)]
[Category("Appearance")]
[Description("The border's thickness")]
[DefaultValue(1)]
public float BorderSize
{
get { return this.borderSize; }
set
{
this.borderSize = value;
if (value == 0)
{ //If border size equals zero, lets disable
//the border by drawing it as transparent
this.drawningPen.Color = Color.Transparent;
}
else
{
this.drawningPen.Color = this.BorderColor;
this.drawningPen.Width = value;
}
this.Invalidate();
}
}
///
/// The border color of this component
///
[Browsable(true)]
[Category("Appearance")]
[Description("The border color of this component")]
public Color BorderColor
{
get { return this.borderColor; }
set
{
this.borderColor = value;
if (this.BorderSize != 0)
this.drawningPen.Color = value;
this.Invalidate();
}
}
///
/// The foreground color of this component
///
[Browsable(true)]
[Category("Appearance")]
[Description("The foreground color of this component")]
public override Color ForeColor
{
get { return base.ForeColor; }
set
{
this.drawningForecolorBrush.Color = value;
base.ForeColor = value;
}
}
///
/// Determines the position of the text within the label
///
[Browsable(true)]
[Category("Appearance")]
[Description("Determines the position of the text within the label.")]
[DefaultValue(ContentAlignment.TopLeft)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public ContentAlignment TextAlign
{
get { return this.textAlign; }
set
{
this.textAlign = value;
this.Invalidate();
}
}
简单吧,不是吗?
下面就让我们处理这个组件最有意思的一部分。protected override void OnPaint(PaintEventArgs e)
{
//First, we begin by setting the smoothing mode to
//AntiAlias, to reduce image sharpening and improve our drawing
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
base.OnPaint(e);
this.drawningPath.Reset();
//Second, let's use a GraphicsPath object and write our string into it.
//Our objective, however, is to measure how much screen space our
//drawing will occupy.
drawningPath.AddString(this.text, this.Font.FontFamily,
(int)this.Font.Style, this.Font.Size,
new Point(0, 0), StringFormat.GenericTypographic);
//Then, let's calculate the area occupied by our drawing...
this.drawningSize = getPathSize(drawningPath);
//..and remember to also sum the future border size
this.drawningSize.Height += this.borderSize + 5;
this.drawningSize.Width += this.borderSize + 5;
//Let's determine then how we should align our text in
//the control's area, both horizontally and vertically
//If text is Left-Aligned
if (this.textAlign == ContentAlignment.TopLeft ||
this.textAlign == ContentAlignment.MiddleLeft ||
this.textAlign == ContentAlignment.BottomLeft)
this.drawningPoint.X = this.Padding.Left;
//If text is Center-Aligned
else if (this.textAlign == ContentAlignment.TopCenter ||
this.textAlign == ContentAlignment.MiddleCenter ||
this.textAlign == ContentAlignment.BottomCenter)
drawningPoint.X = (this.Width - this.drawningSize.ToSize().Width) / 2;
//If text is Right-Aligned
else drawningPoint.X = this.Width - (this.Padding.Right +
drawningSize.ToSize().Width);
//If text is Top-Aligned
if (this.textAlign == ContentAlignment.TopLeft ||
this.textAlign == ContentAlignment.TopCenter ||
this.textAlign == ContentAlignment.TopRight)
drawningPoint.Y = this.Padding.Top;
//If text is Middle-Aligned
else if (this.textAlign == ContentAlignment.MiddleLeft ||
this.textAlign == ContentAlignment.MiddleCenter ||
this.textAlign == ContentAlignment.MiddleRight)
drawningPoint.Y = (this.Height - drawningSize.ToSize().Height) / 2;
//If text is Bottom-Aligned
else drawningPoint.Y = this.Height -
(this.Padding.Bottom + drawningSize.ToSize().Height);
//After that we can reset our path so we can draw the text
//in the proper place now
drawningPath.Reset();
//Next, we start adding our text into it's place, ...
drawningPath.AddString(this.text, this.Font.FontFamily,
(int)this.Font.Style, this.Font.Size,
this.drawningPoint, StringFormat.GenericTypographic);
//... and then let's use our pen and draw our text to screen
e.Graphics.DrawPath(drawningPen, drawningPath);
//So let's reset our path and do it again, but now for the foreground
drawningPath.Reset();
//Again, we add our text into our path ...
drawningPath.AddString(this.text, this.Font.FontFamily,
(int)this.Font.Style, this.Font.Size,
this.drawningPoint, StringFormat.GenericTypographic);
//... but now, we also draw the foreground
e.Graphics.FillPath(this.drawningForecolorBrush, drawningPath);
e.Graphics.DrawPath(drawningPen, drawningPath);
}
你也许已经注意到OnPaint方法中,使用了一个获得最大屏幕面积的函数,该函数实现如下:///
/// A method to get the maximum screen size occupied by a drawing Path object
///
private SizeF getPathSize(GraphicsPath path)
{
SizeF maxSize = new SizeF(0, 0);
foreach (PointF point in path.PathPoints)
{
if (point.X > maxSize.Width)
maxSize.Width = point.X;
if (point.Y > maxSize.Height)
maxSize.Height = point.Y;
}
return maxSize;
}
好了,如果你还有什么其它的疑问,下载本文中的代码来研究研究吧,当然也欢迎到论坛上讨论一下。