|
<p > 从Windows操作系统诞生的第一天开始,所有的窗口都是矩形的。但是,打破这一戒规标新出异的软件正越来越多,即使是Microsoft这个矩形窗体的鼻祖,也开始使用不规则的异形窗体,Windows Media Player就是一个例子。可惜的是,构造异形窗体向来不是一件轻松的事情,不过现在不同了!有了.NET框架特别是Windows Forms包,即使构造复杂的窗体形状也变得轻而易举。 <p > 既然已经提到Media Player,那就继续用它作为例子说明吧。Media Player运用了许多特殊的技术,足以作为典型的范例。想必大家的Windows系统上都安装了Media Player,你可以按照本文的说明马上打开试试。 <p > 本文将示范异形窗体的构造过程,这个异形窗体拥有与Meida Player相似的外形。但在说明这一复杂异形窗体的构造过程之前,首先我们要了解一些基础知识。 <p > <b>一、异形窗体基础</b><p > 构造异形窗体的基本思路很简单,只需定义向量形式的窗体轮廓,然后把这个窗体轮廓指定给窗体。 <p > 窗体的外形由.NET框架类Region定义。每一个Windows的Form有一个成员对象Region,但在默认情况下,Form不会带有用户自定义的Region,其对象引用是null(C#)或Nothing(VB.NET),窗体显示为矩形(Windows XP的“主题”功能会修改窗体的外观,不过本文将忽略这一细节)。 <p > 创建一个Region类的实例,填充异形窗体的形状信息,就可以修改窗体的外形。要做到这一点,最简单的办法是使用GraphicsPath对象。GraphicsPath是一个GDI+类,属于System.Drawing.Drawing2D名称空间。GraphicsPath类能够以向量的形式描述形状,用法很简单,只需给出窗体的轮廓定义即可。定义好的向量路径提交给Region对象的构造函数,Region对象自动把路径信息转换成形状定义数据。窗体获得形状数据之后,它的形状就随之改变。 <p > 因此,要简单地改动一下窗体外形简直轻而易举。.NET的GraphicsPath类功能相当强大,部分方法可以说很复杂。不过,本文只需用到直线和弧形组合成的简单路径。 <p > <b>二、构造椭圆窗体</b><p > 下面先来看一个椭圆窗体的简单例子。首先创建一个VS.NET Windows窗体工程,VS.NET将创建一个默认的矩形窗体。椭圆窗体轮廓的图形路径很简单,只需调用一下GraphicsPath对象的AddEllipse()方法即可得到。GraphicsPath提供了许多方法来构造复杂的向量路径,部分将在本文后面的例子用到,但现在我们只需要一个椭圆。下面这段代码显示了如何创建路径并加入一个椭圆: <p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="2" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"><pre><ccid_code>Imports System.Drawing.Drawing2D Dim oPath As New GraphicsPath() oPath.AddEllipse(0, 0, 200, 100)</ccid_code></pre></td></tr></table></ccid_nobr><p > 这段代码定义的椭圆开始位置是(0,0),也就是窗体(即绘图平面)的左上角,椭圆的大小是200 X 100。注意,为了让这段代码顺利通过编译,必须导入System.Drawing.Drawing2D名称空间。所有的Windows窗体应用自动引用该名称空间,所以不必为它添加工程引用。 <p > AddEllipse()方法有几种重载的形态,其中一种允许传入一个Rectangle对象替代矩形的坐标。如果椭圆的矩形大小和窗体大小一样,用这个方法就很方便,因为每一个Windows的窗体都有一个ClientRectangle成员: <p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="2" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"><pre><ccid_code>Dim oPath As New GraphicsPath() oPath.AddEllipse(Me.ClientRectangle)</ccid_code></pre></td></tr></table></ccid_nobr><p > 利用这个路径创建Region对象,然后再传递给窗体。下面是窗体的Load事件句柄: <p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="2" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"><pre><ccid_code> rivate Sub Form1_Load( ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles MyBase.Load Dim oPath As New GraphicsPath() oPath.AddEllipse(Me.ClientRectangle) Me.Region = New Region(oPath) End Sub</ccid_code></pre></td></tr></table></ccid_nobr><p > 对于C#,处理方法也和VB.NET相似。下面是生成椭圆窗体的C#代码: <p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="2" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"><pre><ccid_code>private void Form1_Load(object sender, System.EventArgs e) { GraphicsPath oPath = new GraphicsPath(); oPath.AddEllipse(this.ClientRectangle); this.Region = new Region(oPath); }</ccid_code></pre></td></tr></table></ccid_nobr><p ><img src="http://www.hh010.com/upload_files/article/244/9_fgsnbd140581.gif"><p > 图一:椭圆窗体 <p > 鉴于C#和VB.NET在构造异形窗体方面的差别实在不大,下面的例子将只介绍VB.NET,想必C#开发者也一样能够通过VB.NET版的例子掌握异形窗体设计思路。 <p > 现在可以运行这个VB.NET应用了,图一就是运行结果(加上了一个“关闭”按钮)。大家一眼就可以看出,这个窗口看起来有点古怪,就象其他Windows窗体一样,它也有一个标题,但椭圆切割了窗体标题,看起来特别不专业。 <p ><img src="http://www.hh010.com/upload_files/article/244/9_mozu6a140582.gif"><p > 图二:设计时异形窗体仍旧显示为矩形 <p > 另一个马上会注意到的细节是在设计模式下,窗体仍旧显示为矩形(图二)。这很正常,只是稍微增加了在异形窗体中放置控件的难度,但除此之外,异形窗体的设计和编程完全与普通窗体一样,包括改变大小、拖放控件、设计事件句柄等。 <p > 首先来解决窗口标题条的问题。一般地,大多数异形窗体不会使用操作系统默认加上的标题条,部分异形窗体会另外制作一个精致的标题条,通常由图形专家设计,以图形的形式放入窗体。有些应用能够以多种不同的模式运行,当它以异形窗体模式运行时,标题条隐藏,只有以普通窗体模式运行时,标题条才会显示出来。Windows Media Player就是一个很典型的例子,参见图三和图四。 <p ><img src="http://www.hh010.com/upload_files/article/244/9_zjk9it140583.gif"><p > 图三:以异形窗体模式运行的Media Player <p ><img src="http://www.hh010.com/upload_files/article/244/9_k3d2wn140584.gif"><p > 图四:以标准窗体模式运行的Media Player <p > 从图三和图四可以看出,异形窗体是从矩形窗体标题条下面“切割”出一部分来。对于前面例子中的椭圆窗体,我们也可以按照同样的方式处理。下面的代码片断针对窗体标题条和窗体边框作了调整: <p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="2" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"><pre><ccid_code>' 计算椭圆的大小 Dim iElTop, iElLeft, iElHeight, iElWidth As Integer iElTop = SystemInformation.BorderSize.Height + _ SystemInformation.CaptionHeight + 2 iElLeft = SystemInformation.BorderSize.Width + 2 iElHeight = Height - iElTop - SystemInformation.BorderSize.Height - 3 iElWidth = Me.Width - iElLeft - SystemInformation.BorderSize.Width - 3 ' 创建图形路径并设置其大小 oPath = New GraphicsPath() oPath.AddEllipse(iElLeft, iElTop, iElWidth, iElHeight)</ccid_code></pre></td></tr></table></ccid_nobr><p > 这段代码看起来要比实际情形复杂一些,大多数代码都在和SystemInformation类(及其静态方法)打交道,查询标题条高度之类的信息。 <p > 再次运行这个VB.NET工程,可以看到它仍是一个椭圆,但要比以前的小一点,看起来舒服不少——虽然还不够完美,但至少改进了不少,标题条已经消失不见了。不过现在出现了另一个问题,在Windows中移动窗口最方便的办法就是点住标题条拖动,现在没有了标题条,要移动窗口就很困难了。 <p > <b>三、实现拖动功能</b><p > 大多数异形窗体采用同样的办法解决窗体移动问题:允许用户点击窗体背景的任意位置移动窗体。Listing 1的代码给出了具体实现。这段代码对于任何异形窗体来说都很有用,所以作为一个新的窗体类ShapedForm实现,本文其余的窗体都将从这个窗体派生。 <p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="2" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"><pre><ccid_code>Listing 1:ShapedForm类实现所有异形窗体必需的标准功能Imports System.Drawing.Drawing2D Public Class ShapedForm Inherits System.Windows.Forms.Form ' 可以在子类窗体Load事件之前赋值 Public oFormPath As GraphicsPath Private oOriginalRegion As Region = Nothing ' 用于窗体移动 Private bFormDragging As Boolean = False Private oPointClicked As Point #Region " Windows 窗体设计器生成的代码" ' 此处略... Private Sub ShapedForm_Load( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load ' 给子类提供一个设置窗体形状的机会 Me.SetInitialFormShape() If Not Me.oFormPath Is Nothing Then Me.AssignShapePath() End If End Sub Public Sub AssignShapePath() If Me.oOriginalRegion Is Nothing Then Me.oOriginalRegion = Me.Region End If Me.Region = New Region(Me.oFormPath) Me.Invalidate() End Sub Public Sub ResetShape() Me.Region = Me.oOriginalRegion Me.Invalidate() End Sub Public Overridable Sub SetInitialFormShape() ' 这个方法用来让子类覆盖 End Sub Private Sub ShapedForm_MouseDown( _ ByVal sender As Object, _ ByVal e As System.Windows.Forms.MouseEventArgs) _ Handles MyBase.MouseDown Me.bFormDragging = True Me.oPointClicked = New Point(e.X, e.Y) End Sub Private Sub ShapedForm_MouseUp( _ ByVal sender As Object, _ ByVal e As System.Windows.Forms.MouseEventArgs) _ Handles MyBase.MouseUp Me.bFormDragging = False End Sub Private Sub ShapedForm_MouseMove(ByVal sender As Object, _ ByVal e As System.Windows.Forms.MouseEventArgs) _ Handles MyBase.MouseMove If Me.bFormDragging Then Dim oMoveToPoint As Point ' 以当前鼠标位置为基础,找出目标位置 oMoveToPoint = Me.PointToScreen(New Point(e.X, e.Y)) ' 根据开始位置作出调整 oMoveToPoint.Offset(Me.oPointClicked.X * -1, _ (Me.oPointClicked.Y + _ SystemInformation.CaptionHeight + _ SystemInformation.BorderSize.Height) * -1) ' 移动窗体 Me.Location = oMoveToPoint End If End Sub End Class</ccid_code></pre></td></tr></table></ccid_nobr><p > 移动窗体的功能通过窗体的鼠标事件句柄实现。当窗体遇到MouseDown事件,程序设置一个标志表示当前正处于“移动状态”,同时记录鼠标在窗体内的位置。MouseMove事件句柄检查窗体是否正处于移动状态,如果是,把窗体移动到新的位置。必须考虑到鼠标在窗体内原始的位置,否则,窗体的左上角将跳转到鼠标所在位置。当MouseUp事件出现时,窗体必须结束其“移动状态”,这样即使鼠标继续移动,窗体的位置也不会再移动。 <p > ShapedForm不仅实现了移动窗体的功能,而且还提供了一种标准的机制,允许通过覆盖SetInitialFormShape()方法创建简单的路径。SetInitialFormShape()方法的任务是创建窗体的oFormPath成员,即一个GraphicsPath对象。如果这个对象存在,窗体将自动采用它定义的形状。另外,这个窗体还会保留上次的形状(大多数情况下是正规的Windows矩形窗体),还有几个方法用来将窗体恢复成原来的外观。 <p > <b>四、修饰窗体外观</b><p > 现在椭圆异形窗体已经能够移动,但看起来还不是很专业,其中一个原因是它没有一个相配的边框。为异形窗体加上边框没有什么捷径,Windows本身只能为矩形窗体描绘边框。另外,对于大多数实际应用中的异形窗体,画边框不会象本例的椭圆窗体那么简单。 <p > 图五显示了经过修饰的椭圆窗体,它有蓝色的底色和一个简单的边框,边框有简单的3D风格,为了方便读者模仿本文的示例,所以一切从简。边框在窗体的Paint事件中绘制,用GDI+画出椭圆图形(类似于创建窗体轮廓的椭圆路径,但略小一些),用不同的颜色、大小重复绘制即可得到3D效果。 <p ><img src="http://www.hh010.com/upload_files/article/244/9_mu5hsb140585.gif"><p > 图五:经过修饰的椭圆窗体 <p > 绘制边框从左上角开始,左上角颜色是亮蓝色。然后,用暗蓝色的笔向右下角绘制,用普通的蓝色画出窗体中间的椭圆形作为窗体底色,最终得到三维的边框效果(仔细观察图五就可以看出来)。Listing 2给出绘制图五窗体的代码。这个窗体是ShapedForm类的子类,继承了前面实现的ShapedForm的功能。 <p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="2" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"><pre><ccid_code>Listing 2:带有3D边框的椭圆窗体Imports System.Drawing.Drawing2D Public Class EllipticalForm Inherits ShapedForm Private iElTop, iElLeft, iElHeight, iElWidth As Integer Private iElTopOffset, iElLeftOffset #Region " Windows 窗体设计器生成的代码" ' 此处略... Public Overrides Sub SetInitialFormShape() ' 计算椭圆的大小 Me.iElTop = SystemInformation.BorderSize.Height _ + SystemInformation.CaptionHeight + 2 Me.iElLeft = SystemInformation.BorderSize.Width + 2 Me.iElHeight = Me.Height - Me.iElTop - _ SystemInformation.BorderSize.Height - 3 Me.iElWidth = Me.Width - Me.iElLeft - _ SystemInformation.BorderSize.Width - 3 ' 记住椭圆的偏移量,以便以后绘制时使用 Me.iElLeftOffset = -2 Me.iElTopOffset = _ (SystemInformation.CaptionHeight + 2) * -1 ' 创建图形路径 Me.oFormPath = New GraphicsPath() Me.oFormPath.AddEllipse(Me.iElLeft, Me.iElTop, _ Me.iElWidth, Me.iElHeight) End Sub Private Sub EllipticalForm_Paint( _ ByVal sender As Object, _ ByVal e As System.Windows.Forms.PaintEventArgs) _ Handles MyBase.Paint ' 创建另一个椭圆(略小一点) Dim oInteriorPath As New GraphicsPath() oInteriorPath.AddEllipse(Me.iElLeft, Me.iElTop, _ Me.iElWidth - 6, Me.iElHeight - 6) ' 创建画笔,用来绘制边框和背景 Dim oLightPen As New Pen(Color.FromArgb(100, 100, 255), 7) Dim oDarkPen As New Pen(Color.FromArgb(0, 0, 120), 7) ' 调整位置,绘制边框和背景 e.Graphics.TranslateTransform(Me.iElLeftOffset - 1, _ Me.iElTopOffset - 1) e.Graphics.DrawPath(oLightPen, oInteriorPath) e.Graphics.TranslateTransform(4, 4) e.Graphics.DrawPath(oDarkPen, oInteriorPath) e.Graphics.TranslateTransform(-2, -2) e.Graphics.FillPath(Brushes.Blue, oInteriorPath) e.Graphics.ResetTransform() ' 清理 oInteriorPath.Dispose() oLightPen.Dispose() oDarkPen.Dispose() End Sub Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click Me.Dispose() End Sub Private Sub Button2_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button2.Click Me.ResetShape() End Sub Private Sub Button3_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button3.Click Me.AssignShapePath() End Sub End Class</ccid_code></pre></td></tr></table></ccid_nobr><p > 注意:另一种显示边框的方式是将它作为背景图形,实际上,许多采用异形窗体的应用看来正是采用这种方式,因此本文后面也将探讨这种绘制方式。 <p > <b>五、构造复杂窗体形状</b><p > 前面介绍了构造异形窗体的基础知识,现在该是构造一个复杂异形窗体的时候了。所谓复杂,其实也只不过是在构造GraphicsPath的时候把许多小线段连接在一起。 <p ><img src="http://www.hh010.com/upload_files/article/244/9_ewzzil140586.gif"><p > 图六:用文字作为异形窗体的轮廓 <p > GraphicsPath对象提供了许多这方面的方法,包括:加入直线的方法AddLine(),加入曲线的方法AddArc()、AddCurve()等等,还有很多,甚至还有一个加入指定字体和大小的字符串的方法AddString(),它能够以向量图形的形式表示出指定的字符串。图六显示了一个形状为字符串轮廓的异形窗体,看起来就象是一些直接写在桌面背景上的图形,但它确实是一个异形窗体!“仙”字上面的按钮可以证明这一点。Listing 3显示了构造该异形窗体的代码。 <p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="2" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"><pre><ccid_code>Listing 3:用文字定义异形窗体的轮廓Imports System.Drawing.Drawing2D Public Class CodeForm Inherits ShapedForm #Region " Windows 窗体设计器生成的代码" ' 此处略... Public Overrides Sub SetInitialFormShape() Me.oFormPath = New GraphicsPath() oFormPath.AddString("仙人掌", _ New FontFamily("隶书"), _ FontStyle.Bold, 200, Me.ClientRectangle, _ StringFormat.GenericDefault) Me.Region = New Region(oFormPath) End Sub End Class</ccid_code></pre></td></tr></table></ccid_nobr><p > 除了Listing 3之外,到目前为止本文用到的绘图方法还只有AddEllipse()一个。AddEllipse()方法创建一个“自包含”的完整的椭圆,换句话说,AddEllipse()方法画出的图形是封闭的,其起点和终点是同一个点。对于由许多小的基本线段(包括曲线)构成的路径,一个很重要的问题是最终必须让整个图形封闭,为此,GraphicsPath对象专门提供了一个自动封闭图形的CloseAllFigures()方法。 <p ><img src="http://www.hh010.com/upload_files/article/244/9_50rq9r140587.gif"><p > 图七:应用了Compact外观的Media Player <p > 现在我们再来看看Windows Media Player这个例子,考虑一下如何用.NET Windows窗体来构造出相似的窗体。大家知道,Media Player支持所谓的换“皮肤”功能,允许用户彻底改变应用运行的外观。图七就是Media Player支持的众多皮肤中的一种。 <p > 这个皮肤令人感兴趣的不仅是它的外形,更特别的是,它有可打开和隐藏的面板,图八显示了面板打开后的Media Player外观。 <p ><img src="http://www.hh010.com/upload_files/article/244/9_fzna2v140588.gif"><p > 图八:打开面板后的Media Player <p > 要构造出类似的异形窗体,我们必须画出一个相当复杂的形状。从图七和图八可以看出,这个窗体轮廓包含了大量的直线和弧线。GraphicsPath对象的优点之一在于它很“聪明”,如果前一线段的终点和后一线段的起点不同,它会用直线把两个线段连接起来。因此,在绘制这个异形窗体时,我们(差不多)只要描述各个弧线就可以了,Listing 4给出了生成图形路径的代码。 <p ><ccid_nobr><table width="550" border="1" cellspacing="0" cellpadding="2" bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"><tr><td bgcolor="e6e6e6" class="code"><pre><ccid_code>Listing 4:能够动态改变的复杂异形窗体Imports System.Drawing.Drawing2D Public Class ComplexShape Inherits ShapedForm ' 两个面板是否打开(默认隐藏) Private bRightPanelVisible As Boolean = False Private bBottomPanelVisible As Boolean = False ' 这是我们要用到的图形 Private imgCollapsed As _ New Bitmap("..\Images\CollapsedForm.bmp") Private imgRightPanel As _ New Bitmap("..\Images\RightPanelForm.bmp") Private imgBottomPanel As _ New Bitmap("..\Images\BottomPanelForm.bmp") Private imgExpanded As New Bitmap _ ("..\Images\ExpandedForm.bmp") #Region " Windows 窗体设计器生成的代码" ' 此处略... Public Overrides Sub SetInitialFormShape() ' 设置初始的背景图形 Me.SetBGImage() ' 创建窗体的轮廓 Me.CreateShape() End Sub Private Sub CreateShape() ' 处理边框和标题条 Dim iOX As Integer = SystemInformation.BorderSize.Width + 2 Dim iOY As Integer = SystemInformation.CaptionHeight + _ (SystemInformation.BorderSize.Height * 2) + 2 ' 生成窗体外形的图形路径 Me.oFormPath = New GraphicsPath() ' 左上方 Me.oFormPath.AddArc(42 + iOX, 285 + iOY, 70, 80, 90, 75) Me.oFormPath.AddArc(15 + iOX, 311 + iOY, 25, 40, -20, -60) Me.oFormPath.AddArc(0 + iOX, 263 + iOY, 50, 45, 110, 70) Me.oFormPath.AddArc(1 + iOX, 42 + iOY - 31, _ 68, 68, 180, 90) Me.oFormPath.AddArc(96 + iOX, -28 + iOY, 34, 40, 90, -60) Me.oFormPath.AddArc(376 + iOX - 34, 0 + iOY, _ 68, 68, 270, 90) ' 右边面板 If Not Me.bRightPanelVisible Then Me.oFormPath.AddArc(396 + iOX, 102 + iOY, 26, 112, _ 270, 180) Else Me.oFormPath.AddLine(409 + iOX, 36 + iOY, 581 + iOX, _ 36 + iOY) Me.oFormPath.AddArc(576 + iOX, 36 + iOY, _ 10, 10, 270, 90) Me.oFormPath.AddArc(574 + iOX, 102 + iOY, 26, 112, _ 270, 180) Me.oFormPath.AddArc(576 + iOX, 287 + iOY, 10, 10, 0, 90) Me.oFormPath.AddLine(580 + iOX, 298 + iOY, 409 + iOX, _ 298 + iOY) End If ' 右下方 Me.oFormPath.AddArc(409 - 68 + iOX, 333 - 36 + iOY, _ 68, 68, 0, 90) ' 底部面板 If Not Me.bBottomPanelVisible Then Me.oFormPath.AddArc(165 + iOX, 352 + iOY, _ 112, 26, 0, 180) Else Me.oFormPath.AddLine(366 + iOX, 365 + iOY, 366 + iOX, _ 461 + iOY) Me.oFormPath.AddArc(355 + iOX, 456 + iOY, 10, 10, 0, 90) Me.oFormPath.AddArc(165 + iOX, 454 + iOY, _ 112, 26, 0, 180) Me.oFormPath.AddArc(75 + iOX, 456 + iOY, 10, 10, 90, 90) Me.oFormPath.AddLine(75 + iOX, 461 + iOY, 75 + iOX, _ 365 + iOY) End If ' 默认窗体形状 Me.oFormPath.CloseAllFigures() End Sub Private Sub ComplexShape_MouseUp( _ ByVal sender As Object, _ ByVal e As System.Windows.Forms.MouseEventArgs) _ Handles MyBase.MouseUp ' 检查是否有人点击了面板的控制开关 If Me.bRightPanelVisible Then If e.X > 572 And e.X < 600 And _ e.Y > 100 And e.Y < 220 Then ' 隐藏右边面板 Me.bRightPanelVisible = False Me.CreateShape() Me.AssignShapePath() Me.SetBGImage() End If Else If e.X > 390 And e.X < 420 And _ e.Y > 100 And e.Y < 220 Then ' 打开右边面板 Me.bRightPanelVisible = True Me.CreateShape() Me.AssignShapePath() Me.SetBGImage() End If End If If Me.bBottomPanelVisible Then If e.X > 160 And e.X < 285 And _ e.Y > 450 And e.Y < 480 Then ' 隐藏底部面板 Me.bBottomPanelVisible = False Me.CreateShape() Me.AssignShapePath() Me.SetBGImage() End If Else If e.X > 160 And e.X < 285 And _ e.Y > 350 And e.Y < 380 Then ' 打开底部面板 Me.bBottomPanelVisible = True Me.CreateShape() Me.AssignShapePath() Me.SetBGImage() End If End If End Sub Private Sub SetBGImage() If Not Me.bRightPanelVisible Then If Not Me.bBottomPanelVisible Then ' 隐藏面板 Me.BackgroundImage = Me.imgCollapsed Else Me.BackgroundImage = Me.imgBottomPanel End If Else If Not Me.bBottomPanelVisible Then ' 隐藏面板 Me.BackgroundImage = Me.imgRightPanel Else Me.BackgroundImage = Me.imgExpanded End If End If End Sub End Class</ccid_code></pre></td></tr></table></ccid_nobr><p > 默认情况下,该窗体的两个面板都是隐藏的。窗体的两个域(bRightPanelVisible和bBottomPanelVisible)定义了右边面板和底部面板是否显示出来。如果面板是可见的,为了改变窗体的外形,程序创建的图形路径也不同。这样,我们可以在任何时候调用CreateShape()方法构造新的窗体轮廓,然后利用AssignShapePath()方法把修改后的窗体轮廓赋值给窗体。实际上,当用户点击窗体的某些特定区域时,程序就要照此步骤执行,其基本思路是:捕获鼠标点击面板控制开关的事件,在事件句柄中切换bRightPanelVisible和bBottomPanelVisible域的值,重新构造窗体轮廓,并把改变后的窗体轮廓指定给窗体。 <p > 说明:当用户点击Media Player面板的控制开关时,面板从窗体的主体部分缓缓推出或缩进,类似一种滑动效果,但在本文的实现中,面板是直接跳出来的。滑动效果可以通过多次逐步改变窗体轮廓的方式获得,应当说,实现这一效果并不是特别复杂。但是,为简单起见,本文的例子不实现这一效果。 <p > 不错,现在我们已经有了一个与众不同的窗体,但它还算不上漂亮,看起来似乎让人觉得是屏幕中央怪模怪样灰不溜秋的一块。要让它变得好看一些,一种简单的办法是加上背景图形。 <p > 在这个例子中,我们要加上四个图形:其中一个用于所有面板关闭时,一个用于所有面板打开时,另外两个分别对应其中一个面板打开的情形。制作背景图形最简单的办法是:运行不带任何修饰的窗体,截取该窗体的图形,用图形编辑工具创建一个精确匹配该窗体轮廓的背景图形。注意背景图形必须和窗体轮廓精确匹配,否则窗体的周围可能留下空白点,使最后得到的窗体显得很难看。 <p ><img src="http://www.hh010.com/upload_files/article/244/9_ws0qoz140589.gif"><p > 图九:定义一个类似于Media Player的异形窗体 <p > 大多数图形编辑工具允许使用纹理和3D边框之类的特效。按照前面介绍的步骤,我们可以方便地构造出图九和图十显示的背景图形。窗体启动的时候预先装入这些图形,做好赋值给窗体BackgroundImage成员的准备。然后,当用户点击面板的控制开关时,只要调用SetBGImage()方法设置适当的背景图形就可以了。 <p ><img src="http://www.hh010.com/upload_files/article/244/9_lvytvg140590.gif"><p > 图十:完成后的复杂异形窗体 <p > 从程序员的角度来看,创建异形窗体其实算不上特别困难的事情,只有当窗体轮廓的向量化描述极其复杂时才会让人感觉棘手。另外,除非你自己有相当好的艺术功底,否则可能需要一个图形专家来帮你制作背景图形。 <p > 现在你已经知道怎样构造异形窗体了,你会发现把标准的Windows矩形控件放入异形窗体是多么格格不入。想象一下Windows Media Player换上一个标准“播放”按钮会成什么样子!接下来,也许你该精心打造匹配异形窗体的按钮和其他控件了。 <p ><center>(责任编辑:<ccid_nobr>西门吹雪</ccid_nobr>)</center><p align="center"></p></p> |
|