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:
TransMenuLib.Hook(Process.GetCurrentProcess)
Application.EnableVisualStyles()
Application.DoEvents()
Application.Run(New Form1)
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
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)))
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
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)
......
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
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 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.