65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.28/5 (21投票s)

2005 年 9 月 2 日

CPOL

5分钟阅读

viewsIcon

237787

downloadIcon

5695

为您的应用程序提供透明菜单,并为窗体的标题栏添加 XP 风格的系统按钮。

Sample Image - TransparentMenu-XPSystemTitleButtons.gif

引言

本文将介绍 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 中获取菜单弹出窗口的窗口句柄。然后我可以使用 SetWindowLongSetLayeredWindowAttributes 来设置菜单窗口使其看起来透明。这确实有效,我使用了原始文章代码中的 '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 日 - 初始版本。
© . All rights reserved.