UML软件工程组织

用VS.NET构造异形窗体-从入门到精通
作者:仙人掌工作室    本文选自:赛迪网  2003年04月08日

 


  从Windows操作系统诞生的第一天开始,所有的窗口都是矩形的。但是,打破这一戒规标新出异的软件正越来越多,即使是Microsoft这个矩形窗体的鼻祖,也开始使用不规则的异形窗体,Windows Media Player就是一个例子。可惜的是,构造异形窗体向来不是一件轻松的事情,不过现在不同了!有了.NET框架特别是Windows Forms包,即使构造复杂的窗体形状也变得轻而易举。

  既然已经提到Media Player,那就继续用它作为例子说明吧。Media Player运用了许多特殊的技术,足以作为典型的范例。想必大家的Windows系统上都安装了Media Player,你可以按照本文的说明马上打开试试。

  本文将示范异形窗体的构造过程,这个异形窗体拥有与Meida Player相似的外形。但在说明这一复杂异形窗体的构造过程之前,首先我们要了解一些基础知识。

  一、异形窗体基础

  构造异形窗体的基本思路很简单,只需定义向量形式的窗体轮廓,然后把这个窗体轮廓指定给窗体。

  窗体的外形由.NET框架类Region定义。每一个Windows的Form有一个成员对象Region,但在默认情况下,Form不会带有用户自定义的Region,其对象引用是null(C#)或Nothing(VB.NET),窗体显示为矩形(Windows XP的“主题”功能会修改窗体的外观,不过本文将忽略这一细节)。

  创建一个Region类的实例,填充异形窗体的形状信息,就可以修改窗体的外形。要做到这一点,最简单的办法是使用GraphicsPath对象。GraphicsPath是一个GDI+类,属于System.Drawing.Drawing2D名称空间。GraphicsPath类能够以向量的形式描述形状,用法很简单,只需给出窗体的轮廓定义即可。定义好的向量路径提交给Region对象的构造函数,Region对象自动把路径信息转换成形状定义数据。窗体获得形状数据之后,它的形状就随之改变。

  因此,要简单地改动一下窗体外形简直轻而易举。.NET的GraphicsPath类功能相当强大,部分方法可以说很复杂。不过,本文只需用到直线和弧形组合成的简单路径。

  二、构造椭圆窗体

  下面先来看一个椭圆窗体的简单例子。首先创建一个VS.NET Windows窗体工程,VS.NET将创建一个默认的矩形窗体。椭圆窗体轮廓的图形路径很简单,只需调用一下GraphicsPath对象的AddEllipse()方法即可得到。GraphicsPath提供了许多方法来构造复杂的向量路径,部分将在本文后面的例子用到,但现在我们只需要一个椭圆。下面这段代码显示了如何创建路径并加入一个椭圆:

Imports System.Drawing.Drawing2D

   Dim oPath As New GraphicsPath()
   oPath.AddEllipse(0, 0, 200, 100)


  这段代码定义的椭圆开始位置是(0,0),也就是窗体(即绘图平面)的左上角,椭圆的大小是200 X 100。注意,为了让这段代码顺利通过编译,必须导入System.Drawing.Drawing2D名称空间。所有的Windows窗体应用自动引用该名称空间,所以不必为它添加工程引用。

  AddEllipse()方法有几种重载的形态,其中一种允许传入一个Rectangle对象替代矩形的坐标。如果椭圆的矩形大小和窗体大小一样,用这个方法就很方便,因为每一个Windows的窗体都有一个ClientRectangle成员:

Dim oPath As New GraphicsPath()
   oPath.AddEllipse(Me.ClientRectangle)


  利用这个路径创建Region对象,然后再传递给窗体。下面是窗体的Load事件句柄:

Private 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


  对于C#,处理方法也和VB.NET相似。下面是生成椭圆窗体的C#代码:

private void Form1_Load(object sender, System.EventArgs e)
   {
   GraphicsPath oPath = new GraphicsPath();
   oPath.AddEllipse(this.ClientRectangle);
   this.Region = new Region(oPath);
   }




  图一:椭圆窗体

  鉴于C#和VB.NET在构造异形窗体方面的差别实在不大,下面的例子将只介绍VB.NET,想必C#开发者也一样能够通过VB.NET版的例子掌握异形窗体设计思路。

  现在可以运行这个VB.NET应用了,图一就是运行结果(加上了一个“关闭”按钮)。大家一眼就可以看出,这个窗口看起来有点古怪,就象其他Windows窗体一样,它也有一个标题,但椭圆切割了窗体标题,看起来特别不专业。



  图二:设计时异形窗体仍旧显示为矩形

  另一个马上会注意到的细节是在设计模式下,窗体仍旧显示为矩形(图二)。这很正常,只是稍微增加了在异形窗体中放置控件的难度,但除此之外,异形窗体的设计和编程完全与普通窗体一样,包括改变大小、拖放控件、设计事件句柄等。

  首先来解决窗口标题条的问题。一般地,大多数异形窗体不会使用操作系统默认加上的标题条,部分异形窗体会另外制作一个精致的标题条,通常由图形专家设计,以图形的形式放入窗体。有些应用能够以多种不同的模式运行,当它以异形窗体模式运行时,标题条隐藏,只有以普通窗体模式运行时,标题条才会显示出来。Windows Media Player就是一个很典型的例子,参见图三和图四。



  图三:以异形窗体模式运行的Media Player



  图四:以标准窗体模式运行的Media Player

  从图三和图四可以看出,异形窗体是从矩形窗体标题条下面“切割”出一部分来。对于前面例子中的椭圆窗体,我们也可以按照同样的方式处理。下面的代码片断针对窗体标题条和窗体边框作了调整:

' 计算椭圆的大小
   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)


  这段代码看起来要比实际情形复杂一些,大多数代码都在和SystemInformation类(及其静态方法)打交道,查询标题条高度之类的信息。

  再次运行这个VB.NET工程,可以看到它仍是一个椭圆,但要比以前的小一点,看起来舒服不少——虽然还不够完美,但至少改进了不少,标题条已经消失不见了。不过现在出现了另一个问题,在Windows中移动窗口最方便的办法就是点住标题条拖动,现在没有了标题条,要移动窗口就很困难了。

  三、实现拖动功能

  大多数异形窗体采用同样的办法解决窗体移动问题:允许用户点击窗体背景的任意位置移动窗体。Listing 1的代码给出了具体实现。这段代码对于任何异形窗体来说都很有用,所以作为一个新的窗体类ShapedForm实现,本文其余的窗体都将从这个窗体派生。

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


  移动窗体的功能通过窗体的鼠标事件句柄实现。当窗体遇到MouseDown事件,程序设置一个标志表示当前正处于“移动状态”,同时记录鼠标在窗体内的位置。MouseMove事件句柄检查窗体是否正处于移动状态,如果是,把窗体移动到新的位置。必须考虑到鼠标在窗体内原始的位置,否则,窗体的左上角将跳转到鼠标所在位置。当MouseUp事件出现时,窗体必须结束其“移动状态”,这样即使鼠标继续移动,窗体的位置也不会再移动。

  ShapedForm不仅实现了移动窗体的功能,而且还提供了一种标准的机制,允许通过覆盖SetInitialFormShape()方法创建简单的路径。SetInitialFormShape()方法的任务是创建窗体的oFormPath成员,即一个GraphicsPath对象。如果这个对象存在,窗体将自动采用它定义的形状。另外,这个窗体还会保留上次的形状(大多数情况下是正规的Windows矩形窗体),还有几个方法用来将窗体恢复成原来的外观。

  四、修饰窗体外观

  现在椭圆异形窗体已经能够移动,但看起来还不是很专业,其中一个原因是它没有一个相配的边框。为异形窗体加上边框没有什么捷径,Windows本身只能为矩形窗体描绘边框。另外,对于大多数实际应用中的异形窗体,画边框不会象本例的椭圆窗体那么简单。

  图五显示了经过修饰的椭圆窗体,它有蓝色的底色和一个简单的边框,边框有简单的3D风格,为了方便读者模仿本文的示例,所以一切从简。边框在窗体的Paint事件中绘制,用GDI+画出椭圆图形(类似于创建窗体轮廓的椭圆路径,但略小一些),用不同的颜色、大小重复绘制即可得到3D效果。



  图五:经过修饰的椭圆窗体

  绘制边框从左上角开始,左上角颜色是亮蓝色。然后,用暗蓝色的笔向右下角绘制,用普通的蓝色画出窗体中间的椭圆形作为窗体底色,最终得到三维的边框效果(仔细观察图五就可以看出来)。Listing 2给出绘制图五窗体的代码。这个窗体是ShapedForm类的子类,继承了前面实现的ShapedForm的功能。

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


  注意:另一种显示边框的方式是将它作为背景图形,实际上,许多采用异形窗体的应用看来正是采用这种方式,因此本文后面也将探讨这种绘制方式。

  五、构造复杂窗体形状

  前面介绍了构造异形窗体的基础知识,现在该是构造一个复杂异形窗体的时候了。所谓复杂,其实也只不过是在构造GraphicsPath的时候把许多小线段连接在一起。



  图六:用文字作为异形窗体的轮廓

  GraphicsPath对象提供了许多这方面的方法,包括:加入直线的方法AddLine(),加入曲线的方法AddArc()、AddCurve()等等,还有很多,甚至还有一个加入指定字体和大小的字符串的方法AddString(),它能够以向量图形的形式表示出指定的字符串。图六显示了一个形状为字符串轮廓的异形窗体,看起来就象是一些直接写在桌面背景上的图形,但它确实是一个异形窗体!“仙”字上面的按钮可以证明这一点。Listing 3显示了构造该异形窗体的代码。

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


  除了Listing 3之外,到目前为止本文用到的绘图方法还只有AddEllipse()一个。AddEllipse()方法创建一个“自包含”的完整的椭圆,换句话说,AddEllipse()方法画出的图形是封闭的,其起点和终点是同一个点。对于由许多小的基本线段(包括曲线)构成的路径,一个很重要的问题是最终必须让整个图形封闭,为此,GraphicsPath对象专门提供了一个自动封闭图形的CloseAllFigures()方法。



  图七:应用了Compact外观的Media Player

  现在我们再来看看Windows Media Player这个例子,考虑一下如何用.NET Windows窗体来构造出相似的窗体。大家知道,Media Player支持所谓的换“皮肤”功能,允许用户彻底改变应用运行的外观。图七就是Media Player支持的众多皮肤中的一种。

  这个皮肤令人感兴趣的不仅是它的外形,更特别的是,它有可打开和隐藏的面板,图八显示了面板打开后的Media Player外观。



  图八:打开面板后的Media Player

  要构造出类似的异形窗体,我们必须画出一个相当复杂的形状。从图七和图八可以看出,这个窗体轮廓包含了大量的直线和弧线。GraphicsPath对象的优点之一在于它很“聪明”,如果前一线段的终点和后一线段的起点不同,它会用直线把两个线段连接起来。因此,在绘制这个异形窗体时,我们(差不多)只要描述各个弧线就可以了,Listing 4给出了生成图形路径的代码。

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


  默认情况下,该窗体的两个面板都是隐藏的。窗体的两个域(bRightPanelVisible和bBottomPanelVisible)定义了右边面板和底部面板是否显示出来。如果面板是可见的,为了改变窗体的外形,程序创建的图形路径也不同。这样,我们可以在任何时候调用CreateShape()方法构造新的窗体轮廓,然后利用AssignShapePath()方法把修改后的窗体轮廓赋值给窗体。实际上,当用户点击窗体的某些特定区域时,程序就要照此步骤执行,其基本思路是:捕获鼠标点击面板控制开关的事件,在事件句柄中切换bRightPanelVisible和bBottomPanelVisible域的值,重新构造窗体轮廓,并把改变后的窗体轮廓指定给窗体。

  说明:当用户点击Media Player面板的控制开关时,面板从窗体的主体部分缓缓推出或缩进,类似一种滑动效果,但在本文的实现中,面板是直接跳出来的。滑动效果可以通过多次逐步改变窗体轮廓的方式获得,应当说,实现这一效果并不是特别复杂。但是,为简单起见,本文的例子不实现这一效果。

  不错,现在我们已经有了一个与众不同的窗体,但它还算不上漂亮,看起来似乎让人觉得是屏幕中央怪模怪样灰不溜秋的一块。要让它变得好看一些,一种简单的办法是加上背景图形。

  在这个例子中,我们要加上四个图形:其中一个用于所有面板关闭时,一个用于所有面板打开时,另外两个分别对应其中一个面板打开的情形。制作背景图形最简单的办法是:运行不带任何修饰的窗体,截取该窗体的图形,用图形编辑工具创建一个精确匹配该窗体轮廓的背景图形。注意背景图形必须和窗体轮廓精确匹配,否则窗体的周围可能留下空白点,使最后得到的窗体显得很难看。



  图九:定义一个类似于Media Player的异形窗体

  大多数图形编辑工具允许使用纹理和3D边框之类的特效。按照前面介绍的步骤,我们可以方便地构造出图九和图十显示的背景图形。窗体启动的时候预先装入这些图形,做好赋值给窗体BackgroundImage成员的准备。然后,当用户点击面板的控制开关时,只要调用SetBGImage()方法设置适当的背景图形就可以了。



  图十:完成后的复杂异形窗体

  从程序员的角度来看,创建异形窗体其实算不上特别困难的事情,只有当窗体轮廓的向量化描述极其复杂时才会让人感觉棘手。另外,除非你自己有相当好的艺术功底,否则可能需要一个图形专家来帮你制作背景图形。

  现在你已经知道怎样构造异形窗体了,你会发现把标准的Windows矩形控件放入异形窗体是多么格格不入。想象一下Windows Media Player换上一个标准“播放”按钮会成什么样子!接下来,也许你该精心打造匹配异形窗体的按钮和其他控件了。

(责任编辑:西门吹雪

  




版权所有:UML软件工程组织