-
vb.net 图形控件_玩转图形:VB.net GlassLabel控件
vb.net 图形控件
A while ago, I was working on a Windows Forms application and I needed a special label control with reflection (glass) effect to show some titles in a stylish way.
不久前,我正在开发Windows Forms应用程序,我需要一个特殊的具有反射(玻璃)效果的标签控件,以一种时尚的方式显示一些标题。
Mmmm... thinking deeply about it I realized that it would not be too difficult to build my own, so hands on! First of all, I wrote my requirements list (and I added more and more things as soon as they popped into my head!). The final requirement list was like this:
嗯...对此深思熟虑,我意识到自己打造自己并不困难,所以动手吧! 首先,我写了我的需求列表(一旦它们出现在我的脑海里,我就添加了越来越多的东西!)。 最终需求列表如下:
It must be a true label control.
它必须是真正的标签控件。
It will show a reflection or glass effect of the text drawn.
它将显示所绘制文本的反射或玻璃效果。
The control's background can be filled with solid or gradient colors.
控件的背景可以填充纯色或渐变色。
The control's text can be filled with solid or gradient colors, too.
控件的文本也可以用纯色或渐变色填充。
The text can draw (or not) a solid-color outline with configurable width.
文本可以绘制(或不绘制)具有可配置宽度的纯色轮廓。
The level of transparency must be configurable, too.
透明度级别也必须是可配置的。
Once done, I thought that writing an article focusing on the graphics work for this control would be a good idea. So I will comment on what I've done and why I did it and will finish my article with the complete code listing of the control.
完成后,我认为写一篇专注于该控件图形工作的文章将是一个好主意。 因此,我将评论我所做的事情以及为什么这么做,并以控件的完整代码清单结束我的文章。
1.如果是标签,则为标签 (1. If it's a Label, then it's a Label)
As said in my requirements list, the control must be a true label control. So I started creating a new class and inheriting it from the standard WinForms Label control.
如我的需求列表中所述,该控件必须是真正的标签控件。 因此,我开始创建一个新类,并从标准WinForms Label控件继承它。
-
Public Class GlassLabel
-
Inherits System.Windows.Forms.Label
-
End Class
-
'The AutoSize property has been overridden to achieve the control
-
'not to be auto-sized.
-
Public Overrides Property AutoSize() As Boolean
-
Get
-
Return MyBase.AutoSize
-
End Get
-
Set(ByVal value As Boolean)
-
MyBase.AutoSize = False
-
End Set
-
End Property
-
'The TextAlign property has been overridden to achieve the control text
-
'be aligned always at Middle-Center.
-
Public Overrides Property TextAlign() As System.Drawing.ContentAlignment
-
Get
-
Return MyBase.TextAlign
-
End Get
-
Set(ByVal value As System.Drawing.ContentAlignment)
-
MyBase.TextAlign = ContentAlignment.MiddleCenter
-
End Set
-
End Property
Finally, I have overridden completely the OnPaint method, because I don't want .Net to draw the control -- I want to do it myself with my requirements. Really, all the work is done in the OnPaint overridden method:
最后,我完全重写了OnPaint方法,因为我不希望.Net绘制控件-我想根据自己的需要进行操作。 确实,所有工作都是通过OnPaint重写方法完成的:
-
Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
-
'All the painting work is done here.
-
...
-
End Sub
2.渐变 (2. Gradients)
该控件支持背景和文本的渐变。 借助GDI +,渐变是.Net中最简单的操作之一。 实际上,.Net为我们提供了一个名为LinearGradientBrush的自定义笔刷,我们只需要为其提供绘制矩形(需要计算渐变的步数),开始和结束颜色以及渐变的方向即可(水平,垂直或对角线)。 就这么简单:-
Dim gradBrush As LinearGradientBrush = New LinearGradientBrush(rectangle, color1, color2, direction)
-
Graphics.FillRectangle(gradBrush, rectangle)
In the complete listing of GlassLabel control you'll find some code in which I use gradients, and you'll see how easy is to use them.
在GlassLabel控件的完整列表中,您将找到一些我在其中使用渐变的代码,并且您会看到使用它们的简便性。
3.大纲 (3. Outline)
有一些方法可以将文本绘制到Graphics对象中。 最基本的方法是使用DrawString方法,该方法带有一些参数(文本,字体,大小,位置...),并使用所需的画笔绘制文本。But for the outline requirement I needed more functionality than DrawString offers. So I used GraphicsPath object. As MSDN says, GraphicsPath represents a series of connected lines and curves. Applications use paths to draw outlines of shapes, fill the interiors of shapes, and create clipping regions. Best of all, the GraphicsPath object provides with it's own AddString method, so we just need to call AddString on the GraphicsPath and it creates a path that outlines the string passed. After that, you can both draw the string outline or fill its contents.
但是对于大纲要求,我需要比DrawString提供的功能更多。 所以我用了GraphicsPath对象。 正如MSDN所说,GraphicsPath代表一系列连接的直线和曲线。 应用程序使用路径绘制形状的轮廓,填充形状的内部以及创建剪切区域。 最棒的是,GraphicsPath对象提供了它自己的AddString方法,因此我们只需要在GraphicsPath上调用AddString,它会创建一个概述所传递字符串的路径。 之后,您既可以绘制字符串轮廓,也可以填充其内容。
-
Dim path As GraphicsPath = New GraphicsPath
-
path.AddString(text, fontFamily, fontStyle, emSize, clippingRect, stringFormat)
-
Graphics.DrawPath(pen, path)
-
Graphics.FillPath(brush, path)
4.反思 (4. Reflection)
显而易见,要求的难度更大是玻璃或反射效果。 与GDI +中的几乎所有内容一样,有很多方法可以做到这一点。 但是我认为我可能使用了所有方法中最简单的方法。A graphics object supports a series of transformations that can affect to his size, rotation angle, etc. Once one of this transformation is applied to a graphics object, all the painting work done over the object is affected by the transformations done. This transformations can be cumulative and that gives us an extremely powerful mechanism to work with graphics.
图形对象支持一系列可能影响其大小,旋转角度等的变换。一旦将此变换中的一个应用于图形对象,在该对象上完成的所有绘制工作都会受到所完成的变换的影响。 这种转换可以是累积的,这为我们提供了一种非常强大的处理图形的机制。
In this case I did a really simple transformation: I "said" to my graphics object that all the painting must be affected by a scale transformation. The scale applied is 1 for the horizontal axis (so no transformation) and -1 for the vertical axis (so no transformation in size but vertically reflected). It's as simple as that. You'll find the complete sample in the code listing at the end of the article.
在这种情况下,我做了一个非常简单的转换:我对图形对象“说”所有画都必须受到比例转换的影响。 对于水平轴,应用的比例为1;对于垂直轴,应用的比例为-1(因此,尺寸没有变化,但垂直反射)。 就这么简单。 您可以在本文结尾的代码清单中找到完整的示例。
Graphics.ScaleTransform(1, -1)
完整清单
(The Complete Listing
)
好吧,信不信由你,使用这几个GDI +对象,就可以完成所有工作。 这是我的GlassLabel控件的完整列表代码。 该代码的注释很好,我认为您可以轻松地遵循它。
As a suggestion for further work, maybe it would be a good idea to not override the TextAlign property and make it work in the control as expected, re-calculating the position for each one of the possible values. Another interesting challenge would be to find a way to show the reflection effect in perspective.
作为进一步工作的建议,最好不要重写TextAlign属性,并使其按预期方式在控件中工作,然后重新计算每个可能值的位置。 另一个有趣的挑战是找到一种方法来透视显示反射效果。
-
Imports System.Drawing
-
Imports System.Drawing.Drawing2D
-
Imports System.Drawing.Imaging
-
-
Public Class GlassLabel
-
Inherits System.Windows.Forms.Label
-
-
'Variables to hold properties values
-
'Each initialization values represent the default value for the property
-
Private m_BackGradientColor1 As Color = Color.Black
-
Private m_BackGradientColor2 As Color = Color.White
-
Private m_BackGradientMode As LinearGradientMode = LinearGradientMode.Horizontal
-
Private m_BackGradient As Boolean = False
-
Private m_Alpha As Integer = 100
-
Private m_ForeGradientColor1 As Color = Color.Purple
-
Private m_ForeGradientColor2 As Color = Color.White
-
Private m_ForeGradientMode As LinearGradientMode = LinearGradientMode.Vertical
-
Private m_ForeGradient As Boolean = False
-
Private m_OffsetY As Integer = 0
-
Private m_OutlineColor As Color = Color.White
-
Private m_OutlineWidth As Integer = 0
-
-
'This property get/set text outline border color
-
Public Property OutlineColor() As Color
-
Get
-
Return m_OutlineColor
-
End Get
-
Set(ByVal value As Color)
-
m_OutlineColor = value
-
Me.Invalidate()
-
End Set
-
End Property
-
-
'This property get/set text outline border width
-
'If set to zero, then it's not outline
-
Public Property OutlineWidth() As Integer
-
Get
-
Return m_OutlineWidth
-
End Get
-
Set(ByVal value As Integer)
-
m_OutlineWidth = value
-
Me.Invalidate()
-
End Set
-
End Property
-
-
'This property get/set an integer value that represents
-
'the number of pixels that must close up the normal text and
-
'the reflected text. As MeasureString considers special characters
-
'and glyphos, it returns vertically an extra space. The OffsetY
-
'property tells the control to dispose N pixels of space between
-
'the drawn texts.
-
Public Property OffsetY() As Integer
-
Get
-
Return m_OffsetY
-
End Get
-
Set(ByVal value As Integer)
-
m_OffsetY = value
-
Me.Invalidate()
-
End Set
-
End Property
-
-
'This property get/set a value indicating if draw the texts
-
'using a gradient fill (true) or a solid fill (false)
-
Public Property ForeGradient() As Boolean
-
Get
-
Return m_ForeGradient
-
End Get
-
Set(ByVal value As Boolean)
-
m_ForeGradient = value
-
Me.Invalidate()
-
End Set
-
End Property
-
-
'This property get/set a value indicating the direction of
-
'the texts gradient fill
-
Public Property ForeGradientMode() As LinearGradientMode
-
Get
-
Return m_ForeGradientMode
-
End Get
-
Set(ByVal value As LinearGradientMode)
-
m_ForeGradientMode = value
-
Me.Invalidate()
-
End Set
-
End Property
-
-
'This property get/set a value indicating the second color of
-
'the texts gradient fill
-
Public Property ForeGradientColor2() As Color
-
Get
-
Return m_ForeGradientColor2
-
End Get
-
Set(ByVal value As Color)
-
m_ForeGradientColor2 = value
-
Me.Invalidate()
-
End Set
-
End Property
-
-
'This property get/set a value indicating the first color of
-
'the texts gradient fill
-
Public Property ForeGradientColor1() As Color
-
Get
-
Return m_ForeGradientColor1
-
End Get
-
Set(ByVal value As Color)
-
m_ForeGradientColor1 = value
-
Me.Invalidate()
-
End Set
-
End Property
-
-
'This property get/set a value that represents the level of
-
'transparency in the reflected text. Values must be between 0 and 255.
-
'The lower value, reflected text is more transparent.
-
'The higher value, more opaque.
-
Public Property Alpha() As Integer
-
Get
-
Return m_Alpha
-
End Get
-
Set(ByVal value As Integer)
-
m_Alpha = value
-
Me.Invalidate()
-
End Set
-
End Property
-
-
'This property get/set a value indicating if draw the control's background
-
'using a gradient fill (true) or a solid fill (false)
-
Public Property BackGradient() As Boolean
-
Get
-
Return m_BackGradient
-
End Get
-
Set(ByVal value As Boolean)
-
m_BackGradient = value
-
Me.Invalidate()
-
End Set
-
End Property
-
-
'This property get/set a value indicating the direction of
-
'the control's background gradient fill
-
Public Property BackGradientMode() As LinearGradientMode
-
Get
-
Return m_BackGradientMode
-
End Get
-
Set(ByVal value As LinearGradientMode)
-
m_BackGradientMode = value
-
Me.Invalidate()
-
End Set
-
End Property
-
-
'This property get/set a value indicating the second color of
-
'the control's background gradient fill
-
Public Property BackGradientColor2() As Color
-
Get
-
Return m_BackGradientColor2
-
End Get
-
Set(ByVal value As Color)
-
m_BackGradientColor2 = value
-
Me.Invalidate()
-
End Set
-
End Property
-
-
'This property get/set a value indicating the first color of
-
'the control's background gradient fill
-
Public Property BackGradientColor1() As Color
-
Get
-
Return m_BackGradientColor1
-
End Get
-
Set(ByVal value As Color)
-
m_BackGradientColor1 = value
-
Me.Invalidate()
-
End Set
-
End Property
-
-
'The AutoSize property has been overridden to achieve the control
-
'not to be auto-sized.
-
Public Overrides Property AutoSize() As Boolean
-
Get
-
Return MyBase.AutoSize
-
End Get
-
Set(ByVal value As Boolean)
-
MyBase.AutoSize = False
-
End Set
-
End Property
-
-
'The TextAlign property has been overridden to achieve the control text
-
'be aligned always at Middle-Center.
-
Public Overrides Property TextAlign() As System.Drawing.ContentAlignment
-
Get
-
Return MyBase.TextAlign
-
End Get
-
Set(ByVal value As System.Drawing.ContentAlignment)
-
MyBase.TextAlign = ContentAlignment.MiddleCenter
-
End Set
-
End Property
-
-
'All the paintint work is done here.
-
Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
-
With e.Graphics
-
'*** Apply high-quality properties to the graphics object ************************
-
.CompositingQuality = CompositingQuality.HighQuality
-
.InterpolationMode = InterpolationMode.HighQualityBicubic
-
.PixelOffsetMode = PixelOffsetMode.HighQuality
-
.SmoothingMode = SmoothingMode.HighQuality
-
.TextRenderingHint = Drawing.Text.TextRenderingHint.AntiAlias
-
-
'*** Draw the control's background ***********************************************
-
If Me.BackGradient Then
-
'The background must show a gradient, so we need to create a gradient brush
-
'and fill the control's background rectangle with this gradient
-
Using bkg As LinearGradientBrush = New LinearGradientBrush(e.ClipRectangle, Me.BackGradientColor1, Me.BackGradientColor2, Me.BackGradientMode)
-
.FillRectangle(bkg, e.ClipRectangle)
-
End Using
-
Else
-
'The background is a solid color. The Clear method of the Graphics object
-
'let us to choose a color to clear the background.
-
.Clear(Me.BackColor)
-
End If
-
'*********************************************************************************
-
-
'*** Create and draw the normal (not reflected) text *****************************
-
'Get the text width & height
-
Dim width As Single = .MeasureString(Me.Text, Me.Font).Width
-
Dim height As Single = .MeasureString(Me.Text, Me.Font).Height
-
-
'Create a rectangle that delimites the position & size of the text drawn
-
-
'The x position must be (control width - text width) / 2, so the text will be
-
'horizontally centered
-
Dim xpos As Single = (e.ClipRectangle.Width - width) / 2
-
-
'The y position must be also vertically centered, so we start from
-
'(control height - text height) / 2
-
'But below the normal text will be the reflected text, so must offset to top
-
'the half of text height
-
'Additionally, MeasureString give us extra space reserved for tall glyphos,
-
'so must consider the OffsetY value to delete this extra space, so must offset
-
'to bottom the half of OffsetY value
-
Dim ypos As Single = ((e.ClipRectangle.Height - height) / 2) - (height / 2) + (Me.OffsetY / 2)
-
-
'Finally, create the rectangle from x,y pos and width & height of the text
-
Dim originalRect As New RectangleF(xpos, ypos, width, height)
-
-
'Draw the original string. We'll use a GraphicsPath object instead
-
'using DrawString directly, because GraphicsPath will let us draw an
-
'outline border to the text
-
-
'Create the path
-
Dim path As GraphicsPath = New GraphicsPath
-
-
'Add the string to the path. Because GraphicsPath's AddString method
-
'uses emSize (the height of the em square box that bounds the character)
-
'instead of Point, we must convert out font's Point size to emSize using
-
'this formula: (Vertical Resolution / 72) * Font's Point Size
-
path.AddString(Me.Text, Me.Font.FontFamily, Me.Font.Style, (.DpiY / 72) * Me.Font.Size, originalRect, StringFormat.GenericDefault)
-
-
'If and outline must be drawn, draw it
-
If Me.OutlineWidth > 0 Then
-
Using p As Pen = New Pen(Me.OutlineColor, Me.OutlineWidth)
-
.DrawPath(p, path)
-
End Using
-
End If
-
-
'Create the brush to fill the text
-
Dim fill As Brush
-
If Me.ForeGradient Then
-
'Text must be filled with a gradient brush
-
fill = New LinearGradientBrush(originalRect, Me.ForeGradientColor1, Me.ForeGradientColor2, Me.ForeGradientMode)
-
Else
-
'Text must be filled with a solid brush
-
fill = New SolidBrush(Me.ForeColor)
-
End If
-
-
'Fill the text and destroy the brush
-
.FillPath(fill, path)
-
fill.Dispose()
-
-
'The GraphicsPath object won't be needed anymore
-
path.Dispose()
-
-
'From this point we must deal with reflected text. So it's a good idea to
-
'save the current state of our graphics object. What is really saved is the
-
'state of the objects (transformations applied, etc), not the drawings done
-
'until here.
-
Dim state As GraphicsState = .Save
-
-
'Reset the transformations done until here so we start from a "fresh clean"
-
'graphics object state.
-
.ResetTransform()
-
-
'ScaleTransform will set the graphics object into a state in which all the
-
'drawings done after the instruction will be affected by the scaling done.
-
'As we use 1 for horizontal value, the drawings will be not changed in the
-
'horizontal plane. But as we use -1 for the vertical value, all the drawings
-
'will be vertically inverted (the reflection effect that we want).
-
.ScaleTransform(1, -1)
-
-
'Now, as we did for the normal text, we'll create a rectangle that delimites
-
'the position and size of the reflected text
-
-
'The x-position must not be changed, as it is the same (horizontally centered)
-
-
'The y-pos must be vertically centered, so we start from
-
'(control height - text height) / 2
-
'From there, as we did with normal text, we must offset the half of the text height
-
'(in this case, offset to top)
-
'Also must offset the OffsetY value, to top too
-
'BUT we must remember that this will be drawn over a transformed (Scaled)
-
'graphics object, so must invert all signs (for example, offset to bottom instead
-
'to top)
-
ypos = (((((e.ClipRectangle.Height - height) / 2) + (height / 2)) * -1) - height) + (Me.OffsetY / 2)
-
-
'Create the rectangle
-
Dim reflectedRect As New RectangleF(xpos, ypos, width, height)
-
-
'Create the path to hold the text
-
Dim reflectedPath As GraphicsPath = New GraphicsPath
-
-
'Add the string to the path
-
reflectedPath.AddString(Me.Text, Me.Font.FontFamily, Me.Font.Style, (.DpiY / 72) * Me.Font.Size, reflectedRect, StringFormat.GenericDefault)
-
-
'Draw the outline, if it applies
-
If Me.OutlineWidth > 0 Then
-
'Note that we apply alpha transparency to the outline. If not, reflected
-
'text's outline will appear too much "solid"
-
Using p As Pen = New Pen(Color.FromArgb(Me.Alpha, Me.OutlineColor), Me.OutlineWidth)
-
.DrawPath(p, reflectedPath)
-
End Using
-
End If
-
-
'Create the brush to fill the reflected text
-
If Me.ForeGradient Then
-
'We must apply Alpha transparency on both gradient colors
-
fill = New LinearGradientBrush(reflectedRect, Color.FromArgb(Me.Alpha, Me.ForeGradientColor1), Color.FromArgb(Me.Alpha, Me.ForeGradientColor2), Me.ForeGradientMode)
-
Else
-
'Apply Alpha to solid color too
-
fill = New SolidBrush(Color.FromArgb(Me.Alpha, Me.ForeColor))
-
End If
-
-
'Draw the text (it will be automatically reflected because of the Scale
-
'transformation applied)
-
.FillPath(fill, reflectedPath)
-
-
'Destroy objects that are no more needed
-
fill.Dispose()
-
reflectedPath.Dispose()
-
-
'Restore the Graphics object state (eliminate transformations, so if we drew
-
'anymore from here will not be reflected)
-
.Restore(state)
-
'*********************************************************************************
-
End With
-
End Sub
-
End Class
翻译自: https://www.experts-exchange.com/articles/3623/Having-fun-with-graphics-a-VB-net-GlassLabel-control.html