C#实现字体镶边的Label控件

发布时间: 2009/6/15 8:42:47

当我在做一些界面设计的时候,我发现我需要一种类似在字体周边加上边框的效果。不幸的是,不但.NET没有提供这种给字体镶上边框的效果,而且,我也没法在网上找到任何一个免费的实现。于是,我决定自己做一个。

 背景

开始,我打算通过在屏幕上绘制两次不同大小的文字来实现它。后来,我就把这篇文章发表到CodeProject上,并期望能有谁提出一个更好的解决 办法。结果也真是如此,fwsouthern提出了很好的建议。主要的思想还是一样的,不过不再采用重叠文字的方法了,转而,采用 GraphicPath, Brush以及其它效果,当然,后一种方法明显好多了。

我对于GDI+没有什么经验,不知道如何直接在屏幕上绘制图像,于是学习了Bob Powell的入门方法。

创建代码

现在,开始编码了,首先,我创建了一个继承于System.Windows.Forms.Control的组件,然后,重写了其OnPaint方法,添加了几个新的属性,以使其更有点label-like的感觉。

创建组件属性

因为,我非常希望它用起来像一个label,所以,很自然地实现了如Text, TextAlign和AutoSize等属性。另外,我还需要控制边框的风格,于是又添加了BorderColor和BorderSize属性。

//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();
    }
}




简单吧,不是吗?

答案当然是否定的了!
下面就让我们处理这个组件最有意思的一部分。

实现最关键的方法:重载OnPaint

就如我之前说的,重载Onpaint几乎是解决我的问题的唯一的办法。然而,直接继承了System.Windows.Forms.Control,我们不得不人工添加大部分的绘图逻辑:包括绘制文字,文字的位置,处理因AutoSize属性产生的自动调整。

让我们看看如何做到这些吧:

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;
}

 


好了,如果你还有什么其它的疑问,下载本文中的代码来研究研究吧,当然也欢迎到论坛上讨论一下。

代码使用

添加这个组件到你的项目中,打开Form designer,拖拽BorderLabel到你的Form中。设置一些其它的属性,OK!

 

赞助商