用VB打造“超酷”个性化菜单


  众所周知,MS Office 2003推出已经有一段时间了,但我们依然不会忘记Office XP刚刚推出时其令人耳目一新的菜单给我们留下的深刻印象。突起的悬浮式图标,不同寻常的菜单项填充方式,不仅让办公一族们赞不绝口,更让广大的程序员和编程爱好者对这种风格的菜单的制作产生了浓厚的兴趣。所以,在这篇文章里,我们就来好好地研究研究用VB怎么制作这种风格的菜单,在文章的最后,我将给出源代码的下载地址。事实上,在了解其原理以后,不论是用VB、VC还是Delphi,都能够制作出XP风格的菜单。不仅如此,我们还可以制作出更加充满个性的另类风格的菜单,比如3D立体风格、渐变风格、多彩风格等等。只有想不到的,没有做不到的。Follow me!

  现在,我想有必要说一说我们现在要做的事情。事实上,我们只要做一个菜单类就行了。但谁都会明白,只做一个菜单类是不够的,我们需要一个程序,或者更详细的说,是一个窗体,来测试我们的菜单类。在我个人的开发过程中,我是先写的菜单类,后写的测试窗体,但为了让大家先领略一下写好的菜单类在应用时是多么的方便,所以让我们先来看看测试窗体:

  (1)打开VB,新建“标准EXE”工程。

  (2)--下面是窗体的控件:

组件名称 属性
Form Name frmMain
Caption 菜单例子
Frame Name fraStyle
Caption 菜单风格
Label Name lblHelp
Caption 在窗体空白处单击鼠标右键
OptionButton Name opnStyle
Caption Window 标准
Index 0
OptionButton Name opnStyle
Caption XP 风格
Index 1
OptionButton Name opnStyle
Caption 3D 立体风格
Index 2
OptionButton Name opnStyle
Caption 渐变风格
Index 3
OptionButton Name opnStyle
Caption 多彩风格
Index 4

  其实就是在窗体上添加了一个Frame,然后在Frame里添加OptionButton控件数组,用来设置菜单风格,还有一个Label,上面只显示一行提示文字,非常简单。

  (3)窗体代码:

Option Explicit

Private Declare Function GetCursorPos Lib "user32.dll" (lpPoint As POINTAPI) As Long
Private Type POINTAPI
 X As Long
 Y As Long
End Type

Dim menu As cMenu

Private Sub Form_Load()

 ' 初始化菜单并添加菜单项

 Set menu = New cMenu
 menu.CreateMenu
 menu.AddItem "open", LoadPicture("images\open.ico"), "打开", MIT_STRING
 menu.AddItem "save", LoadPicture("images\save.ico"), "保存", MIT_STRING
 menu.AddItem "print", LoadPicture("images\print.ico"), "打印", MIT_STRING
 menu.AddItem "find", LoadPicture("images\find.ico"), "查找", MIT_STRING
 menu.AddItem "sep1", LoadPicture(), "", MIT_SEPARATOR
 menu.AddItem "undo", LoadPicture("images\undo.ico"), "撤消", MIT_STRING
 menu.AddItem "redo", LoadPicture("images\redo.ico"), "重复", MIT_STRING
 menu.AddItem "sep2", LoadPicture(), "", MIT_SEPARATOR
 menu.AddItem "cut", LoadPicture("images\cut.ico"), "剪切", MIT_STRING
 menu.AddItem "copy", LoadPicture("images\copy.ico"), "复制", MIT_STRING
 menu.AddItem "paste", LoadPicture("images\paste.ico"), "粘贴", MIT_STRING
 menu.AddItem "sep3", LoadPicture(), "", MIT_SEPARATOR
 menu.AddItem "check", LoadPicture("images\check.ico"), "一个 CheckBox", MIT_CHECKBOX
 menu.AddItem "exit", LoadPicture("images\exit.ico"), "退出", MIT_STRING

End Sub

Private Sub Form_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)

 ' 单击鼠标右建弹出菜单
 If Button = vbRightButton Then
  Dim pos As POINTAPI
  GetCursorPos pos
  menu.PopupMenu pos.X, pos.Y, POPUP_LEFTALIGN Or POPUP_TOPALIGN
 End If

End Sub

Private Sub Form_Unload(Cancel As Integer)
 ' 释放资源, 卸载窗体
 Set menu = Nothing
 Dim frm As Form
 For Each frm In Forms
  Unload frm
 Next
End Sub

Private Sub opnStyle_Click(Index As Integer)

 ' 设置菜单风格

 Select Case Index
  Case 0 ' Windows 标准
   menu.Style = STYLE_WINDOWS
  Case 1 ' XP 风格
   menu.Style = STYLE_XP
  Case 2 ' 3D 立体风格
   menu.Style = STYLE_3D
  Case 3 ' 渐变风格
   menu.Style = STYLE_SHADE
  Case 4 ' 多彩风格
   menu.Style = STYLE_COLORFUL
 End Select

End Sub

  代码中创建了一个cMenu类的对象,我们的编程重点将会放在cMenu类上,上面的代码只是简单地调用cMenu。在后面的文章中,我们会看到其实cMenu有多达30个方法和属性供我们调用,它的Style属性只提供了5种内置风格,在实际应用中,我们可以利用cMenu类提供的方法和属性制作出各种各样风格的菜单,为自己的程序锦上添花。

  (4)运行结果:


        图1


         图2


         图3


         图4


         图5

  下面我们来创建接收消息的窗体:打开上面建好的工程,添加一个窗体,并将其名称设置为frmMenu(注意:这一步是必须的)。图5菜单左边那个黑底色的附加条,为了方便,将frmMenu的Picture属性设置成图5。到此,这个窗体就算OK了!对了,就这样,因为这个窗体仅仅是为了处理消息和存储那个黑底色的风格条,我们将会对它进行子类处理,处理消息的代码全部都放在了将在下面详细介绍的标准模块中。

  接下来添加一个类模块,并将其名称设置为cMenu,代码如下:


'***************************************************************
'* 本类模块是一个菜单类, 提供了各种样式的菜单的制作方案
'*
'* 版权: LPP软件工作室
'* 作者: 卢培培(goodname008)
'* (******* 复制请保留以上信息 *******)
'*********************************************************************

Option Explicit

Private Declare Function TrackPopupMenu Lib "user32" (ByVal hMenu As Long, ByVal wFlags As Long, ByVal x As Long, ByVal y As Long, ByVal nReserved As Long, ByVal hwnd As Long, lprc As Any) As Long

Public Enum MenuUserStyle ' 菜单总体风格
 STYLE_WINDOWS
 STYLE_XP
 STYLE_SHADE
 STYLE_3D
 STYLE_COLORFUL
End Enum

Public Enum MenuSeparatorStyle ' 菜单分隔条风格
 MSS_SOLID
 MSS_DASH
 MSS_DOT
 MSS_DASDOT
 MSS_DASHDOTDOT
 MSS_NONE
 MSS_DEFAULT
End Enum

Public Enum MenuItemSelectFillStyle ' 菜单项背景填充风格
 ISFS_NONE
 ISFS_SOLIDCOLOR
 ISFS_HORIZONTALCOLOR
 ISFS_VERTICALCOLOR
End Enum

Public Enum MenuItemSelectEdgeStyle ' 菜单项边框风格
 ISES_SOLID
 ISES_DASH
 ISES_DOT
 ISES_DASDOT
 ISES_DASHDOTDOT
 ISES_NONE
 ISES_SUNKEN
 ISES_RAISED
End Enum

Public Enum MenuItemIconStyle ' 菜单项图标风格
 IIS_NONE
 IIS_SUNKEN
 IIS_RAISED
 IIS_SHADOW
End Enum

Public Enum MenuItemSelectScope ' 菜单项高亮条的范围
 ISS_TEXT = &H1
 ISS_ICON_TEXT = &H2
 ISS_LEFTBAR_ICON_TEXT = &H4
End Enum

Public Enum MenuLeftBarStyle ' 菜单附加条风格
 LBS_NONE
 LBS_SOLIDCOLOR
 LBS_HORIZONTALCOLOR
 LBS_VERTICALCOLOR
 LBS_IMAGE
End Enum

Public Enum MenuItemType ' 菜单项类型
 MIT_STRING = &H0
 MIT_CHECKBOX = &H200
 MIT_SEPARATOR = &H800
End Enum

Public Enum MenuItemState ' 菜单项状态
 MIS_ENABLED = &H0
 MIS_DISABLED = &H2
 MIS_CHECKED = &H8
 MIS_UNCHECKED = &H0
End Enum

Public Enum PopupAlign ' 菜单弹出对齐方式
 POPUP_LEFTALIGN = &H0& ' 水平左对齐
 POPUP_CENTERALIGN = &H4& ' 水平居中对齐
 POPUP_RIGHTALIGN = &H8& ' 水平右对齐
 POPUP_TOPALIGN = &H0& ' 垂直上对齐
 POPUP_VCENTERALIGN = &H10& ' 垂直居中对齐
 POPUP_BOTTOMALIGN = &H20& ' 垂直下对齐
End Enum

' 释放类

Private Sub Class_Terminate()
 SetWindowLong frmMenu.hwnd, GWL_WNDPROC, preMenuWndProc
 Erase MyItemInfo
 DestroyMenu hMenu
End Sub

' 创建弹出式菜单

Public Sub CreateMenu()
 preMenuWndProc = SetWindowLong(frmMenu.hwnd, GWL_WNDPROC, AddressOf MenuWndProc)
 hMenu = CreatePopupMenu()
 Me.Style = STYLE_WINDOWS
End Sub

' 插入菜单项并保存自定义菜单项数组, 设置Owner_Draw自绘菜单

Public Sub AddItem(ByVal itemAlias As String, ByVal itemIcon As StdPicture,
  ByVal itemText As String, ByVal itemType As MenuItemType,
  Optional ByVal itemState As MenuItemState)

 Static ID As Long, i As Long
 Dim ItemInfo As MENUITEMINFO
 ' 插入菜单项
 With ItemInfo
  .cbSize = LenB(ItemInfo)
  .fMask = MIIM_STRING Or MIIM_FTYPE Or MIIM_STATE Or MIIM_SUBMENU Or MIIM_ID Or MIIM_DATA

  .fType = itemType
  .fState = itemState
  .wID = ID
  .dwItemData = True
  .cch = lstrlen(itemText)
  .dwTypeData = itemText
 End With

 InsertMenuItem hMenu, ID, False, ItemInfo

 ' 将菜单项数据存入动态数组

 ReDim Preserve MyItemInfo(ID) As MyMenuItemInfo
 For i = 0 To UBound(MyItemInfo)
  If MyItemInfo(i).itemAlias = itemAlias Then
   Class_Terminate
   Err.Raise vbObjectError + 513, "cMenu", "菜单项别名相同."
  End If
 Next i

 With MyItemInfo(ID)
  Set .itemIcon = itemIcon
  .itemText = itemText
  .itemType = itemType
  .itemState = itemState
  .itemAlias = itemAlias
 End With

 ' 获得菜单项数据

 With ItemInfo
  .cbSize = LenB(ItemInfo)
  .fMask = MIIM_DATA Or MIIM_ID Or MIIM_TYPE
 End With

 GetMenuItemInfo hMenu, ID, False, ItemInfo

 ' 设置菜单项数据

 With ItemInfo
  .fMask = .fMask Or MIIM_TYPE
  .fType = MFT_OWNERDRAW
 End With

 SetMenuItemInfo hMenu, ID, False, ItemInfo

 ' 菜单项ID累加
 ID = ID + 1
End Sub

' 删除菜单项

Public Sub DeleteItem(ByVal itemAlias As String)
 Dim i As Long
 For i = 0 To UBound(MyItemInfo)
  If MyItemInfo(i).itemAlias = itemAlias Then
   DeleteMenu hMenu, i, 0
   Exit For
  End If
 Next i

End Sub

' 弹出菜单

Public Sub PopupMenu(ByVal x As Long, ByVal y As Long, ByVal Align As PopupAlign)
 TrackPopupMenu hMenu, Align, x, y, 0, frmMenu.hwnd, ByVal 0
End Sub

' 设置菜单项图标

Public Sub SetItemIcon(ByVal itemAlias As String, ByVal itemIcon As StdPicture)

 Dim i As Long
 For i = 0 To UBound(MyItemInfo)
  If MyItemInfo(i).itemAlias = itemAlias Then
   Set MyItemInfo(i).itemIcon = itemIcon
   Exit For
  End If
 Next i

End Sub

' 获得菜单项图标

Public Function GetItemIcon(ByVal itemAlias As String) As StdPicture

 Dim i As Long
 For i = 0 To UBound(MyItemInfo)
  If MyItemInfo(i).itemAlias = itemAlias Then
   Set GetItemIcon = MyItemInfo(i).itemIcon
   Exit For
  End If
 Next i
End Function

' 设置菜单项文字

Public Sub SetItemText(ByVal itemAlias As String, ByVal itemText As String)

 Dim i As Long
 For i = 0 To UBound(MyItemInfo)
  If MyItemInfo(i).itemAlias = itemAlias Then
   MyItemInfo(i).itemText = itemText
   Exit For
  End If
 Next i
End Sub

' 获得菜单项文字

Public Function GetItemText(ByVal itemAlias As String) As String

 Dim i As Long
 For i = 0 To UBound(MyItemInfo)
  If MyItemInfo(i).itemAlias = itemAlias Then
   GetItemText = MyItemInfo(i).itemText
   Exit For
  End If
 Next i

End Function

' 设置菜单项状态
Public Sub SetItemState(ByVal itemAlias As String, ByVal itemState As MenuItemState)

 Dim i As Long
 For i = 0 To UBound(MyItemInfo)
  If MyItemInfo(i).itemAlias = itemAlias Then
   MyItemInfo(i).itemState = itemState
   Dim ItemInfo As MENUITEMINFO
   With ItemInfo
    .cbSize = Len(ItemInfo)
    .fMask = MIIM_STRING Or MIIM_FTYPE Or MIIM_STATE Or MIIM_SUBMENU Or MIIM_ID Or MIIM_DATA

   End With
   GetMenuItemInfo hMenu, i, False, ItemInfo
   With ItemInfo
    .fState = .fState Or itemState
   End With
   SetMenuItemInfo hMenu, i, False, ItemInfo
   Exit For
  End If
 Next i

End Sub

' 获得菜单项状态

Public Function GetItemState(ByVal itemAlias As String) As MenuItemState

 Dim i As Long
 For i = 0 To UBound(MyItemInfo)
  If MyItemInfo(i).itemAlias = itemAlias Then
   GetItemState = MyItemInfo(i).itemState
   Exit For
  End If
 Next i

End Function

' 属性: 菜单句柄

Public Property Get hwnd() As Long
 hwnd = hMenu
End Property

Public Property Let hwnd(ByVal nValue As Long)

End Property

' 属性: 菜单附加条宽度

Public Property Get LeftBarWidth() As Long
 LeftBarWidth = BarWidth
End Property

Public Property Let LeftBarWidth(ByVal nBarWidth As Long)
 If nBarWidth >= 0 Then
  BarWidth = nBarWidth
 End If
End Property

' 属性: 菜单附加条风格

Public Property Get LeftBarStyle() As MenuLeftBarStyle
 LeftBarStyle = BarStyle
End Property

Public Property Let LeftBarStyle(ByVal nBarStyle As MenuLeftBarStyle)
 If nBarStyle >= 0 And nBarStyle <= 4 Then
  BarStyle = nBarStyle
 End If
End Property

' 属性: 菜单附加条图像(只有当 LeftBarStyle 设置为 LBS_IMAGE 时才有效)

Public Property Get LeftBarImage() As StdPicture
 Set LeftBarImage = BarImage
End Property

Public Property Let LeftBarImage(ByVal nBarImage As StdPicture)
 Set BarImage = nBarImage
End Property

' 属性: 菜单附加条过渡色起始颜色(
'只有当 LeftBarStyle 设置为 LBS_HORIZONTALCOLOR 或 LBS_VERTICALCOLOR 时才有效)
' 当 LeftBarStyle 设置为 LBS_SOLIDCOLOR (实色填充)时以 LeftBarStartColor 颜色为准

Public Property Get LeftBarStartColor() As Long
 LeftBarStartColor = BarStartColor
End Property

Public Property Let LeftBarStartColor(ByVal nBarStartColor As Long)
 BarStartColor = nBarStartColor
End Property

' 属性: 菜单附加条过渡色终止颜色(
'只有当 LeftBarStyle 设置为 LBS_HORIZONTALCOLOR 或 LBS_VERTICALCOLOR 时才有效)
' 当 LeftBarStyle 设置为 LBS_SOLIDCOLOR (实色填充)时以 LeftBarStartColor 颜色为准

Public Property Get LeftBarEndColor() As Long
 LeftBarEndColor = BarEndColor
End Property

Public Property Let LeftBarEndColor(ByVal nBarEndColor As Long)
 BarEndColor = nBarEndColor
End Property

' 属性: 菜单项高亮条的范围

Public Property Get ItemSelectScope() As MenuItemSelectScope
 ItemSelectScope = SelectScope
End Property

Public Property Let ItemSelectScope(ByVal nSelectScope As MenuItemSelectScope)
 SelectScope = nSelectScope
End Property

' 属性: 菜单项可用时文字颜色

Public Property Get ItemTextEnabledColor() As Long
 ItemTextEnabledColor = TextEnabledColor
End Property

Public Property Let ItemTextEnabledColor(ByVal nTextEnabledColor As Long)
 TextEnabledColor = nTextEnabledColor
End Property

' 属性: 菜单项不可用时文字颜色

Public Property Get ItemTextDisabledColor() As Long
 ItemTextDisabledColor = TextDisabledColor
End Property

Public Property Let ItemTextDisabledColor(ByVal nTextDisabledColor As Long)
 TextDisabledColor = nTextDisabledColor
End Property

' 属性: 菜单项选中时文字颜色

Public Property Get ItemTextSelectColor() As Long
 ItemTextSelectColor = TextSelectColor
End Property

Public Property Let ItemTextSelectColor(ByVal nTextSelectColor As Long)
 TextSelectColor = nTextSelectColor
End Property

' 属性: 菜单项图标风格

Public Property Get ItemIconStyle() As MenuItemIconStyle
 ItemIconStyle = IconStyle
End Property

Public Property Let ItemIconStyle(ByVal nIconStyle As MenuItemIconStyle)
 IconStyle = nIconStyle
End Property

' 属性: 菜单项边框风格

Public Property Get ItemSelectEdgeStyle() As MenuItemSelectEdgeStyle
 ItemSelectEdgeStyle = EdgeStyle
End Property

Public Property Let ItemSelectEdgeStyle(ByVal nEdgeStyle As MenuItemSelectEdgeStyle)
 EdgeStyle = nEdgeStyle
End Property

' 属性: 菜单项边框颜色

Public Property Get ItemSelectEdgeColor() As Long
 ItemSelectEdgeColor = EdgeColor
End Property

Public Property Let ItemSelectEdgeColor(ByVal nEdgeColor As Long)
 EdgeColor = nEdgeColor
End Property

' 属性: 菜单项背景填充风格

Public Property Get ItemSelectFillStyle() As MenuItemSelectFillStyle
 ItemSelectFillStyle = FillStyle
End Property

Public Property Let ItemSelectFillStyle(ByVal nFillStyle As MenuItemSelectFillStyle)
 FillStyle = nFillStyle
End Property

' 属性: 菜单项过渡色起始颜色(
'只有当 ItemSelectFillStyle 设置为 ISFS_HORIZONTALCOLOR 或 ISFS_VERTICALCOLOR 时才有效)

' 当 ItemSelectFillStyle 设置为 ISFS_SOLIDCOLOR (实色填充)时以
'ItemSelectFillStartColor 颜色为准

Public Property Get ItemSelectFillStartColor() As Long
 ItemSelectFillStartColor = FillStartColor
End Property

Public Property Let ItemSelectFillStartColor(ByVal nFillStartColor As Long)
 FillStartColor = nFillStartColor
End Property

' 属性: 菜单项过渡色终止颜色(
'只有当 ItemSelectFillStyle 设置为 ISFS_HORIZONTALCOLOR 或 ISFS_VERTICALCOLOR 时才有效)

' 当 ItemSelectFillStyle 设置为 ISFS_SOLIDCOLOR (实色填充)时以
'ItemSelectFillStartColor 颜色为准

Public Property Get ItemSelectFillEndColor() As Long
 ItemSelectFillEndColor = FillEndColor
End Property

Public Property Let ItemSelectFillEndColor(ByVal nFillEndColor As Long)
 FillEndColor = nFillEndColor
End Property

' 属性: 菜单背景颜色

Public Property Get BackColor() As Long
 BackColor = BkColor
End Property

Public Property Let BackColor(ByVal nBkColor As Long)
 BkColor = nBkColor
End Property

' 属性: 菜单分隔条风格

Public Property Get SeparatorStyle() As MenuSeparatorStyle
 SeparatorStyle = SepStyle
End Property

Public Property Let SeparatorStyle(ByVal nSepStyle As MenuSeparatorStyle)
 SepStyle = nSepStyle
End Property

' 属性: 菜单分隔条颜色

Public Property Get SeparatorColor() As Long
 SeparatorColor = SepColor
End Property

Public Property Let SeparatorColor(ByVal nSepColor As Long)
 SepColor = nSepColor
End Property

' 属性: 菜单总体风格
Public Property Get Style() As MenuUserStyle
 Style = MenuStyle
End Property

Public Property Let Style(ByVal nMenuStyle As MenuUserStyle)
 MenuStyle = nMenuStyle
 Select Case nMenuStyle
  Case STYLE_WINDOWS ' Windows 默认风格
   Set BarImage = LoadPicture()
   BarWidth = 20
   BarStyle = LBS_NONE
   BarStartColor = GetSysColor(COLOR_MENU)
   BarEndColor = BarStartColor
   SelectScope = ISS_ICON_TEXT
   TextEnabledColor = GetSysColor(COLOR_MENUTEXT)
   TextDisabledColor = GetSysColor(COLOR_GRAYTEXT)
   TextSelectColor = GetSysColor(COLOR_HIGHLIGHTTEXT)
   IconStyle = IIS_NONE
   EdgeStyle = ISES_SOLID
   EdgeColor = GetSysColor(COLOR_HIGHLIGHT)
   FillStyle = ISFS_SOLIDCOLOR
   FillStartColor = EdgeColor
   FillEndColor = FillStartColor
   BkColor = GetSysColor(COLOR_MENU)
   SepColor = TextDisabledColor
   SepStyle = MSS_DEFAULT
  Case STYLE_XP ' XP 风格
   Set BarImage = LoadPicture()
   BarWidth = 20
   BarStyle = LBS_NONE
   BarStartColor = GetSysColor(COLOR_MENU)
   BarEndColor = BarStartColor
   SelectScope = ISS_ICON_TEXT
   TextEnabledColor = GetSysColor(COLOR_MENUTEXT)
   TextDisabledColor = GetSysColor(COLOR_GRAYTEXT)
   TextSelectColor = TextEnabledColor
   IconStyle = IIS_SHADOW
   EdgeStyle = ISES_SOLID
   EdgeColor = RGB(49, 106, 197)
   FillStyle = ISFS_SOLIDCOLOR
   FillStartColor = RGB(180, 195, 210)
   FillEndColor = FillStartColor
   BkColor = GetSysColor(COLOR_MENU)
   SepColor = RGB(192, 192, 192)
   SepStyle = MSS_SOLID
  Case STYLE_SHADE ' 渐变风格
   Set BarImage = LoadPicture()
   BarWidth = 20
   BarStyle = LBS_VERTICALCOLOR
   BarStartColor = vbBlack
   BarEndColor = vbWhite
   SelectScope = ISS_ICON_TEXT
   TextEnabledColor = GetSysColor(COLOR_MENUTEXT)
   TextDisabledColor = GetSysColor(COLOR_GRAYTEXT)
   TextSelectColor = GetSysColor(COLOR_HIGHLIGHTTEXT)
   IconStyle = IIS_NONE
   EdgeStyle = ISES_NONE
   EdgeColor = GetSysColor(COLOR_HIGHLIGHT)
   FillStyle = ISFS_HORIZONTALCOLOR
   FillStartColor = vbBlack
   FillEndColor = vbWhite
   BkColor = GetSysColor(COLOR_MENU)
   SepColor = TextDisabledColor
   SepStyle = MSS_DEFAULT
  Case STYLE_3D ' 3D 立体风格
   Set BarImage = LoadPicture()
   BarWidth = 20
   BarStyle = LBS_NONE
   BarStartColor = GetSysColor(COLOR_MENU)
   BarEndColor = BarStartColor
   SelectScope = ISS_TEXT
   TextEnabledColor = GetSysColor(COLOR_MENUTEXT)
   TextDisabledColor = GetSysColor(COLOR_GRAYTEXT)
   TextSelectColor = vbBlue
   IconStyle = IIS_RAISED
   EdgeStyle = ISES_SUNKEN
   EdgeColor = GetSysColor(COLOR_HIGHLIGHT)
   FillStyle = ISFS_NONE
   FillStartColor = EdgeColor
   FillEndColor = FillStartColor
   BkColor = GetSysColor(COLOR_MENU)
   SepColor = TextDisabledColor
   SepStyle = MSS_DEFAULT
  Case STYLE_COLORFUL ' 炫彩风格
   Set BarImage = frmMenu.Picture
   BarWidth = 20
   BarStyle = LBS_IMAGE
   BarStartColor = GetSysColor(COLOR_MENU)
   BarEndColor = BarStartColor
   SelectScope = ISS_ICON_TEXT
   TextEnabledColor = vbBlue
   TextDisabledColor = RGB(49, 106, 197)
   TextSelectColor = vbRed
   IconStyle = IIS_NONE
   EdgeStyle = ISES_DOT
   EdgeColor = vbBlack
   FillStyle = ISFS_VERTICALCOLOR
   FillStartColor = vbYellow
   FillEndColor = vbGreen
   BkColor = RGB(230, 230, 255)
   SepColor = vbMagenta
   SepStyle = MSS_DASHDOTDOT
  End Select
End Property

  这个类模块中包含了各种属性和方法及关于菜单的一些枚举类型,我想强调的有以下几点:

  1、在CreateMenu方法中用SetWindowLong重新定义了frmMenu的窗口入口函数的地址,MenuWndProc是标准模块中的一个函数,就是处理消息的那个函数。
 
  2、AddItem这个方法是添加菜单项的,使用一个叫做MyItemInfo的动态数组存储菜单项的内容,在“画”菜单项的时候要用到它。在AddItem方法的最后,将菜单项的fType设置成了MFT_OWNERDRAW,也就是物主绘图,这一步最关键,因为将菜单项设置成了Owner Draw,Windows将不会替我们写字,不会替我们画图标,一切都由我们自己来。

  3、在PopupMenu方法中,调用了API函数中的TrackPopupMenu,看到第6个参数了吗?将处理菜单消息的窗口设置成了frmMenu,而我们又对frmMenu进行了子类处理,一切都在我们的掌握之中。

  4、记得要在Class_Terminate中还原frmMenu的窗口入口函数的地址,并释放和菜单相关的资源。

  好了,类模块已经OK了,大家可能对这个菜单类有了更多的了解,也看到了它的属性和方法。怎么样?还算比较丰富吧。如果觉得不够丰富的话,自己加就好了,呵呵。不过,最核心的部分还不在这里,而是在那个处理消息的函数,也就是MenuWndProc,它将完成复杂地“画”菜单的任务以及处理各种菜单事件。

' 拦截菜单消息 (frmMenu 窗口入口函数)
Function MenuWndProc(ByVal hwnd As Long, ByVal Msg As Long,
  ByVal wParam As Long, ByVal lParam As Long) As Long
 Select Case Msg
  Case WM_COMMAND ' 单击菜单项
   If MyItemInfo(wParam).itemType = MIT_CHECKBOX Then
    If MyItemInfo(wParam).itemState = MIS_CHECKED Then
     MyItemInfo(wParam).itemState = MIS_UNCHECKED
    Else
     MyItemInfo(wParam).itemState = MIS_CHECKED
    End If
   End If
   MenuItemSelected wParam
  Case WM_EXITMENULOOP ' 退出菜单消息循环(保留)

  Case WM_MEASUREITEM ' 处理菜单项高度和宽度
   MeasureItem hwnd, lParam
  Case WM_MENUSELECT ' 选择菜单项
   Dim itemID As Long
   itemID = GetMenuItemID(lParam, wParam And &HFF)
   If itemID <> -1 Then
    MenuItemSelecting itemID
   End If
  Case WM_DRAWITEM ' 绘制菜单项
   DrawItem lParam
 End Select
 MenuWndProc = CallWindowProc(preMenuWndProc, hwnd, Msg, wParam, lParam)
End Function
' 处理菜单高度和宽度
Private Sub MeasureItem(ByVal hwnd As Long, ByVal lParam As Long)
 Dim TextSize As Size, hdc As Long
 hdc = GetDC(hwnd)
 CopyMemory MeasureInfo, ByVal lParam, Len(MeasureInfo)
 If MeasureInfo.CtlType And ODT_MENU Then
  MeasureInfo.itemWidth = lstrlen(MyItemInfo(MeasureInfo.itemID).itemText) *
_(GetSystemMetrics(SM_CYMENU) / 2.5) + BarWidth
  If MyItemInfo(MeasureInfo.itemID).itemType <> MIT_SEPARATOR Then
   MeasureInfo.itemHeight = GetSystemMetrics(SM_CYMENU)
  Else
   MeasureInfo.itemHeight = 6
  End If
 End If
 CopyMemory ByVal lParam, MeasureInfo, Len(MeasureInfo)
 ReleaseDC hwnd, hdc
End Sub

' 绘制菜单项
Private Sub DrawItem(ByVal lParam As Long)
 Dim hPen As Long, hBrush As Long
 Dim itemRect As RECT, barRect As RECT, iconRect As RECT, textRect As RECT
 Dim i As Long
 CopyMemory DrawInfo, ByVal lParam, Len(DrawInfo)
 If DrawInfo.CtlType = ODT_MENU Then
  SetBkMode DrawInfo.hdc, TRANSPARENT

  ' 初始化菜单项矩形, 图标矩形, 文字矩形
  itemRect = DrawInfo.rcItem
  iconRect = DrawInfo.rcItem
  textRect = DrawInfo.rcItem

  ' 设置菜单附加条矩形
  With barRect
   .Left = 0
   .Top = 0
   .Right = BarWidth - 1
   For i = 0 To GetMenuItemCount(hMenu) - 1
    If MyItemInfo(i).itemType = MIT_SEPARATOR Then
     .Bottom = .Bottom + 6
    Else
     .Bottom = .Bottom + MeasureInfo.itemHeight
    End If
   Next i
   .Bottom = .Bottom - 1
  End With

 ' 设置图标矩形, 文字矩形
 If BarStyle <> LBS_NONE Then iconRect.Left = barRect.Right + 2
  iconRect.Right = iconRect.Left + 20
  textRect.Left = iconRect.Right + 3

  With DrawInfo

  ' 画菜单背景
  itemRect.Left = barRect.Right
  hBrush = CreateSolidBrush(BkColor)
  FillRect .hdc, itemRect, hBrush
  DeleteObject hBrush


  ' 画菜单左边的附加条
  Dim RedArea As Long, GreenArea As Long, BlueArea As Long
  Dim red As Long, green As Long, blue As Long
  Select Case BarStyle
   Case LBS_NONE ' 无附加条
  
   Case LBS_SOLIDCOLOR ' 实色填充
    hBrush = CreateSolidBrush(BarStartColor)
    FillRect .hdc, barRect, hBrush
    DeleteObject hBrush
   Case LBS_HORIZONTALCOLOR ' 水平过渡色
    BlueArea = Int(BarEndColor / &H10000) - Int(BarStartColor / &H10000)
    GreenArea = (Int(BarEndColor / &H100) And &HFF) - (Int(BarStartColor / &H100) And &HFF)
    RedArea = (BarEndColor And &HFF) - (BarStartColor And &HFF)

    For i = 0 To BarWidth - 1
     red = Int(BarStartColor And &HFF) + Int(i / BarWidth * RedArea)
     green = (Int(BarStartColor / &H100) And &HFF) + Int(i / BarWidth * GreenArea)
     blue = Int(BarStartColor / &H10000) + Int(i / BarWidth * BlueArea)
     hPen = CreatePen(PS_SOLID, 1, RGB(red, green, blue))
     Call SelectObject(.hdc, hPen)
     Call MoveToEx(.hdc, i, 0, 0)
     Call LineTo(.hdc, i, barRect.Bottom)
     Call DeleteObject(hPen)
    Next i

   Case LBS_VERTICALCOLOR ' 垂直过渡色
    BlueArea = Int(BarEndColor / &H10000) - Int(BarStartColor / &H10000)
    GreenArea = (Int(BarEndColor / &H100) And &HFF) - (Int(BarStartColor / &H100) And &HFF)
    RedArea = (BarEndColor And &HFF) - (BarStartColor And &HFF)

    For i = 0 To barRect.Bottom
     red = Int(BarStartColor And &HFF) + Int(i / (barRect.Bottom + 1) *  RedArea)
     green = (Int(BarStartColor / &H100) And &HFF) + Int(i / (barRect.Bottom + 1) * GreenArea)
     blue = Int(BarStartColor / &H10000) + Int(i / (barRect.Bottom + 1) * BlueArea)
     hPen = CreatePen(PS_SOLID, 1, RGB(red, green, blue))
     Call SelectObject(.hdc, hPen)
     Call MoveToEx(.hdc, 0, i, 0)
     Call LineTo(.hdc, barRect.Right, i)
     Call DeleteObject(hPen)
    Next i

   Case LBS_IMAGE ' 图像

    If BarImage.Handle <> 0 Then
     Dim barhDC As Long
     barhDC = CreateCompatibleDC(GetDC(0))
     SelectObject barhDC, BarImage.Handle
     BitBlt .hdc, 0, 0, BarWidth, barRect.Bottom - barRect.Top + 1, barhDC, 0, 0, vbSrcCopy
     DeleteDC barhDC
    End If

   End Select

  ' 画菜单项
  If MyItemInfo(.itemID).itemType = MIT_SEPARATOR Then
   ' 画菜单分隔条(MIT_SEPARATOR)
   If MyItemInfo(.itemID).itemType = MIT_SEPARATOR Then
    itemRect.Top = itemRect.Top + 2
    itemRect.Bottom = itemRect.Top + 1
    itemRect.Left = barRect.Right + 5
    Select Case SepStyle
     Case MSS_NONE ' 无分隔条

     Case MSS_DEFAULT ' 默认样式
      DrawEdge .hdc, itemRect, EDGE_ETCHED, BF_TOP
     Case Else ' 其它
      hPen = CreatePen(SepStyle, 0, SepColor)
      hBrush = CreateSolidBrush(BkColor)
      SelectObject .hdc, hPen
      SelectObject .hdc, hBrush
      Rectangle .hdc, itemRect.Left, itemRect.Top, itemRect.Right, itemRect.Bottom
      DeleteObject hPen
      DeleteObject hBrush
    End Select
   End If
   Else
    If Not CBool(MyItemInfo(.itemID).itemState And MIS_DISABLED) Then
    ' 当菜单项可用时
     If .itemState And ODS_SELECTED Then ' 当鼠标移动到菜单项时
 
      ' 设置菜单项高亮范围
      If SelectScope And ISS_ICON_TEXT Then
       itemRect.Left = iconRect.Left
      ElseIf SelectScope And ISS_TEXT Then
       itemRect.Left = textRect.Left - 2
      Else
       itemRect.Left = .rcItem.Left
      End If

      ' 处理菜单项无图标或为CHECKBOX时的情况
      If (MyItemInfo(.itemID).itemType = MIT_CHECKBOX Or MyItemInfo(.itemID).itemIcon = 0) And SelectScope <> ISS_LEFTBAR_ICON_TEXT Then
       itemRect.Left = iconRect.Left
      End If

     ' 画菜单项边框
    Select Case EdgeStyle
     Case ISES_NONE ' 无边框

     Case ISES_SUNKEN ' 凹进
      DrawEdge .hdc, itemRect, BDR_SUNKENOUTER, BF_RECT
     Case ISES_RAISED ' 凸起
      DrawEdge .hdc, itemRect, BDR_RAISEDINNER, BF_RECT
     Case Else ' 其它
      hPen = CreatePen(EdgeStyle, 0, EdgeColor)
      hBrush = CreateSolidBrush(BkColor)
      SelectObject .hdc, hPen
      SelectObject .hdc, hBrush
      Rectangle .hdc, itemRect.Left, itemRect.Top, itemRect.Right, itemRect.Bottom
      DeleteObject hPen
      DeleteObject hBrush
    End Select

   ' 画菜单项背景
   InflateRect itemRect, -1, -1
   Select Case FillStyle
    Case ISFS_NONE ' 无背景
 
    Case ISFS_HORIZONTALCOLOR ' 水平渐变色
     BlueArea = Int(FillEndColor / &H10000) - Int(FillStartColor / &H10000)
     GreenArea = (Int(FillEndColor / &H100) And &HFF) - (Int(FillStartColor / &H100) And &HFF)
     RedArea = (FillEndColor And &HFF) - (FillStartColor And &HFF)

     For i = itemRect.Left To itemRect.Right - 1
      red = Int(FillStartColor And &HFF) + Int((i - itemRect.Left) / (itemRect.Right - itemRect.Left + 1) * RedArea)
      green = (Int(FillStartColor / &H100) And &HFF) + Int((i - itemRect.Left) / (itemRect.Right - itemRect.Left + 1) * GreenArea)
      blue = Int(FillStartColor / &H10000) + Int((i - itemRect.Left) / (itemRect.Right - itemRect.Left + 1) * BlueArea)
      hPen = CreatePen(PS_SOLID, 1, RGB(red, green, blue))
Call SelectObject(.hdc, hPen)
      Call MoveToEx(.hdc, i, itemRect.Top, 0)
      Call LineTo(.hdc, i, itemRect.Bottom)
      Call DeleteObject(hPen)
     Next i

    Case ISFS_VERTICALCOLOR ' 垂直渐变色
     BlueArea = Int(FillEndColor / &H10000) - Int(FillStartColor / &H10000)
     GreenArea = (Int(FillEndColor / &H100) And &HFF) - (Int(FillStartColor / &H100) And &HFF)
     RedArea = (FillEndColor And &HFF) - (FillStartColor And &HFF)

     For i = itemRect.Top To itemRect.Bottom - 1
      red = Int(FillStartColor And &HFF) + Int((i - itemRect.Top) / (itemRect.Bottom - itemRect.Top + 1) * RedArea)
      green = (Int(FillStartColor / &H100) And &HFF) + Int((i - itemRect.Top) / (itemRect.Bottom - itemRect.Top + 1) * GreenArea)
      blue = Int(FillStartColor / &H10000) + Int((i - itemRect.Top) / (itemRect.Bottom - itemRect.Top + 1) * BlueArea)
      hPen = CreatePen(PS_SOLID, 1, RGB(red, green, blue))
      Call SelectObject(.hdc, hPen)
      Call MoveToEx(.hdc, itemRect.Left, i, 0)
      Call LineTo(.hdc, itemRect.Right, i)
      Call DeleteObject(hPen)
     Next i

    Case ISFS_SOLIDCOLOR ' 实色填充
     hPen = CreatePen(PS_SOLID, 0, FillStartColor)
     hBrush = CreateSolidBrush(FillStartColor)
     SelectObject .hdc, hPen
     SelectObject .hdc, hBrush
     Rectangle .hdc, itemRect.Left, itemRect.Top, itemRect.Right, itemRect.Bottom
     DeleteObject hPen
     DeleteObject hBrush
   End Select

   ' 画菜单项文字
   SetTextColor .hdc, TextSelectColor
   DrawText .hdc, MyItemInfo(.itemID).itemText, -1, textRect, DT_SINGLELINE Or DT_LEFT Or DT_VCENTER

   ' 画菜单项图标
   If MyItemInfo(.itemID).itemType <> MIT_CHECKBOX Then
    DrawIconEx .hdc, iconRect.Left + 2, iconRect.Top + (iconRect.Bottom - iconRect.Top + 1 - 16) / 2, MyItemInfo(.itemID).itemIcon, 16, 16, 0, 0, DI_NORMAL
    Select Case IconStyle
     Case IIS_NONE ' 无效果

     Case IIS_SUNKEN ' 凹进
      If MyItemInfo(.itemID).itemIcon <> 0 Then
       DrawEdge .hdc, iconRect, BDR_SUNKENOUTER, BF_RECT
      End If
     Case IIS_RAISED ' 凸起
      If MyItemInfo(.itemID).itemIcon <> 0 Then
       DrawEdge .hdc, iconRect, BDR_RAISEDINNER, BF_RECT
      End If
     Case IIS_SHADOW ' 阴影
      hBrush = CreateSolidBrush(RGB(128, 128, 128))
      DrawState .hdc, hBrush, 0, MyItemInfo(.itemID).itemIcon, 0, iconRect.Left + 3, iconRect.Top + (iconRect.Bottom - iconRect.Top + 1 - 16) / 2 + 1, 0, 0, DST_ICON Or DSS_MONO
      DeleteObject hBrush
      DrawIconEx .hdc, iconRect.Left + 1, iconRect.Top + (iconRect.Bottom - iconRect.Top + 1 - 16) / 2 - 1, MyItemInfo(.itemID).itemIcon, 16, 16, 0, 0, DI_NORMAL
    End Select
   Else
    ' CHECKBOX型菜单项图标效果
    If MyItemInfo(.itemID).itemState And MIS_CHECKED Then
     DrawIconEx .hdc, iconRect.Left + 2, iconRect.Top + (iconRect.Bottom - iconRect.Top + 1 - 16) / 2, MyItemInfo(.itemID).itemIcon, 16, 16, 0, 0, DI_NORMAL
    End If
   End If

  Else ' 当鼠标移开菜单项时

   ' 画菜单项边框和背景(清除)
   If BarStyle <> LBS_NONE Then
    itemRect.Left = barRect.Right + 1
   Else
    itemRect.Left = 0
   End If
   hBrush = CreateSolidBrush(BkColor)
   FillRect .hdc, itemRect, hBrush
   DeleteObject hBrush

   ' 画菜单项文字
   SetTextColor .hdc, TextEnabledColor
   DrawText .hdc, MyItemInfo(.itemID).itemText, -1, textRect, DT_SINGLELINE Or DT_LEFT Or DT_VCENTER

   ' 画菜单项图标
   If MyItemInfo(.itemID).itemType <> MIT_CHECKBOX Then
    DrawIconEx .hdc, iconRect.Left + 2, iconRect.Top + (iconRect.Bottom - iconRect.Top + 1 - 16) / 2, MyItemInfo(.itemID).itemIcon, 16, 16, 0, 0, DI_NORMAL
   Else
    If MyItemInfo(.itemID).itemState And MIS_CHECKED Then
     DrawIconEx .hdc, iconRect.Left + 2, iconRect.Top + (iconRect.Bottom - iconRect.Top + 1 - 16) / 2, MyItemInfo(.itemID).itemIcon, 16, 16, 0, 0, DI_NORMAL
   End If
  End If

 End If
 Else ' 当菜单项不可用时

  ' 画菜单项文字
  SetTextColor .hdc, TextDisabledColor
  DrawText .hdc, MyItemInfo(.itemID).itemText, -1, textRect, DT_SINGLELINE Or DT_LEFT Or DT_VCENTER

  ' 画菜单项图标
  If MyItemInfo(.itemID).itemType <> MIT_CHECKBOX Then
   DrawState .hdc, 0, 0, MyItemInfo(.itemID).itemIcon, 0, iconRect.Left + 2, iconRect.Top + (iconRect.Bottom - iconRect.Top + 1 - 16) / 2, 0, 0, DST_ICON Or DSS_DISABLED
  Else
   If MyItemInfo(.itemID).itemState And MIS_CHECKED Then
    DrawState .hdc, 0, 0, MyItemInfo(.itemID).itemIcon, 0, iconRect.Left + 2, iconRect.Top + (iconRect.Bottom - iconRect.Top + 1 - 16) / 2, 0, 0, DST_ICON Or DSS_DISABLED
  End If
 End If

 End If
End If

End With
End If
End Sub

' 菜单项事件响应(单击菜单项)
Private Sub MenuItemSelected(ByVal itemID As Long)
Debug.Print "鼠标单击了:" & MyItemInfo(itemID).itemText
Select Case MyItemInfo(itemID).itemAlias
Case "exit"
Dim frm As Form
For Each frm In Forms
Unload frm
Next
End Select
End Sub

' 菜单项事件响应(选择菜单项)
Private Sub MenuItemSelecting(ByVal itemID As Long)
Debug.Print "鼠标移动到:" & MyItemInfo(itemID).itemText
End Sub

  到此为止,我们就完成了菜单类的编写,且还包括一个测试窗体。现在,完整的工程里应该包括两个窗体:frmMain和frmMenu;一个标准模块:mMenu;一个类模块:cMenu。按F5编译运行一下,在窗体空白处单击鼠标右键。怎么样,出现弹出式菜单了吗?换个风格再试试。

  看完这篇文章后,我想你应该已经对采用物主绘图技术的自绘菜单有了一定的了解,再看看MS Office 2003的菜单,其实也没什么难的嘛。

  本文程序在Windows XP、VB6下调试通过。