Download MetroUI_Form.zip - 87.87 KB

zuneui.jpg

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's aren't as easy as it seems. This arcticle will show you how to create those forms using a bit of DWM and some other Windows Api's.

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. Luckly, José Mendez shown us on his arcticle how to do some of these stuffs.

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 easy for you to use my code. Unluckly, the resize thing must be changed if you want to extend that funcionality 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 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
    ''' <summary>
    ''' Equivalent to the HiWord C Macro
    ''' </summary>
    ''' <param name="dwValue"></param>
    ''' <returns></returns>
    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)

            ' Adjust (shrink) the client rectangle to accommodate the border:
            nccsp.rect0.Top += 0
            nccsp.rect0.Bottom += 0
            nccsp.rect0.Left += 0
            nccsp.rect0.Right += 0

            If Not _marginOk Then
                'Set what client area would be for passing to DwmExtendIntoClientArea. Also remember that at least one of these values NEEDS TO BE > 1, else it won't work.
                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 has AT LEAST one value higher then 1.

You should have something like this by now:

Sem_t_tulo.png

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




            'Change cursor

            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

        'Calculate which direction to resize based on mouse position




        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




    "user32.dll")> _

    Public Shared Function ReleaseCapture() As Boolean

    End Function




    "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 easly 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:

zuneui.jpg

That's the Zune interface.

geet.jpg

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 a 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.

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"