Introduction
The future of Windows Interfaces is probably the Zune-like ones, with a borderless form and some controls inside of it. The problem is: if you're using WindowsForm
, creating that borderless form with shadows and resizing stuff isn't as easy as it seems. This article will show you how to create those forms using a bit of DWM and some other Windows APIs.
Background
To create the desired effect, we will need to extend the non-client area, cut off the Aero's glass from the Window, and handle some resizing and moving events. Luckily, José Mendez has shown us in his article how to do some of these things.
Of course, "the non-client area trick" only works if Aero is turned on. But I've handled it on my code. Let's take a look:
Using the Code
I've tried to create a Form for you to inherit it, making it easy for you to use my code. Unluckily, the resize thing must be changed if you want to extend that functionality into a panel or some other control. So I preferred to give my code for you to add it inside your own form.
Also, you need to have a form's text and set the FormBorderStyle
property to "Sizable
" to make everything work properly. DWM can't extend the glass and the client-area if it can't find one, so removing your form's text + controlbox or setting the FormBorderStyle
to anything else other than "Sizable
" would bring you some unexpected bugs. Keep that it mind.
First, you'll need to add both classes DWM.vb and WinApi.vb to your project. Then, use this code to give your form a Zune (Metro) like shape:
#Region "Fields"
Private dwmMargins As Dwm.MARGINS
Private _marginOk As Boolean
Private _aeroEnabled As Boolean = False
#End Region
#Region "Ctor"
Public Sub New()
SetStyle(ControlStyles.ResizeRedraw, True)
InitializeComponent()
DoubleBuffered = True
End Sub
#End Region
#Region "Props"
Public ReadOnly Property AeroEnabled() As Boolean
Get
Return _aeroEnabled
End Get
End Property
#End Region
#Region "Methods"
Public Shared Function LoWord(ByVal dwValue As Integer) As Integer
Return dwValue And &HFFFF
End Function
Public Shared Function HiWord(ByVal dwValue As Integer) As Integer
Return (dwValue >> 16) And &HFFFF
End Function
#End Region
Private Sub Form1_Activated(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Me.Activated
Dwm.DwmExtendFrameIntoClientArea(Me.Handle, dwmMargins)
End Sub
Protected Overloads Overrides Sub WndProc(ByRef m As Message)
Dim WM_NCCALCSIZE As Integer = &H83
Dim WM_NCHITTEST As Integer = &H84
Dim result As IntPtr
Dim dwmHandled As Integer = Dwm.DwmDefWindowProc_
(m.HWnd, m.Msg, m.WParam, m.LParam, result)
If dwmHandled = 1 Then
m.Result = result
Exit Sub
End If
If m.Msg = WM_NCCALCSIZE AndAlso CInt(m.WParam) = 1 Then
Dim nccsp As NCCALCSIZE_PARAMS = _
DirectCast(Marshal.PtrToStructure(m.LParam, _
GetType(NCCALCSIZE_PARAMS)), NCCALCSIZE_PARAMS)
nccsp.rect0.Top += 0
nccsp.rect0.Bottom += 0
nccsp.rect0.Left += 0
nccsp.rect0.Right += 0
If Not _marginOk Then
dwmMargins.cyTopHeight = 0
dwmMargins.cxLeftWidth = 0
dwmMargins.cyBottomHeight = 1
dwmMargins.cxRightWidth = 0
_marginOk = True
End If
Marshal.StructureToPtr(nccsp, m.LParam, False)
m.Result = IntPtr.Zero
ElseIf m.Msg = WM_NCHITTEST AndAlso CInt(m.Result) = 0 Then
m.Result = HitTestNCA(m.HWnd, m.WParam, m.LParam)
Else
MyBase.WndProc(m)
End If
End Sub
Private Function HitTestNCA(ByVal hwnd As IntPtr, ByVal wparam _
As IntPtr, ByVal lparam As IntPtr) As IntPtr
Dim HTNOWHERE As Integer = 0
Dim HTCLIENT As Integer = 1
Dim HTCAPTION As Integer = 2
Dim HTGROWBOX As Integer = 4
Dim HTSIZE As Integer = HTGROWBOX
Dim HTMINBUTTON As Integer = 8
Dim HTMAXBUTTON As Integer = 9
Dim HTLEFT As Integer = 10
Dim HTRIGHT As Integer = 11
Dim HTTOP As Integer = 12
Dim HTTOPLEFT As Integer = 13
Dim HTTOPRIGHT As Integer = 14
Dim HTBOTTOM As Integer = 15
Dim HTBOTTOMLEFT As Integer = 16
Dim HTBOTTOMRIGHT As Integer = 17
Dim HTREDUCE As Integer = HTMINBUTTON
Dim HTZOOM As Integer = HTMAXBUTTON
Dim HTSIZEFIRST As Integer = HTLEFT
Dim HTSIZELAST As Integer = HTBOTTOMRIGHT
Dim p As New Point(LoWord(CInt(lparam)), HiWord(CInt(lparam)))
Dim topleft As Rectangle = RectangleToScreen(New Rectangle(0, 0, _
dwmMargins.cxLeftWidth, dwmMargins.cxLeftWidth))
If topleft.Contains(p) Then
Return New IntPtr(HTTOPLEFT)
End If
Dim topright As Rectangle = _
RectangleToScreen(New Rectangle(Width - dwmMargins.cxRightWidth, 0, _
dwmMargins.cxRightWidth, dwmMargins.cxRightWidth))
If topright.Contains(p) Then
Return New IntPtr(HTTOPRIGHT)
End If
Dim botleft As Rectangle = _
RectangleToScreen(New Rectangle(0, Height - dwmMargins.cyBottomHeight, _
dwmMargins.cxLeftWidth, dwmMargins.cyBottomHeight))
If botleft.Contains(p) Then
Return New IntPtr(HTBOTTOMLEFT)
End If
Dim botright As Rectangle = _
RectangleToScreen(New Rectangle(Width - dwmMargins.cxRightWidth, _
Height - dwmMargins.cyBottomHeight, _
dwmMargins.cxRightWidth, dwmMargins.cyBottomHeight))
If botright.Contains(p) Then
Return New IntPtr(HTBOTTOMRIGHT)
End If
Dim top As Rectangle = _
RectangleToScreen(New Rectangle(0, 0, Width, dwmMargins.cxLeftWidth))
If top.Contains(p) Then
Return New IntPtr(HTTOP)
End If
Dim cap As Rectangle = _
RectangleToScreen(New Rectangle(0, dwmMargins.cxLeftWidth, _
Width, dwmMargins.cyTopHeight - dwmMargins.cxLeftWidth))
If cap.Contains(p) Then
Return New IntPtr(HTCAPTION)
End If
Dim left As Rectangle = _
RectangleToScreen(New Rectangle(0, 0, dwmMargins.cxLeftWidth, Height))
If left.Contains(p) Then
Return New IntPtr(HTLEFT)
End If
Dim right As Rectangle = _
RectangleToScreen(New Rectangle(Width - dwmMargins.cxRightWidth, _
0, dwmMargins.cxRightWidth, Height))
If right.Contains(p) Then
Return New IntPtr(HTRIGHT)
End If
Dim bottom As Rectangle = _
RectangleToScreen(New Rectangle(0, Height - dwmMargins.cyBottomHeight, _
Width, dwmMargins.cyBottomHeight))
If bottom.Contains(p) Then
Return New IntPtr(HTBOTTOM)
End If
Return New IntPtr(HTCLIENT)
End Function
At:
dwmMargins.cyTopHeight = 0
dwmMargins.cxLeftWidth = 0
dwmMargins.cyBottomHeight = 1
dwmMargins.cxRightWidth = 0
You need to have AT LEAST one value higher than 1.
You should have something like this by now:
NICE! Now you have a Zune like shape, with shadows and everything. What else will we need? The resizing stuff, of course!
Private Const BorderWidth As Integer = 6
Private _resizeDir As ResizeDirection = ResizeDirection.None
Private Sub Form1_MouseDown(ByVal sender As System.Object, _
ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseDown
If e.Button = Windows.Forms.MouseButtons.Left Then
If Me.Width - BorderWidth > e.Location.X AndAlso _
e.Location.X > BorderWidth AndAlso e.Location.Y > BorderWidth Then
MoveControl(Me.Handle)
Else
If Me.WindowState <> FormWindowState.Maximized Then
ResizeForm(resizeDir)
End If
End If
End If
End Sub
Public Enum ResizeDirection
None = 0
Left = 1
TopLeft = 2
Top = 4
TopRight = 8
Right = 16
BottomRight = 32
Bottom = 64
BottomLeft = 128
End Enum
Private Property resizeDir() As ResizeDirection
Get
Return _resizeDir
End Get
Set(ByVal value As ResizeDirection)
_resizeDir = value
Select Case value
Case ResizeDirection.Left
Me.Cursor = Cursors.SizeWE
Case ResizeDirection.Right
Me.Cursor = Cursors.SizeWE
Case ResizeDirection.Top
Me.Cursor = Cursors.SizeNS
Case ResizeDirection.Bottom
Me.Cursor = Cursors.SizeNS
Case ResizeDirection.BottomLeft
Me.Cursor = Cursors.SizeNESW
Case ResizeDirection.TopRight
Me.Cursor = Cursors.SizeNESW
Case ResizeDirection.BottomRight
Me.Cursor = Cursors.SizeNWSE
Case ResizeDirection.TopLeft
Me.Cursor = Cursors.SizeNWSE
Case Else
Me.Cursor = Cursors.Default
End Select
End Set
End Property
Private Sub Form1_MouseMove(ByVal sender As System.Object, _
ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseMove
If e.Location.X < BorderWidth And e.Location.Y < BorderWidth Then
resizeDir = ResizeDirection.TopLeft
ElseIf e.Location.X < BorderWidth And e.Location.Y > Me.Height - BorderWidth Then
resizeDir = ResizeDirection.BottomLeft
ElseIf e.Location.X > Me.Width - BorderWidth And e.Location.Y > _
Me.Height - BorderWidth Then
resizeDir = ResizeDirection.BottomRight
ElseIf e.Location.X > Me.Width - BorderWidth And e.Location.Y < BorderWidth Then
resizeDir = ResizeDirection.TopRight
ElseIf e.Location.X < BorderWidth Then
resizeDir = ResizeDirection.Left
ElseIf e.Location.X > Me.Width - BorderWidth Then
resizeDir = ResizeDirection.Right
ElseIf e.Location.Y < BorderWidth Then
resizeDir = ResizeDirection.Top
ElseIf e.Location.Y > Me.Height - BorderWidth Then
resizeDir = ResizeDirection.Bottom
Else
resizeDir = ResizeDirection.None
End If
End Sub
Private Sub MoveControl(ByVal hWnd As IntPtr)
ReleaseCapture()
SendMessage(hWnd, WM_NCLBUTTONDOWN, HTCAPTION, 0)
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
End Select
If dir <> -1 Then
ReleaseCapture()
SendMessage(Me.Handle, WM_NCLBUTTONDOWN, dir, 0)
End If
End Sub
<DllImport("user32.dll")> _
Public Shared Function ReleaseCapture() As Boolean
End Function
<DllImport("user32.dll")> _
Public Shared Function SendMessage(ByVal hWnd As IntPtr, _
ByVal Msg As Integer, ByVal wParam As Integer, ByVal lParam As Integer) _
As Integer
End Function
Private Const WM_NCLBUTTONDOWN As Integer = &HA1
Private Const HTBORDER As Integer = 18
Private Const HTBOTTOM As Integer = 15
Private Const HTBOTTOMLEFT As Integer = 16
Private Const HTBOTTOMRIGHT As Integer = 17
Private Const HTCAPTION As Integer = 2
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
Nice, everything should be working by now. :D
Ah, you can also increase and decrease border size by changing the "BorderWidth
" value to whatever you want. Just remember that it needs to be an integer value.
The Last Problem
Everything works fine if you don't overlay any border, but if you do, there's a simple way to fix it. All you have to do is to add your control's mousedown and mousemove events on the Form1_MouseDown
and Form1_MouseMove
events. That's all. :D
A Bit of Work...
With a little bit more of work and my code, you can easily create full-featured and amazing interfaces. And the coolest part is that now, you'd be able to create everything on your form! Want a Mac OS X window? Just create it! Want a Zune Clone interface? Just copy it! That simple! :D
I've created a simple demonstration of what can be made without much work (that's just a preview to show something different than a black window). Check this out:
That's the Zune interface.
And this is a simple example of what can be made with a little work. That's just a sample application I've created within 5 minutes to test this article, and as you can see, it's pretty much the same as the Zune UI.
Points of Interest
It is cool to develop this kind of interface since you can have full control over your form without losing some pretty cool Vista/7 effects such as shadows.
By the way, I'd like to say sorry for my bad English.
Peace.
History
- 1st code revision published