gTitleBar - Custom TitleBar for Borderless Forms (VB.NET)






4.81/5 (30 votes)
Simulates standard titlebar on a borderless form for easy placement of additional controls on the titlebar, freeing up space on the form. It can also be put on a panel to simulate an MDI Form.

Introduction
I often encounter the situation where I have a Form
with only one large control like a Grid
or Report
on it. I want to use all the form space I can, but if I just want to add one other control like a save button, then you end up with a large amount of wasted empty space. If you could just put the extra control up on the TitleBar
then all the form space can be used for the large control.
There are ways of forcing controls up onto the non-client area, but it was difficult and confusing. Then I got the idea of making a control that looks like a TitleBar
and put it on a borderless form. Then additional controls could be easily added. In trying to avoid the difficulty of the non-client area, I backed right into the crazy world of Themes.
Control Properties
Here is a list of the primary properties:
AllowMove
Get or Set if the
gTitleBar
allows moving with the mouse.ControlBoxAffects
Get or Set if clicking the Control Box Buttons affects the Form or triggers the custom Events.
FrameHeightAdj
Get or Set a padding value for the bottom of the frame.
FrameShow
Get or Set if the frame is visible around the parent.
FrameWidth
Get or Set the width of the frame.
ShowCloseBox
,ShowMinimizeBox
,ShowMaximizeBox
Get or Set if the Close, Minimize, Maximize button is visible.
TitleImage
Get or Set the image to display in the
TitleBar
.TitleImageSize
Get or Set the size to make the image on the
TitleBar
.TitleText
Text to display on the Tile Bar.
Using the gTitleBar
Set the Form
's FormBorderStyle
to None
and the TransparencyKey
to some color like Magenta
. Drop a gTitleBar
on your Form
, dock it to the top and set the BackColor
to the same Color
as the Form
's TransparencyKey
Color
.
Points of Interest
Theming was quite an undertaking. It was hard to find a straight forward explanation, so I had to piece it together from many sources. First there is a huge amount of enumerations to sort through, plus a RECT
structure and the Theme
functions to boot.
The Theme
functions include:
openThemeData
CloseThemeData
drawThemeBackground
isAppThemed
As the gTitleBar
paints itself, it will force the form to paint the border on itself at the same time. isAppThemed
is used to determine if the themes are currently active. I will explain how to handle it if they are not later. If they are, then we need to get the graphics object of the Parent
and get a pointer for it.
Dim g As Graphics = Parent.CreateGraphics
Dim hdcGraphics As IntPtr = g.GetHdc
Next open the Theme
data and get a pointer for that.
intptrWindowTheme = openThemeData(Parent.Handle, "Window")
Using the drawThemeBackground
function, each side of the frame can be drawn. For example, here is how to draw the left side of the frame/border.
'Draw Left Side of the Frame
drawThemeBackground(intptrWindowTheme, hdcGraphics, _
UxThemeWindowParts.WP_FRAMELEFT, _
_IsFormActive, _
New RECT(New Rectangle(0, 0, _
_FrameWidth, Parent.Height - _FrameHeightAdj)), _
IntPtr.Zero)
The parameters for drawThemeBackground
are:
HTHEME
- Handle to a window's specified theme data. Use
OpenThemeData
to create anHTHEME
.HDC
HDC
used for drawing the theme-defined background image.iPartId
- Value of type
int
that specifies the part to draw. See Parts and States.iStateId
- Value of type
int
that specifies the state of the part to draw. See Parts and States.pRect
- Pointer to a
RECT
structure that contains the rectangle, in logical coordinates, in which the background image is drawn.pClipRect
- Pointer to a
RECT
structure that contains a clipping rectangle. This parameter may be set toNULL
.
Continue this for the other sides and then the graphics pointer and the Theme
pointer need to be released.
g.ReleaseHdc(hdcGraphics)
CloseThemeData(intptrWindowTheme)
Drawing the TitleBar
uses the same drawThemeBackground
function, but with a iPartId
of UxThemeWindowParts.WP_CAPTION
.
If Theming is not available, then the border
and titlebar
can be painted in the classic style using the ControlPaint
method.
'Draw Left Side of the Frame
ControlPaint.DrawBorder3D(Parent.CreateGraphics, _
New Rectangle(0, 0, _FrameWidth, _
Parent.Height - _FrameHeightAdj), _
Border3DStyle.Raised, _
Border3DSide.All)
The last part is the Caption
and Image
which use basic GDI+ Draw
functions to paint to the graphics object.
Control Box Buttons
The Close Minimize and Maximize buttons are just normal buttons. The Theming functions are used in the buttons Paint
event. The button state is determined and set in the mouse events. Just like the frame, the drawThemeBackground
function will use the iPartId
and iStateId
to paint the type of button and in what state.
Private Sub CloseWindowButton_Paint(ByVal sender As Object, _
ByVal e As System.Windows.Forms.PaintEventArgs) _
Handles CloseWindowButton.Paint
IsButtonActive(CloseButtonState)
If isAppThemed() Then
intptrWindowTheme = openThemeData( _
CloseWindowButton.Handle, "Window")
drawThemeBackground(intptrWindowTheme, e.Graphics.GetHdc, _
UxThemeWindowParts.WP_CLOSEBUTTON, _
CloseButtonState, _
New RECT(New Rectangle(0, 0, _
CloseWindowButton.Width, CloseWindowButton.Height)), _
IntPtr.Zero)
e.Graphics.ReleaseHdc()
CloseThemeData(intptrWindowTheme)
Else
ControlPaint.DrawCaptionButton(e.Graphics, _
CloseWindowButton.DisplayRectangle, _
CaptionButton.Close, _
ConvertThemeToClassic(CloseButtonState))
End If
End Sub
Resizing a Borderless Form
Because there isn't a real border to resize the form with, put this "Borderless Form Helper" in the Form's code. This will send the message where the border is and resize accordingly.
#Region "Borderless Form Helper"
Private Const HTLEFT As Integer = 10
Private Const HTRIGHT As Integer = 11
Private Const HTTOP As Integer = 12
Private Const HTTOPLEFT As Integer = 13
Private Const HTTOPRIGHT As Integer = 14
Private Const HTBOTTOM As Integer = 15
Private Const HTBOTTOMLEFT As Integer = 16
Private Const HTBOTTOMRIGHT As Integer = 17
Private Const WM_NCHITTEST As Integer = &H84
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
If m.Msg = WM_NCHITTEST Then
Dim pt As New Point(m.LParam.ToInt32)
pt = PointToClient(pt)
If pt.X < GTitleBar1.FrameWidth AndAlso _
pt.Y < GTitleBar1.FrameWidth Then
m.Result = New IntPtr(HTTOPLEFT)
ElseIf pt.X > (Width - GTitleBar1.FrameWidth) AndAlso _
pt.Y < GTitleBar1.FrameWidth Then
m.Result = New IntPtr(HTTOPRIGHT)
ElseIf pt.Y < GTitleBar1.FrameWidth Then
m.Result = New IntPtr(HTTOP)
ElseIf pt.X < GTitleBar1.FrameWidth AndAlso _
pt.Y > (Height - GTitleBar1.FrameWidth - _
GTitleBar1.FrameHeightAdj) Then
m.Result = New IntPtr(HTBOTTOMLEFT)
ElseIf pt.X > (Width - GTitleBar1.FrameWidth) AndAlso _
pt.Y > (Height - GTitleBar1.FrameWidth - _
GTitleBar1.FrameHeightAdj) Then
m.Result = New IntPtr(HTBOTTOMRIGHT)
ElseIf pt.Y > (Height - GTitleBar1.FrameWidth - _
GTitleBar1.FrameHeightAdj) Then
m.Result = New IntPtr(HTBOTTOM)
ElseIf pt.X < GTitleBar1.FrameWidth Then
m.Result = New IntPtr(HTLEFT)
ElseIf pt.X > (Width - GTitleBar1.FrameWidth) Then
m.Result = New IntPtr(HTRIGHT)
Else
MyBase.WndProc(m)
End If
Else
MyBase.WndProc(m)
End If
End Sub
Private Sub Form1_Activated(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Me.Activated
GTitleBar1.IsFormActive = True
End Sub
Private Sub Form1_Deactivate(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Me.Deactivate
GTitleBar1.IsFormActive = False
End Sub
#End Region
This works great if the form is getting the message that the mouse is on the edge, but the gTitlebar
covers the top edge so the form doesn't get the message. To fix this, I needed to monitor the mouse over the gTitleBar
and send the message through to the Form
when it is over the edge. in the "Resize-Move form" code, the direction of the resize is set, the cursor is updated, and the message is sent to the form.
Private Sub MoveForm()
ReleaseCapture()
SendMessage(Parent.Handle, WM_NCLBUTTONDOWN, HTCAPTION, 0)
Invalidate()
End Sub
Private Sub ResizeForm(ByVal direction As ResizeDirection)
Dim dir As Integer = -1
Select Case direction
Case ResizeDirection.Left
dir = HTLEFT
Case ResizeDirection.TopLeft
dir = HTTOPLEFT
Case ResizeDirection.Top
dir = HTTOP
Case ResizeDirection.TopRight
dir = HTTOPRIGHT
Case ResizeDirection.Right
dir = HTRIGHT
Case ResizeDirection.BottomRight
dir = HTBOTTOMRIGHT
Case ResizeDirection.Bottom
dir = HTBOTTOM
Case ResizeDirection.BottomLeft
dir = HTBOTTOMLEFT
Case ResizeDirection.None
End Select
If dir <> -1 Then
ReleaseCapture()
SendMessage(ParentForm.Handle, WM_NCLBUTTONDOWN, dir, 0)
End If
End Sub
Floaters
In the screen shot above, you may notice that there are buttons floating below the Form
outside the frame. This is just a trick using the FrameHeightAdj
Property. This pads the frame up leaving a space on the Form
. Place a Panel
there and color it with the forms TransparentKey
color. Put whatever controls you want there and they will appear to be floating outside the Form
.
Other Features
Form2
shows how you can change the TitleBar Height
and Border
width. You can also see a Form
inside a Form
. This is really a Panel
with the gTitleBar
added. This could be used as a limited type of popup if you want. It can be moved or locked in place. Setting the ControlBoxAffects
property to EventTrigger
will let you take over the button's event.

MDI Forms
Form3
shows how you can use the gTitleBar
on a MDI form. Use the gTitleBarMDI
as the Child MDI Form.

History
- Version 1.0.0 - September 2010
- First version
- Version 1.1.0 - October 2011
- Added MDI support