为您的应用程序添加透明菜单和 XP 标题栏按钮






4.28/5 (21投票s)
为您的应用程序提供透明菜单,并为窗体的标题栏添加 XP 风格的系统按钮。
- 下载源代码和演示项目 - 41.6 Kb (更新于 05-09-04)
- 下载源代码和演示项目 - 95.3 Kb (更新于 06-02-05)
请阅读 Readme.txt 以了解更改信息。
引言
本文将介绍 VB.NET 中一些最少文档记录的原理:透明菜单和系统标题按钮。本文将讨论如何包装 UxTheme.dll 和窗口事件来创建窗口标题按钮,以及如何挂钩系统菜单使其看起来透明。请注意,此代码仅适用于 Windows XP,因为这是我使用的操作系统,不能保证此代码能在其他 Windows 操作系统上正常运行。
背景
透明菜单背景:大约一年前,当我谷歌搜索“透明菜单”时,C++ 透明菜单文章是网上最早出现的地方之一。我尝试了好几个月想将其转换为 VB.NET,但最终没有成功。直到我遇到了 Flat Visual Studio Style Menus 文章,它给这个主题带来了新的希望,我将在本文稍后解释原因。
窗口标题栏按钮背景:这也是一个很难找到文档的主题,但 Code Project 再次提供了答案,即 MinTrayButton 文章。这篇文章是用 C# 编写的,因此相对容易转换为 VB.NET。但是,它的一个缺点是不支持 XP 主题。所以我必须修改代码,使其使用 Uxtheme.dll 来绘制所有标题按钮。
透明菜单代码
正如我所说,这段代码基于文章 Flat Visual Studio Style Menus。在文章的代码中,它有一个挂钩,用于挂钩窗体的所有系统菜单,并通过挂钩菜单窗口并绘制新边框来使它们看起来是平坦的。 well 我只取了平坦菜单代码中的挂钩部分,并添加了挂钩 WM_NCPAINT
事件的代码。
Select case m.msg
...
Case WM_NCPAINT
Dim dwstyle As Integer = GetWindowLong(hwnd, GWL_EXSTYLE)
SetWindowLong(hwnd, GWL_EXSTYLE, WS_EX_LAYERED Or dwstyle)
SetWindowPos(hwnd, -1, 0, 0, 0, 0, SWP_FRAMECHANGED Or SWP_NOACTIVATE _
Or SWP_SHOWWINDOW Or SWP_NOMOVE Or SWP_NOSIZE)
SetLayeredWindowAttributes(hwnd.ToInt32, 0, 150, LWA_ALPHA)
DrawBorder(hwnd, IntPtr.Zero)
...
End Select
通过这段代码,可以从 WndProc
函数的 wparameter 中获取菜单弹出窗口的窗口句柄。然后我可以使用 SetWindowLong
和 SetLayeredWindowAttributes
来设置菜单窗口使其看起来透明。这确实有效,我使用了原始文章代码中的 'DrawBorder
' 函数来绘制一个围绕代码的平坦边框。我确实需要修改这个函数,以便边框能够正确地绘制一半。当前 DrawBorder
函数的一个缺点是,当鼠标悬停在菜单项上时,菜单项的左边框会消失。
使用透明菜单代码
要在您的项目中使用的代码,只需将组件复制到您的项目中,然后为 TransMenuLib
组件声明一个变量。然后,在您的 'Sub Main
' 例程中包含挂钩和取消挂钩函数。例如:
'you hook your program at the beginning
TransMenuLib.Hook(Process.GetCurrentProcess)
Application.EnableVisualStyles()
Application.DoEvents()
Application.Run(New Form1)
'and unhook your program at the end
TransMenuLib.Unhook()
WinTrayButton 代码
要理解代码的基础,我建议您查看此代码所基于的文章:MinTrayButton。但我会解释我添加的部分。
正如大多数 Windows 程序员所知,Windows XP 使用 UxTheme.dll 的主题引擎来绘制操作系统中的所有 UI 元素。这还包括标题按钮,它有总共四个状态由 UxTheme 绘制。热、正常、按下和禁用这四种状态用于向用户显示按钮是否易于访问。我改变了原始 C# 代码的绘制方式,使其使用了所有这些状态。但是 UxTheme 没有一个 Windows 标题按钮是没有图标的,所以我决定使用带有最小图标的按钮,即最小化按钮。这样我就可以在最小化按钮上放一个图标。在 UxTheme.dll 中,MinButton 的部分号是 15,它的状态是数字 1-4。然而,这引出了另一个常见问题的解决方案:“如果我想在标题栏中有一个帮助按钮,但默认的按钮(最小化/最大化/关闭)会覆盖 form.helpbutton
属性怎么办?”我考虑到了这一点,并添加了代码,以便可以从 UxTheme 绘制最小化按钮或帮助按钮。UxTheme 中帮助按钮的部分号是 23,它的状态与任何标题按钮的状态相同(状态 1-4)。
Private Sub DrawButton(ByVal g As Graphics, ByVal state As state)
Select Case parent.FormBorderStyle
Case FormBorderStyle.SizableToolWindow, FormBorderStyle.FixedToolWindow
'small caption button
If bhelpbutton = True Then
Select Case state
Case state.Normal
drawThemeBackground(openThemeData(Me.Handle, "Window"), _
g.GetHdc, 23, 1, New RECT(New Rectangle(pos.X, _
pos.Y, btn_width / 1.5, btn_height / 1.5)), IntPtr.Zero)
Case state.Hot
drawThemeBackground(openThemeData(Me.Handle, "Window"), _
g.GetHdc, 23, 2, New RECT(New Rectangle(pos.X, pos.Y, _
btn_width / 1.5, btn_height / 1.5)), IntPtr.Zero)
Case state.Pushed
drawThemeBackground(openThemeData(Me.Handle, "Window"), _
g.GetHdc, 23, 3, New RECT(New Rectangle(pos.X, pos.Y, _
btn_width / 1.5, btn_height / 1.5)), IntPtr.Zero)
Case state.Disabled
drawThemeBackground(openThemeData(Me.Handle, "Window"), _
g.GetHdc, 23, 4, New RECT(New Rectangle(pos.X, _
pos.Y, btn_width / 1.5, btn_height / 1.5)), IntPtr.Zero)
End Select
ElseIf bimageonly = False Then
Select Case state
Case state.Normal
drawThemeBackground(openThemeData(Me.Handle, "Window"), _
g.GetHdc, 15, 1, New RECT(New Rectangle(pos.X, pos.Y, _
btn_width / 1.5, btn_height / 1.5)), IntPtr.Zero)
Case state.Hot
drawThemeBackground(openThemeData(Me.Handle, "Window"), _
g.GetHdc, 15, 2, New RECT(New Rectangle(pos.X, pos.Y, _
btn_width / 1.5, btn_height / 1.5)), IntPtr.Zero)
Case state.Pushed
drawThemeBackground(openThemeData(Me.Handle, "Window"), _
g.GetHdc, 15, 3, New RECT(New Rectangle(pos.X, pos.Y, _
btn_width / 1.5, btn_height / 1.5)), IntPtr.Zero)
Case state.Disabled
drawThemeBackground(openThemeData(Me.Handle, "Window"), _
g.GetHdc, 15, 4, New RECT(New Rectangle(pos.X, pos.Y, _
btn_width / 1.5, btn_height / 1.5)), IntPtr.Zero)
End Select
End If
g = Graphics.FromHdc(New IntPtr(GetWindowDC(parent.Handle.ToInt32)))
'm.HWnd));
If bhelpbutton = False Then
If Not bimagelist Is Nothing Then
If bimagelist.Images.Count = 1 Then
If state <> state.Disabled Then
g.DrawImage(bimagelist.Images(0), pos.X, pos.Y)
Else
ControlPaint.DrawImageDisabled(g, _
bimagelist.Images(0), pos.X, pos.Y, _
Color.Transparent)
End If
ElseIf bimagelist.Images.Count = 4 Then
Select Case state
Case state.Disabled
g.DrawImage(bimagelist.Images(3), pos.X, pos.Y)
Case state.Hot
g.DrawImage(bimagelist.Images(1), pos.X, pos.Y)
Case state.Normal
g.DrawImage(bimagelist.Images(0), pos.X, pos.Y)
Case state.Pushed
g.DrawImage(bimagelist.Images(2), pos.X, pos.Y)
End Select
ElseIf bimagelist.Images.Count > 0 Then
g.DrawImage(bimagelist.Images(0), pos.X, pos.Y)
End If
End If
End If
g.Dispose()
...
End Select
End Sub 'DrawButton
现在我大量修改了原始代码的另一个区域是 WndProc
子例程。我不得不修改代码以包含 WinTrayButton
点击和状态更改。这也是我添加代码以仅在必要时绘制 WinTrayButton
的区域。
<System.Security.Permissions.PermissionSet(_
System.Security.Permissions.SecurityAction.Demand, Name:="FullTrust")> _
Protected Overrides Sub WndProc(ByRef m As Message)
......
' Drawing the Button and getting the Real Size of the Window
If m.Msg = WM_ACTIVATE OrElse m.Msg = WM_SIZE OrElse m.Msg = _
WM_SYNCPAINT OrElse m.Msg = WM_NCCREATE OrElse _
m.Msg = WM_NCPAINT OrElse m.Msg = WM_NCACTIVATE _
OrElse m.Msg = WM_NCHITTEST OrElse m.Msg = WM_PAINT Then
If m.Msg = WM_SIZE Then
wnd_size = New Size(New Point(m.LParam.ToInt32))
End If
Dim pnt As New Point(m.LParam.ToInt32)
If drawn = False Then
RaiseEvent MinTrayBtnStateChanged(Me, sstate)
End If
If parent.Enabled = False Or bEnabled = False Then
SetState(state.Disabled)
Else
If MouseinBtn(pnt) = False Then
If Not parent.ActiveForm Is parent Then
Checkstate(state.Disabled)
Else
Checkstate(state.Normal)
End If
Else
Checkstate(state.Hot)
End If
End If
End If
MyBase.WndProc(m)
End Sub 'WndProc
您在 WndProc
函数中看到的 Checkstate
函数用于检查标题按钮的状态是否不是参数中使用的状态。Checkstate
函数通过检查按钮的状态是否不是 newstate
来实现这一点。
Public Function Checkstate(ByVal newstate As state)
'if the state is new then change state to the new state and return true
If sstate <> newstate Then
sstate = newstate
RaiseEvent MinTrayBtnStateChanged(Me, sstate)
End If
End Function
我将讨论的 WinTrayButton
的最后一部分是它的帮助函数。您可以设置按钮,以便显示帮助按钮。当显示帮助按钮时,按钮上将没有图标,并且在单击帮助按钮时会发送不同的事件。当单击帮助按钮时,它的行为与任何帮助按钮一样。我添加此功能是因为默认的窗体控件仅在窗口标题栏中只有一个关闭按钮时才允许使用帮助按钮。
使用 WinTrayButton 代码
这段代码使用起来需要更多时间,因为 WinTrayButton
不是组件的固有部分,而是原生窗口的一部分。这意味着您不能在 IDE 设计模式下直接编辑该对象,而必须手动编写所有代码。一旦您知道如何做,这会相当容易。基本上,将对象声明为 WithEvents
,然后在 Form.Load
事件中编辑按钮的属性。
Friend WithEvents wintraybtn As WinTrayButton = New WinTrayButton(Me)
Private Sub Form1_Load(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
wintraybtn.ButtonImageList = b4imagelist
wintraybtn.ButtonMenuText = "Tray Button Menu"
End Sub
历史
- 2005 年 9 月 2 日 - 初始版本。