Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Add Transparent Menus and XP Titlebar Buttons to your application

0.00/5 (No votes)
7 Feb 2006 1  
Give your applications transparent menus and add XP style system buttons to a form's titlebar.

Sample Image - TransparentMenu-XPSystemTitleButtons.gif

Introduction

This article will go over some of the least documented (in VB.NET) principles, Transparent Menus and System Title Buttons. This article will discuss how to wrap UxTheme.dll and window events to make a window caption button, and how to hook system menus to make them appear transparent. Please keep in mind that this code is meant for Windows XP, as that is the operating system that I am using, their is no guarantee that this code will work on other Windows operating systems.

Background

Transparent Menu Background: C++ Transparent Menu article was one of the first places on the web that came up when I Googled 'transparent menus' about a year ago. I tried for months to try and convert it to VB.NET but alas with no success. That is until I ran across Flat Visual Studio Style Menus article which shined new hope on the subject, I will explain why later in this article.

Window Titlebar Button Background: This has also been a hard topic to find documentation on, but alas The Code Project had the answer again with MinTrayButton article. This article was written in C# and so was fairly easy to convert to VB.NET. However a drawback was that it did not support XP theming. So I had to modify the code so that it used the Uxtheme.dll to do all the drawing of the caption buttons.

Transparent menu code

As I said this code is based on the article Flat Visual Studio Style Menus. In the code for the article, it had a hook which hooked all system menus of a form and made them appear flat by hooking the menu window and painting a new border on the menu window. Well I took just the hook portion of the flat menu code and added code where it hooked the WM_NCPAINT event.

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

With this code it gets the window handle of the menu popup from the wparameter of the WndProc function. I could then use SetWindowLong and SetLayeredWindowAttributes to set the menu window to appear transparent. This did work and I used the 'DrawBorder' function from the original article code to draw a flat border around the code. I did have to modify the function so that the border drew half-ways properly. A drawback of the current DrawBorder function is that when the mouse goes over a menu item the left border of the menu item will disappear.

Using Transparent menu code

To use the code in your project just copy the component to your project and declare a variable for the TransMenuLib component. Then, in your 'Sub Main' routine include the hooking and unhooking functions. Like:

'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 code

To understand the basics of the code I would suggest that you look at the article that this code is based on: MinTrayButton. I will however explain the parts that I have added.

As most Windows programmers know, Windows XP is using the theming engine of UxTheme.dll to draw everything with a UI in the operating system. This also includes caption buttons, which have a total of four states that are painted by UxTheme. The states of Hot, Normal, Pressed, and Disabled are used to show the user if the button is easily accessible or not. I have changed the drawing of the original C# code so that all these states are used. However UxTheme did not have a Windows caption button that did not already have an icon on it, so I decided to use the button that had the smallest icon, the minimize button. This way I could then put an icon over the minimize icon in the minimize button. In the UxTheme.dll the MinButton's part number is 15 and its states are numbers 1-4. However this leads to another solution to a common problem 'What if I want to have a help button in the titlebar when the default buttons (min/max/close) will cover up the form.helpbutton property?'. I have took this into consideration and I have added code so that either the minimize button or the help button can be drawn from UxTheme. The part number in UxTheme for the help button is 23 and its states are the same as those of any of the caption buttons (states 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

Now the other area where I changed the original code a lot is the WndProc subroutine. I have had to change the code to include WinTrayButton clicks, and state changes. This is also the area where I added code to draw the WinTrayButton only when it was necessary.

<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

The Checkstate function that you see used in this WndProc function checks to make sure that the caption button state is not already the state used in the parameter. The Checkstate function does this by checking to make sure the state of the button is not already the 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

The last part that I will discuss about the WinTrayButton is its help function. You are allowed to set the button so that you can have a help button displayed. When the help button is displayed there will be no icon appearing over the button and a different event is sent when the help button is clicked. When the help button is clicked it will act the same as any help button. I added this feature because the default form control only allows the help button when the only button in the window caption bar is a close button.

Using WinTrayButton code

This code is a little more time consuming to use, this is because the WinTrayButton is not an inherent of a component but of a native window. This means that you cannot edit the object directly using the IDE Design Mode, but you have to manually type in the code for everything. It is fairly easy to do once you know how to do it. Basically declare the object as WithEvents and edit the button's properties in the Form.Load event.

    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

History

  • September 2, 2005 - Initial version.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here