Page 1 of 1

Creating A Custom Form Class Library Rate Topic: ***** 2 Votes

#1 IronRazer   User is offline

  • Custom Control Freak
  • member icon

Reputation: 1500
  • View blog
  • Posts: 3,799
  • Joined: 01-February 13

Posted 26 April 2018 - 05:46 AM

What We Are Going To Do...

In this tutorial we will create our own custom Form class as a Class Library project. This will create a dll file which we can use to make the Forms in other Form projects inherit from. When those forms inherit from our custom Form class, they will look the same in the designer window of Visual Studio as we have designed our custom form to look. They will also have all of the custom Properties, Methods, and Events we add to it along with all the ones included in a standard Form class.

Then we will create a new Form project and modify it's (Form.Designer.vb) file to make it's main form (Form1) inherit from our custom form. It is not normally a good idea to modify the Designer file of your projects unless you know what you are doing and/or there is no other way around it. In our case, there is no other way to get around modifying it. The Designer file is created and kept updated by VS with the code needed for VS to know what controls you have added, where they belong on the form, and what properties you have changed from their default values.



Creating The Class Library Project

First, Open Visual Studio and on the VS menu click (File -> New Project). When the (New Project) dialog window opens, select the (Class Library) project template. At the bottom of the (New Project) dialog window, you will want to give the project a name. To easily follow along with this tutorial I recommend naming it the same as I have. I named mine as (IronWorks). Now you can Press the (Ok) button to create the new project.

The next thing we will do is give the Class in our project a name. Open the (Solution Explorer) tab and right-click on the "Class1.vb" file as seen in the image below, then choose (Rename) and name it (CustomForm.vb). Before going any further, you should save the project using (File -> Save All) on the VS menu. It is always a good idea to use (Save All) every time you make any significant changes to your code or project settings.

Posted Image



Adding Assembly References and Importing Namespaces

Alright, now we need to add a few assembly references. These assemblies are ones that are automatically referenced when you create a standard (Windows Form) project and they are required if we want to create a Form class. Since we are starting with a blank class which had no idea what it was going to be, we need to add the references we need manually.

So, on the VS menu select (Project -> Add Reference). When the (Reference Manager) window opens, select the (Assemblies) tab. Scroll down through and check the checkbox's next to the (System.Windows.Forms) and (System.Drawing) assemblies. Then click the (Ok) button to add those references.

Posted Image

To make the classes, structures, and enumerations in those referenced assemblies easier to use throughout our class's code we can Import their namespaces at the top of our class's code as shown here.
Imports System.Windows.Forms
Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.ComponentModel

Public Class CustomForm

End Class




Now we are ready to look over and add the code for our custom Form class which will make it look and act the way we want. In this example, we are just going to make this Form into a Borderless Form and draw the caption area, borders, and caption buttons ourselves.

This custom Form example is designed so that the Visual Studio user of the custom form can change the colors, sizes, and other aspects of the form's caption area as well as the caption buttons too. You do not have to do all of this work if you have a specific look you want for your form and don't want it to be changed. That would reduce the code quite a bit because you would not need to add all the custom properties which I have added to change it's appearance.


What is the funny looking code above the properties?

First, lets take a look at one of the properties so I can explain what all the funny code is above the properties. Here is the CanResize property...
    ''' <summary>Determines if the user can resize the form with the mouse at runtime.</summary>
    <Browsable(True), EditorBrowsable(EditorBrowsableState.Always), DefaultValue(True)>
    <Category("behavior"), Description("Determines if the user can resize the form with the mouse at runtime.")>
    Public Property CanResize As Boolean = True


The first line which starts with the three ''' characters identifies that the following <tags> contain xml documentation. The <summery> xml tags which I used here is used to explain to the user what the Property is used for. These comments can be seen by the user in the Intellisense window when the user is typing the property name into the visual studio code window. You can refer to the following links to learn more about xml documentation comments.

How to: Create XML Documentation in Visual Basic
Recommended XML Tags for Documentation Comments (Visual Basic)
Documenting Your Code with XML (Visual Basic)

Now take a look at the second and third line, these are what is called Attributes. There are several types of Attributes but, I am only using a few common ones in this example. The (Description) attribute is used to show the user what the Property is used for. This comment is the one shown at the bottom of the Properties tab in the designer window. You can look at the following links to learn more about Attributes.

Design-Time Attributes for Components
Attributes and Design-Time Support



Looking Over The Specially Handled Properties

We will skip past going over all the custom properties I added since the names of the properties pretty much explain what each of them is for and there is nothing special about them. However, we will take a quick look at a few of the properties which are actually inherited from the Form class.

For the (BackColor) property, I used the property Override for a few reasons. One is so I could set the color of the class scoped ClientBrush and then call the Invalidate method of the form to tell it to repaint itself. The other reason is so that I could change the default BackColor to "WhiteSmoke" instead of the normal "Control" color used for a default. I use the DefaultValue attribute to do that.

I Shadowed the (ControlBox) property for one purpose, so I could make the form repaint itself to reflect the change. If it is True, then the custom ControlBox is drawn, if it is False, the custom ControlBox is not drawn.

The last three properties here, the (DoubleBuffered, FormBorderStyle, and ResizeRedraw) properties are ones that we want set to specific values and do not want the user to change them. So, the one that can not be hidden from the user, the DoubleBuffered property, I am just marking with an Obsolete attribute and also making it so the user can not change it. The (FormBorderStyle) and (ResizeRedraw) properties can be hidden from the user so, I hid those two using the EditorBrowsable and Browsable attributes. I also made them so the user can not change them either.

When I say hidden, I mean the property is not seen in the design or code window of VS but, if the user actually typed the property names out in code, it could still be changes by the user. It just would not recognized and shown by intellisense. That is why I stop the hidden property from being changed too.

    ''' <summary>Gets or sets the background color of the client area.</summary>
    <Browsable(True), EditorBrowsable(EditorBrowsableState.Always), DefaultValue(GetType(SystemColors), "WhiteSmoke")>
    <Category("Appearance"), Description("The background color of the form.")>
    Public Overrides Property BackColor As Color
        Get
            Return MyBase.BackColor
        End Get
        Set(value As Color)
            MyBase.BackColor = value
            ClientBrush.Color = value 'set the class scoped ClientBrush.Color to the new color
            Me.Invalidate(False)
        End Set
    End Property

    'Here we Shadow the ControlBox property in the Form class. We Shadow this property just so that we can make our form repaint itself when this property has been changed.
    Public Shadows Property ControlBox As Boolean
        Get
            Return MyBase.ControlBox
        End Get
        Set(value As Boolean)
            MyBase.ControlBox = value
            Me.Invalidate(False) 'tell the form to repaint itself to reflect this change
        End Set
    End Property


    'This property can not be hidden from the user so, we just mark it as an obsolete property which explains to the user why. We also stop the user from changing it.
    <Obsolete("The DoubleBuffered property can not be changed and is always set to True in this class.")>
    Protected Overrides Property DoubleBuffered As Boolean
        Get
            Return MyBase.DoubleBuffered
        End Get
        Set(value As Boolean)
            value = True 'we are forcing this property to True if the user tries setting it to False
            MyBase.DoubleBuffered = value
        End Set
    End Property

    'We always want our Form to have no Border (Borderless), so we will just stop the user from seeing it in the designer or code view and stop them from changing it in any way too.
    <Browsable(False), EditorBrowsable(EditorBrowsableState.Never)>
    Public Shadows Property FormBorderStyle As FormBorderStyle
        Get
            Return MyBase.FormBorderStyle
        End Get
        Set(value As FormBorderStyle)
            value = FormBorderStyle.None
            MyBase.FormBorderStyle = FormBorderStyle.None
        End Set
    End Property

    'We always want this set to True in our class so, we will just mark it with an obsolete attribute which explains to the user why, and stop the user from changing it.
    <Obsolete("The ResizeRedraw property can not be changed and is always set to True in this class.")>
    <Browsable(False), EditorBrowsable(EditorBrowsableState.Never)>
    Public Shadows Property ResizeRedraw As Boolean
        Get
            Return MyBase.ResizeRedraw
        End Get
        Set(value As Boolean)
            value = True 'we are forcing this property to True if the user tries setting it to False
            MyBase.ResizeRedraw = value
        End Set
    End Property





Looking At The Window Process (WndProc)

Alright, lets take a quick look at the WndProc overrides sub of our custom form. This sub receives and processes Window Messages which are sent to our form from the operating system. These messages tell the window if it needs to do something or react in some way. There are a lot of different kinds of window messages it could receive but, we are only interested in a few of them. One is the WM_NCHITTEST message which is received when the mouse is moved or a button is pressed or released while the mouse is over the form. Even if the mouse is over a non-client areas of the form like the caption area, caption buttons, or it's borders.

By detecting the WM_NCHITTEST message, we can handle the message and return a specific HTxxx value to tell the form what part of the form the mouse it is over. For example the Caption area, or Left Border, a Close, Minimize, or Maximize button and so on. This allows us to make our borderless form react to the mouse the same way as a standard form (not borderless).

For example, If we handle the WM_NCHITTEST message and the mouse is over our specified caption area, and we return the HTCAPTION value, the WM_NCMOUSEMOVE message will be sent to the form as the mouse is moved over our specified Caption area.

Here are the window messages, the hit test values, and the WndProc overrides sub from our custom form class. I commented it a little to try helping understand it.
    'These are Window Messages that a Window can receive through its Window Process, in our case we use the (WndProc) overrides method of this Form. 
    'We use these messages to control the mouse's interaction with the Form when the mouse is moved or pressed down on this borderless form/window.
    Private Const WM_NCMOUSEMOVE As Integer = &HA0
    Private Const WM_NCLBUTTONDOWN As Integer = &HA1
    Private Const WM_NCRBUTTONUP As Integer = &HA5
    Private Const WM_NCHITTEST As Integer = &H84
    Private Const HTCLIENT As Integer = &H1
    Private Const HTCAPTION As Integer = &H2
    Private Const HTSYSMENU As Integer = &H3
    Private Const HTMINBUTTON As Integer = &H8
    Private Const HTMAXBUTTON As Integer = &H9
    Private Const HTLEFT As Integer = &HA
    Private Const HTRIGHT As Integer = &HB
    Private Const HTTOP As Integer = &HC
    Private Const HTTOPLEFT As Integer = &HD
    Private Const HTTOPRIGHT As Integer = &HE
    Private Const HTBOTTOM As Integer = &HF
    Private Const HTBOTTOMLEFT As Integer = &H10
    Private Const HTBOTTOMRIGHT As Integer = &H11
    Private Const HTCLOSE As Integer = &H14


    Protected Overrides Sub WndProc(ByRef m As Message)
        If m.Msg = WM_NCMOUSEMOVE Then 'the WM_NCMOUSEMOVE message is sent to the form/window when the mouse is moving over a non-client area of the form.
            'get the mouse position relative to the form location and reset the boolean variables used to tell the Paint event how to draw the buttons, active or not active.
            Dim loc As Point = Me.PointToClient(MousePosition)
            MouseOnClose = CloseBtnBounds.Contains(loc)
            MouseOnMax = Me.MaximizeBox AndAlso MaximizeBtnBounds.Contains(loc)
            MouseOnMin = Me.MinimizeBox AndAlso MinimizeBtnBounds.Contains(loc)
            MouseOnIcon = Me.ShowIcon AndAlso IconBounds.Contains(loc)
            Me.Invalidate(False)

        ElseIf m.Msg = WM_NCLBUTTONDOWN Then 'The WM_NCLBUTTONDOWN message is sent to the form when the left mouse button is pressed down in a non-client area of the form.
            If Me.ControlBox Then
                If m.WParam.ToInt32 = HTCLOSE Then 'if we told it the mouse was on our Close button, then close the form
                    Me.Close()
                    m.Result = CType(0, IntPtr)
                    Return
                ElseIf m.WParam.ToInt32 = HTMINBUTTON Then 'if we told it the mouse was on our Minimize button, then minimize the form
                    Me.WindowState = FormWindowState.Minimized
                    m.Result = CType(0, IntPtr)
                    Return
                ElseIf m.WParam.ToInt32 = HTMAXBUTTON Then 'if we told it the mouse was on our Maximize button, then maximize or restore the form depending on it's current state
                    If Me.WindowState = FormWindowState.Normal Then
                        Me.WindowState = FormWindowState.Maximized
                    Else
                        Me.WindowState = FormWindowState.Normal
                    End If
                    m.Result = CType(0, IntPtr)
                    Return
                ElseIf m.WParam.ToInt32 = HTSYSMENU Then 'if we told it the mouse was on our 'system menu' Icon, then show our custom 'system menu'
                    Dim sl As Point = Me.PointToScreen(New Point(3, Me.CaptionHeight))
                    SysMenu.Show(sl)
                    m.Result = CType(0, IntPtr)
                    Return
                End If
            End If

        ElseIf m.Msg = WM_NCRBUTTONUP Then 'The WM_NCRBUTTONUP message is sent to the form when the right mouse button is released in a non-client area of the form.
            'if the mouse is in the caption area but, not over a button or the icon, then show our custom 'System Menu' when the right button is released
            If Not MouseOnIcon AndAlso Not MouseOnMin AndAlso Not MouseOnMax AndAlso Not MouseOnClose Then
                SysMenu.Show(MousePosition)
                m.Result = CType(0, IntPtr)
                Return
            End If

        ElseIf m.Msg = WM_NCHITTEST Then 'this message is sent to the form when the mouse is moving anywhere over the form. We will use this to tell the form what part of the non-cliect area the mouse is over so it can act appropriatly.
            Dim loc As Point = Me.PointToClient(MousePosition)
            Dim bTop As Boolean = (loc.Y < 4 AndAlso Me.CanResize)
            Dim bLeft As Boolean = (loc.X < 4 AndAlso Me.CanResize)
            Dim bRight As Boolean = (loc.X > Me.ClientSize.Width - 4 AndAlso Me.CanResize)
            Dim bBottom As Boolean = (loc.Y > Me.ClientSize.Height - 4 AndAlso Me.CanResize)
            Dim bClose As Boolean = (CloseBtnBounds.Contains(loc) AndAlso Me.ControlBox)
            Dim bMin As Boolean = (MinimizeBtnBounds.Contains(loc) AndAlso Me.ControlBox AndAlso Me.MinimizeBox)
            Dim bMax As Boolean = (MaximizeBtnBounds.Contains(loc) AndAlso Me.ControlBox AndAlso Me.MaximizeBox)
            Dim bCaption As Boolean = (loc.Y > 3 AndAlso loc.Y < _CaptionHeight AndAlso loc.X < Me.Width - 3)
            Dim bClient As Boolean = ClntBnds.Contains(loc)
            Dim bIcon As Boolean = (Me.ShowIcon AndAlso IconBounds.Contains(loc))

            If bClose Then 'if the mouse is over our Close button
                m.Result = CType(HTCLOSE, IntPtr) 'return HTCLOSE result to tell our form that the mouse is over our close button
                If Not Me.DesignMode Then MouseTrackingTimer.Start() 'if the mouse enters the Close button, start the MouseTrackingTimer
                Return
            ElseIf bMin Then
                m.Result = CType(HTMINBUTTON, IntPtr)
                If Not Me.DesignMode Then MouseTrackingTimer.Start() 'if the mouse enters the Minimize button, start the MouseTrackingTimer
                Return
            ElseIf bMax Then
                m.Result = CType(HTMAXBUTTON, IntPtr)
                If Not Me.DesignMode Then MouseTrackingTimer.Start() 'if the mouse enters the Maximize button, start the MouseTrackingTimer
                Return
            ElseIf bIcon Then 'if the mouse is over the Icon (System Menu)
                m.Result = CType(HTSYSMENU, IntPtr) 'return the HTSYSMENU. This lets us detect it being clicked And show our custom 'system menu'
                Return
            ElseIf bCaption Then 'if the mouse is over the caption area
                m.Result = CType(HTCAPTION, IntPtr) 'return HTCAPTION. This will allow use to click and drag the form to move it
                Return
            ElseIf bBottom AndAlso bLeft Then 'if the mouse is at the bottom left corner of our form
                m.Result = CType(HTBOTTOMLEFT, IntPtr) 'return HTBOTTOMLEFT. This will change the cursur to a sizing cursor and allow us to resize the form
                Return
            ElseIf bBottom AndAlso bRight Then
                m.Result = CType(HTBOTTOMRIGHT, IntPtr)
                Return
            ElseIf bTop AndAlso bLeft Then
                m.Result = CType(HTTOPLEFT, IntPtr)
                Return
            ElseIf bTop AndAlso bRight Then
                m.Result = CType(HTTOPRIGHT, IntPtr)
                Return
            ElseIf bLeft Then
                m.Result = CType(HTLEFT, IntPtr)
                Return
            ElseIf bTop Then
                m.Result = CType(HTTOP, IntPtr)
                Return
            ElseIf bRight Then
                m.Result = CType(HTRIGHT, IntPtr)
                Return
            ElseIf bBottom Then
                m.Result = CType(HTBOTTOM, IntPtr)
                Return
            ElseIf bClient Then
                m.Result = CType(HTCLIENT, IntPtr)
                Return
            End If
        End If
        MyBase.WndProc(m)
    End Sub





Adding The Code For The CustomForm Class

Now the majority of the rest of the code is for drawing our custom form. I have commented the (OnPaint) overrides sub to explain which part is drawing what on the form. It should be pretty clear how most of it is working. If not, I would recommend looking up the online Msdn documentation for the methods you don't understand.

So, here is the full code for the complete IronWorks CustomForm class. I have left it all commented to help you understand it easier. You can copy this code and paste it into the (CustomForm) class that you created in the (Creating The Class Library Project) section. After adding the code you can build the dll file by going to the VS menu and clicking (Build -> Build IronWorks). That's it, you will then have a dll file in the Debug folder of the IronWorks project folder that you can use to make other project Forms Inherit from.
Imports System.Windows.Forms
Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.ComponentModel

'This is an enumeration that I created for setting the CaptionButtonVerticalAlign property.
Public Enum VerticalAlignment As Integer
    Top = 0
    Center = 1
    Bottom = 2
End Enum

Public Class CustomForm
    Inherits Form

    'These are for our own 'System Menu'. We need to create our own because a borderless form does not have one by default.
    Private SysMenu As ContextMenuStrip
    Private CloseItem As ToolStripItem
    Private MinimizeItem As ToolStripItem
    Private MaximizeItem As ToolStripItem

    'These are Window Messages that a Window can receive through its Window Process, in our case we use the (WndProc) overrides method of this Form. 
    'We use these messages to control the mouse's interaction with the Form when the mouse is moved or pressed down on this borderless form/window.
    Private Const WM_NCMOUSEMOVE As Integer = &HA0
    Private Const WM_NCLBUTTONDOWN As Integer = &HA1
    Private Const WM_NCRBUTTONUP As Integer = &HA5
    Private Const WM_NCHITTEST As Integer = &H84
    Private Const HTCLIENT As Integer = &H1
    Private Const HTCAPTION As Integer = &H2
    Private Const HTSYSMENU As Integer = &H3
    Private Const HTMINBUTTON As Integer = &H8
    Private Const HTMAXBUTTON As Integer = &H9
    Private Const HTLEFT As Integer = &HA
    Private Const HTRIGHT As Integer = &HB
    Private Const HTTOP As Integer = &HC
    Private Const HTTOPLEFT As Integer = &HD
    Private Const HTTOPRIGHT As Integer = &HE
    Private Const HTBOTTOM As Integer = &HF
    Private Const HTBOTTOMLEFT As Integer = &H10
    Private Const HTBOTTOMRIGHT As Integer = &H11
    Private Const HTCLOSE As Integer = &H14

    Private Const CS_DROPSHADOW As Integer = &H20000

    Private ClientBrush As New SolidBrush(SystemColors.Control) 'used to draw the client area of the form
    Private BorderPen As New Pen(Color.Black) 'used to draw the Form's Border
    Private CaptionButtonBorderPen As New Pen(Color.Transparent) 'used to draw the caption button Borders
    Private CaptionButtonForeColorPen As New Pen(Color.White, 2) 'used to draw the symbals in the caption buttons

    Private FrmBnds, ClntBnds, CapBnds As Rectangle
    Private CloseBtnBounds, MaximizeBtnBounds, MinimizeBtnBounds, IconBounds As Rectangle
    Private MouseOnClose, MouseOnMax, MouseOnMin, MouseOnIcon As Boolean

    Private bBlend As New Blend 'used to control the color blending of the gradient colors in the caption area and caption buttons

    'This timer is only enabled when the mouse is moved onto a caption button, if they are enabled/shown.
    'It fixes a bug With the mouse quickly leaving the form and the caption button being left highlighted.
    Private MouseTrackingTimer As Timer

    ''' <summary>Creates a new instance of a CustomForm.</summary>
    Public Sub New()
        MyBase.ResizeRedraw = True
        MyBase.DoubleBuffered = True
        MyBase.FormBorderStyle = FormBorderStyle.None
        Me.BackColor = Color.WhiteSmoke
        bBlend.Positions = New Single() {0.0F, 0.1F, 0.2F, 0.3F, 0.4F, 0.5F, 0.6F, 0.7F, 0.8F, 0.9F, 1.0F}

        'if we are not in design mode, then create our 'System Menu', it's images, and add the handlers for the events we need to use from it.
        If Not Me.DesignMode Then
            SysMenu = New ContextMenuStrip
            AddHandler SysMenu.Opening, AddressOf SysMenu_Opening
            AddHandler SysMenu.ItemClicked, AddressOf SysMenu_ItemClicked

            Dim MinBmp As New Bitmap(20, 20)
            Using g As Graphics = Graphics.FromImage(MinBmp)
                g.FillRectangle(Brushes.Black, 3, 16, 14, 2)
            End Using
            MinimizeItem = SysMenu.Items.Add("Minimize", MinBmp)

            Dim MaxBmp As New Bitmap(20, 20)
            Using g As Graphics = Graphics.FromImage(MaxBmp)
                g.DrawRectangle(Pens.Black, 3, 3, 14, 14)
            End Using
            MaximizeItem = SysMenu.Items.Add("Maximize", MaxBmp)

            SysMenu.Items.Add(New ToolStripSeparator)

            Dim CloseBmp As New Bitmap(20, 20)
            Using g As Graphics = Graphics.FromImage(CloseBmp), xpen As New Pen(Color.Black, 2)
                g.DrawLine(xpen, 4, 3, 16, 15)
                g.DrawLine(xpen, 4, 15, 16, 3)
            End Using
            CloseItem = SysMenu.Items.Add("Close", CloseBmp)
 
            MouseTrackingTimer = New Timer With {.Interval = 100}
            AddHandler MouseTrackingTimer.Tick, AddressOf MouseTrackingTimer_Tick
       End If
    End Sub

    Private Sub MouseTrackingTimer_Tick(sender As Object, e As EventArgs)
        Dim loc As Point = Me.PointToClient(MousePosition)
        'The timer is only started when the mouse moves onto a caption button so, we will check to see if the mouse has left the caption button(s).
        If Not CloseBtnBounds.Contains(loc) AndAlso Not (Me.MaximizeBox AndAlso MaximizeBtnBounds.Contains(loc)) AndAlso Not (Me.MinimizeBox AndAlso MinimizeBtnBounds.Contains(loc)) Then
            MouseTrackingTimer.Stop() 'stop the timer if the mouse is no longer over a caption button
            MouseOnClose = False
            MouseOnMax = False
            MouseOnMin = False
            MouseOnIcon = False
            Me.Invalidate(False)
        End If
    End Sub

    'when the form class is being disposed, we also want to dispose of any new objects that we have created instances of, any that implement IDisposable that is.
    Protected Overrides Sub Dispose(disposing As Boolean)
        If Not Me.DesignMode Then
            RemoveHandler SysMenu.ItemClicked, AddressOf SysMenu_ItemClicked
            RemoveHandler SysMenu.Opening, AddressOf SysMenu_Opening
            SysMenu.Dispose()
        End If
        ClientBrush.Dispose()
        BorderPen.Dispose()
        CaptionButtonBorderPen.Dispose()
        CaptionButtonForeColorPen.Dispose()
        MyBase.Dispose(disposing)
    End Sub

    Protected Overrides ReadOnly Property CreateParams As CreateParams
        Get
            Dim cp As CreateParams = MyBase.CreateParams
            If Not Me.DesignMode Then cp.ClassStyle = (cp.ClassStyle Or CS_DROPSHADOW) 'if we are not in the design mode, we add the CS_DROPSHADOW class style to the window's class styles
            Return cp
        End Get
    End Property

    ''' <summary>Determines if the user can resize the form with the mouse at runtime.</summary>
    <Browsable(True), EditorBrowsable(EditorBrowsableState.Always), DefaultValue(True)>
    <Category("behavior"), Description("Determines if the user can resize the form with the mouse at runtime.")>
    Public Property CanResize As Boolean = True

    ''' <summary>Gets or sets the color of the border around the Form.</summary>
    <Browsable(True), EditorBrowsable(EditorBrowsableState.Always), DefaultValue(GetType(Color), "Black")>
    <Category("Appearance"), Description("The color of the border around the Form.")>
    Public Property BorderColor As Color
        Get
            Return BorderPen.Color
        End Get
        Set(value As Color)
            BorderPen.Color = value
            Me.Invalidate(False)
        End Set
    End Property

    Private _CaptionTextFont As New Font("Microsoft Sans Serif", 11.25, FontStyle.Bold)
    ''' <summary>Gets or sets the font used for the caption text.</summary>
    <Browsable(True), EditorBrowsable(EditorBrowsableState.Always), DefaultValue(GetType(Font), "Microsoft Sans Serif, 11.25pt, style=Bold")>
    <Category("Caption Appearance"), Description("The font used for the caption text.")>
    Public Property CaptionTextFont As Font
        Get
            Return _CaptionTextFont
        End Get
        Set(value As Font)
            If value IsNot Nothing Then
                _CaptionTextFont = value
                Me.Invalidate(False)
            End If
        End Set
    End Property

    Private _CaptionTextColor As Color = Color.White
    ''' <summary>Gets or sets the color of the caption text.</summary>
    <Browsable(True), EditorBrowsable(EditorBrowsableState.Always), DefaultValue(GetType(Color), "White")>
    <Category("Caption Appearance"), Description("The color of the caption text.")>
    Public Property CaptionTextColor As Color
        Get
            Return _CaptionTextColor
        End Get
        Set(value As Color)
            _CaptionTextColor = value
            Me.Invalidate(False)
        End Set
    End Property

    Private _CaptionColor As Color = Color.Black
    ''' <summary>Gets or sets the gradient starting color of the form's caption area.</summary>
    <Browsable(True), EditorBrowsable(EditorBrowsableState.Always), DefaultValue(GetType(Color), "Black")>
    <Category("Caption Appearance"), Description("The gradient starting color of the form's caption area.")>
    Public Property CaptionColor As Color
        Get
            Return _CaptionColor
        End Get
        Set(value As Color)
            _CaptionColor = value
            Me.Invalidate(False)
        End Set
    End Property

    Private _CaptionendColor As Color = Color.White
    ''' <summary>Gets or sets the gradient ending color of the caption area of the Form.</summary>
    <Browsable(True), EditorBrowsable(EditorBrowsableState.Always), DefaultValue(GetType(Color), "White")>
    <Category("Caption Appearance"), Description("The gradient ending color of the caption area of the Form.")>
    Public Property CaptionendColor As Color
        Get
            Return _CaptionendColor
        End Get
        Set(value As Color)
            _CaptionendColor = value
            Me.Invalidate(False)
        End Set
    End Property

    Private _CaptionHeight As Integer = 30
    ''' <summary>Gets or sets the height of the caption area.</summary>
    <Browsable(True), EditorBrowsable(EditorBrowsableState.Always), DefaultValue(30)>
    <Category("Caption Appearance"), Description("The height of the caption area.")>
    Public Property CaptionHeight As Integer
        Get
            Return _CaptionHeight
        End Get
        Set(value As Integer)
            If value < 0 Then value = 0
            If value > Me.Height Then value = Me.Height
            _CaptionHeight = value
            Me.Invalidate(False)
        End Set
    End Property

    Private _CaptionButtonVerticalAlign As VerticalAlignment = VerticalAlignment.Center
    ''' <summary>Gets or sets the vertical alignment of the caption buttons within the caption area.</summary>
    <Browsable(True), EditorBrowsable(EditorBrowsableState.Always), DefaultValue(GetType(VerticalAlignment), "Center")>
    <Category("Caption Appearance"), Description("The vertical alignment of the caption buttons within the caption area.")>
    Public Property CaptionButtonVerticalAlign As VerticalAlignment
        Get
            Return _CaptionButtonVerticalAlign
        End Get
        Set(value As VerticalAlignment)
            _CaptionButtonVerticalAlign = value
            Me.Invalidate(False)
        End Set
    End Property

    Private _CaptionButtonSize As Size = New Size(28, 20)
    ''' <summary>Gets or sets the size of the caption buttons.</summary>
    <Browsable(True), EditorBrowsable(EditorBrowsableState.Always), DefaultValue(GetType(Size), "28, 20")>
    <Category("Caption Appearance"), Description("The size of the caption buttons.")>
    Public Property CaptionButtonSize As Size
        Get
            Return _CaptionButtonSize
        End Get
        Set(value As Size)
            If value.Width < 18 Then value.Width = 18
            If value.Height < 18 Then value.Height = 18
            _CaptionButtonSize = value
            Me.Invalidate(False)
        End Set
    End Property

    Private _CaptionButtonColor As Color = Color.Transparent
    ''' <summary>Gets or sets the gradient ending color of the caption buttons.</summary>
    <Browsable(True), EditorBrowsable(EditorBrowsableState.Always), DefaultValue(GetType(Color), "Transparent")>
    <Category("Caption Appearance"), Description("The gradient ending color of the caption buttons.")>
    Public Property CaptionButtonColor As Color
        Get
            Return _CaptionButtonColor
        End Get
        Set(value As Color)
            _CaptionButtonColor = value
            Me.Invalidate(False)
        End Set
    End Property

    Private _CaptionButtonendColor As Color = Color.Transparent
    ''' <summary>Gets or sets the gradient starting color of the caption buttons.</summary>
    <Browsable(True), EditorBrowsable(EditorBrowsableState.Always), DefaultValue(GetType(Color), "Transparent")>
    <Category("Caption Appearance"), Description("The gradient starting color of the caption buttons.")>
    Public Property CaptionButtonendColor As Color
        Get
            Return _CaptionButtonendColor
        End Get
        Set(value As Color)
            _CaptionButtonendColor = value
            Me.Invalidate(False)
        End Set
    End Property

    Private _CaptionButtonActiveColor As Color = Color.Blue
    ''' <summary>Gets or sets the color of an active caption button.</summary>
    <Browsable(True), EditorBrowsable(EditorBrowsableState.Always), DefaultValue(GetType(Color), "Blue")>
    <Category("Caption Appearance"), Description("The color of an active caption button. Buttons are considered active when the mouse is over top of a button.")>
    Public Property CaptionButtonActiveColor As Color
        Get
            Return _CaptionButtonActiveColor
        End Get
        Set(value As Color)
            _CaptionButtonActiveColor = value
            Me.Invalidate(False)
        End Set
    End Property

    ''' <summary>Gets or sets the color of the border around the caption buttons.</summary>
    <Browsable(True), EditorBrowsable(EditorBrowsableState.Always), DefaultValue(GetType(Color), "Transparent")>
    <Category("Caption Appearance"), Description("The color of the border around the caption buttons.")>
    Public Property CaptionButtonBorderColor As Color
        Get
            Return CaptionButtonBorderPen.Color
        End Get
        Set(value As Color)
            CaptionButtonBorderPen.Color = value
            Me.Invalidate(False)
        End Set
    End Property

    ''' <summary>Gets or sets the color of the symbals in the caption buttons.</summary>
    <Browsable(True), EditorBrowsable(EditorBrowsableState.Always), DefaultValue(GetType(Color), "White")>
    <Category("Caption Appearance"), Description("The color of the symbals in the caption buttons.")>
    Public Property CaptionButtonForeColor As Color
        Get
            Return CaptionButtonForeColorPen.Color
        End Get
        Set(value As Color)
            CaptionButtonForeColorPen.Color = value
            Me.Invalidate(False)
        End Set
    End Property

    ''' <summary>Gets or sets the background color of the client area.</summary>
    <Browsable(True), EditorBrowsable(EditorBrowsableState.Always), DefaultValue(GetType(SystemColors), "WhiteSmoke")>
    <Category("Appearance"), Description("The background color of the form.")>
    Public Overrides Property BackColor As Color
        Get
            Return MyBase.BackColor
        End Get
        Set(value As Color)
            MyBase.BackColor = value
            ClientBrush.Color = value 'set the class scoped ClientBrush.Color to the new color
            Me.Invalidate(False)
        End Set
    End Property

    'Here we Shadow the ControlBox property in the Form class. We Shadow this property just so that we can make our form repaint itself when this property has been changed.
    Public Shadows Property ControlBox As Boolean
        Get
            Return MyBase.ControlBox
        End Get
        Set(value As Boolean)
            MyBase.ControlBox = value
            Me.Invalidate(False) 'tell the form to repaint itself to reflect this change
        End Set
    End Property


    'This property can not be hidden from the user so, we just mark it as an obsolete property which explains to the user why. We also stop the user from changing it.
    <Obsolete("The DoubleBuffered property can not be changed and is always set to True in this class.")>
    Protected Overrides Property DoubleBuffered As Boolean
        Get
            Return MyBase.DoubleBuffered
        End Get
        Set(value As Boolean)
            value = True 'we are forcing this property to True if the user tries setting it to False
            MyBase.DoubleBuffered = value
        End Set
    End Property

    'We always want our Form to have no Border (Borderless), so we will just stop the user from seeing it in the designer or code view and stop them from changing it in any way too.
    <Browsable(False), EditorBrowsable(EditorBrowsableState.Never)>
    Public Shadows Property FormBorderStyle As FormBorderStyle
        Get
            Return MyBase.FormBorderStyle
        End Get
        Set(value As FormBorderStyle)
            value = FormBorderStyle.None
            MyBase.FormBorderStyle = FormBorderStyle.None
        End Set
    End Property

    'We always want this set to True in our class so, we will just mark it with an obsolete attribute which explains to the user why, and stop the user from changing it.
    <Obsolete("The ResizeRedraw property can not be changed and is always set to True in this class.")>
    <Browsable(False), EditorBrowsable(EditorBrowsableState.Never)>
    Public Shadows Property ResizeRedraw As Boolean
        Get
            Return MyBase.ResizeRedraw
        End Get
        Set(value As Boolean)
            value = True 'we are forcing this property to True if the user tries setting it to False
            MyBase.ResizeRedraw = value
        End Set
    End Property

    Protected Overrides Sub OnPaint(e As PaintEventArgs)
        MyBase.OnPaint(e)

        'using this smoothing mode will help make any diagnal lines that are drawn look smoother. However, it can negativly effect the appearnce of drawn text too.
        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias

        FrmBnds = New Rectangle(0, 0, Me.Width - 1, Me.Height - 1)
        ClntBnds = New Rectangle(0, _CaptionHeight, Me.Width - 1, Me.Height - _CaptionHeight)
        CapBnds = New Rectangle(0, 0, Me.Width, _CaptionHeight)
        Dim txtrect As Rectangle = New Rectangle(4, 4, Me.Width - 4, _CaptionHeight - 8)

        e.Graphics.FillRectangle(ClientBrush, ClntBnds) 'fills the background of the form using it's BackColor

        'if the Background image has been set, then draw the image. This just streches the image to fill the client area. You may want to implement
        'the code to draw it according to the BackgroundImageLayout property of the form.
        If Me.BackgroundImage IsNot Nothing Then e.Graphics.DrawImage(Me.BackgroundImage, 3, _CaptionHeight + 3, Me.Width - 3, Me.Height - _CaptionHeight - 6)

        'fills the caption area of the form using a LinearGradientBrush.
        bBlend.Factors = New Single() {0.5F, 0.6F, 0.7F, 0.7F, 0.6F, 0.5F, 0.4F, 0.3F, 0.2F, 0.1F, 0.0F}
        Using capbrush As New LinearGradientBrush(Point.Empty, New Point(0, _CaptionHeight), CaptionColor, _CaptionendColor)
            capbrush.Blend = bBlend
            e.Graphics.FillRectangle(capbrush, 0, 0, Me.Width, _CaptionHeight)
        End Using

        e.Graphics.DrawRectangle(BorderPen, FrmBnds) 'draws the border around the form
        e.Graphics.DrawRectangle(BorderPen, 1, _CaptionHeight, Me.Width - 3, Me.Height - _CaptionHeight - 2) 'draws a second border around the inside of the 'client' area

        'if the ShowIcon property is set to True, then we need to draw the Icon in the caption area and adjust the rectangle area for the caption text.
        If Me.ShowIcon Then
            IconBounds = New Rectangle(4, 4, _CaptionHeight - 8, _CaptionHeight - 8)
            e.Graphics.DrawIcon(Me.Icon, IconBounds)
            txtrect.X += (_CaptionHeight - 4) 'we also need to offset the location for the caption text to the right side of the Icon
            txtrect.Width -= (_CaptionHeight - 4) 'and narrow the width for the text area too.
        End If

        'if the ControlBox property is set to True, we need to draw the Minimize, Maximize, and Close buttons. We will also need to adjust the width for the caption text area too.
        If Me.ControlBox Then

            'calculate the Y position of the caption buttons according to what the user has set the CaptionButtonVerticalAlign property to.
            Dim CapBtnTop As Integer = (_CaptionHeight \ 2) - (CaptionButtonSize.Height \ 2)
            If Me.CaptionButtonVerticalAlign = VerticalAlignment.Top Then CapBtnTop = 0
            If Me.CaptionButtonVerticalAlign = VerticalAlignment.Bottom Then CapBtnTop = _CaptionHeight - CaptionButtonSize.Height - 4

            'calculate the current bounding rectangle area of the three caption buttons
            CloseBtnBounds = New Rectangle(Me.Width - _CaptionButtonSize.Width - 4, CapBtnTop, _CaptionButtonSize.Width, _CaptionButtonSize.Height)
            MaximizeBtnBounds = New Rectangle(CloseBtnBounds.Left - _CaptionButtonSize.Width, CapBtnTop, _CaptionButtonSize.Width, _CaptionButtonSize.Height)
            MinimizeBtnBounds = New Rectangle(MaximizeBtnBounds.Left - _CaptionButtonSize.Width, CapBtnTop, _CaptionButtonSize.Width, _CaptionButtonSize.Height)

            bBlend.Factors = New Single() {0.4F, 0.5F, 0.6F, 0.6F, 0.5F, 0.4F, 0.3F, 0.2F, 0.1F, 0.0F, 0.0F}
            Using btnbrush As New LinearGradientBrush(New Point(0, CloseBtnBounds.Top), New Point(0, CloseBtnBounds.Bottom), _CaptionButtonColor, _CaptionButtonendColor)
                btnbrush.Blend = bBlend

                'draw the Close button and then draw the symbal in it
                DrawCaptionButton(e.Graphics, btnbrush, MouseOnClose, CloseBtnBounds)
                e.Graphics.DrawLine(CaptionButtonForeColorPen, CloseBtnBounds.X + 6, CloseBtnBounds.Y + 6, CloseBtnBounds.Right - 6, CloseBtnBounds.Bottom - 6)
                e.Graphics.DrawLine(CaptionButtonForeColorPen, CloseBtnBounds.Right - 6, CloseBtnBounds.Y + 6, CloseBtnBounds.X + 6, CloseBtnBounds.Bottom - 6)

                'draw the Maximize button and then draw the symbal in it
                DrawCaptionButton(e.Graphics, btnbrush, MouseOnMax, MaximizeBtnBounds)
                e.Graphics.DrawRectangle(CaptionButtonForeColorPen, MaximizeBtnBounds.X + 6, MaximizeBtnBounds.Y + 6, MaximizeBtnBounds.Width - 12, MaximizeBtnBounds.Height - 12)

                'draw the Minimize button and then draw the symbal in it
                DrawCaptionButton(e.Graphics, btnbrush, MouseOnMin, MinimizeBtnBounds)
                e.Graphics.DrawLine(CaptionButtonForeColorPen, MinimizeBtnBounds.Left + 6, MinimizeBtnBounds.Bottom - 6, MinimizeBtnBounds.Right - 6, MinimizeBtnBounds.Bottom - 6)
            End Using

            'if the caption buttons are drawn, then we need to adjust the width for the caption text so that it does not overlap onto the caption buttons
            txtrect.Width = MinimizeBtnBounds.Left - txtrect.Left - 8
        End If

        'finally, draw the caption text inside the calculated (txtrect) rectangle area. If the (textrect) is to narrow for the text, it will be ellipsed
        TextRenderer.DrawText(e.Graphics, Me.Text, _CaptionTextFont, txtrect, _CaptionTextColor, TextFormatFlags.VerticalCenter Or TextFormatFlags.Left Or TextFormatFlags.SingleLine Or TextFormatFlags.EndEllipsis)
    End Sub

    'this sub is called from the Paint event to draw each caption buttom. By calling this, it sub prevents us from repeating this code in the Paint event for each button.
    Private Sub DrawCaptionButton(g As Graphics, lgbrsh As LinearGradientBrush, DrawActive As Boolean, btnbnds As Rectangle)
        If DrawActive Then
            lgbrsh.LinearColors = {_CaptionButtonActiveColor, _CaptionButtonendColor}
        Else
            lgbrsh.LinearColors = {_CaptionButtonColor, _CaptionButtonendColor}
        End If
        g.FillRectangle(lgbrsh, btnbnds)
        g.DrawRectangle(CaptionButtonBorderPen, btnbnds)
    End Sub

    'this is needed to make the form redraw itself after the text property is changed
    Protected Overrides Sub OnTextChanged(e As EventArgs)
        MyBase.OnTextChanged(e)
        Me.Invalidate(False) 'tell the form to repaint itself but, not its child controls (ones the user added to it in the designer window)
    End Sub


    Protected Overrides Sub WndProc(ByRef m As Message)
        If m.Msg = WM_NCMOUSEMOVE Then 'the WM_NCMOUSEMOVE message is sent to the form/window when the mouse is moving over a non-client area of the form.
            'get the mouse position relative to the form location and reset the boolean variables used to tell the Paint event how to draw the buttons, active or not active.
            Dim loc As Point = Me.PointToClient(MousePosition)
            MouseOnClose = CloseBtnBounds.Contains(loc)
            MouseOnMax = Me.MaximizeBox AndAlso MaximizeBtnBounds.Contains(loc)
            MouseOnMin = Me.MinimizeBox AndAlso MinimizeBtnBounds.Contains(loc)
            MouseOnIcon = Me.ShowIcon AndAlso IconBounds.Contains(loc)
            Me.Invalidate(False)

        ElseIf m.Msg = WM_NCLBUTTONDOWN Then 'The WM_NCLBUTTONDOWN message is sent to the form when the left mouse button is pressed down in a non-client area of the form.
            If Me.ControlBox Then
                If m.WParam.ToInt32 = HTCLOSE Then 'if we told it the mouse was on our Close button, then close the form
                    Me.Close()
                    m.Result = CType(0, IntPtr)
                    Return
                ElseIf m.WParam.ToInt32 = HTMINBUTTON Then 'if we told it the mouse was on our Minimize button, then minimize the form
                    Me.WindowState = FormWindowState.Minimized
                    m.Result = CType(0, IntPtr)
                    Return
                ElseIf m.WParam.ToInt32 = HTMAXBUTTON Then 'if we told it the mouse was on our Maximize button, then maximize or restore the form depending on it's current state
                    If Me.WindowState = FormWindowState.Normal Then
                        Me.WindowState = FormWindowState.Maximized
                    Else
                        Me.WindowState = FormWindowState.Normal
                    End If
                    m.Result = CType(0, IntPtr)
                    Return
                ElseIf m.WParam.ToInt32 = HTSYSMENU Then 'if we told it the mouse was on our 'system menu' Icon, then show our custom 'system menu'
                    Dim sl As Point = Me.PointToScreen(New Point(3, Me.CaptionHeight))
                    SysMenu.Show(sl)
                    m.Result = CType(0, IntPtr)
                    Return
                End If
            End If

        ElseIf m.Msg = WM_NCRBUTTONUP Then 'The WM_NCRBUTTONUP message is sent to the form when the right mouse button is released in a non-client area of the form.
            'if the mouse is in the caption area but, not over a button or the icon, then show our custom 'System Menu' when the right button is released
            If Not MouseOnIcon AndAlso Not MouseOnMin AndAlso Not MouseOnMax AndAlso Not MouseOnClose Then
                SysMenu.Show(MousePosition)
                m.Result = CType(0, IntPtr)
                Return
            End If

        ElseIf m.Msg = WM_NCHITTEST Then 'this message is sent to the form when the mouse is moving anywhere over the form. We will use this to tell the form what part of the non-cliect area the mouse is over so it can act appropriatly.
            Dim loc As Point = Me.PointToClient(MousePosition)
            Dim bTop As Boolean = (loc.Y < 4 AndAlso Me.CanResize)
            Dim bLeft As Boolean = (loc.X < 4 AndAlso Me.CanResize)
            Dim bRight As Boolean = (loc.X > Me.ClientSize.Width - 4 AndAlso Me.CanResize)
            Dim bBottom As Boolean = (loc.Y > Me.ClientSize.Height - 4 AndAlso Me.CanResize)
            Dim bClose As Boolean = (CloseBtnBounds.Contains(loc) AndAlso Me.ControlBox)
            Dim bMin As Boolean = (MinimizeBtnBounds.Contains(loc) AndAlso Me.ControlBox AndAlso Me.MinimizeBox)
            Dim bMax As Boolean = (MaximizeBtnBounds.Contains(loc) AndAlso Me.ControlBox AndAlso Me.MaximizeBox)
            Dim bCaption As Boolean = (loc.Y > 3 AndAlso loc.Y < _CaptionHeight AndAlso loc.X < Me.Width - 3)
            Dim bClient As Boolean = ClntBnds.Contains(loc)
            Dim bIcon As Boolean = (Me.ShowIcon AndAlso IconBounds.Contains(loc))

            If bClose Then 'if the mouse is over our Close button
                m.Result = CType(HTCLOSE, IntPtr) 'return HTCLOSE result to tell our form that the mouse is over our close button
                If Not Me.DesignMode Then MouseTrackingTimer.Start() 'if the mouse enters the Close button, start the MouseTrackingTimer
                Return
            ElseIf bMin Then
                m.Result = CType(HTMINBUTTON, IntPtr)
                If Not Me.DesignMode Then MouseTrackingTimer.Start() 'if the mouse enters the Minimize button, start the MouseTrackingTimer
                Return
            ElseIf bMax Then
                m.Result = CType(HTMAXBUTTON, IntPtr)
                If Not Me.DesignMode Then MouseTrackingTimer.Start() 'if the mouse enters the Maximize button, start the MouseTrackingTimer
                Return
            ElseIf bIcon Then 'if the mouse is over the Icon (System Menu)
                m.Result = CType(HTSYSMENU, IntPtr) 'return the HTSYSMENU. This lets us detect it being clicked And show our custom 'system menu'
                Return
            ElseIf bCaption Then 'if the mouse is over the caption area
                m.Result = CType(HTCAPTION, IntPtr) 'return HTCAPTION. This will allow use to click and drag the form to move it
                Return
            ElseIf bBottom AndAlso bLeft Then 'if the mouse is at the bottom left corner of our form
                m.Result = CType(HTBOTTOMLEFT, IntPtr) 'return HTBOTTOMLEFT. This will change the cursur to a sizing cursor and allow us to resize the form
                Return
            ElseIf bBottom AndAlso bRight Then
                m.Result = CType(HTBOTTOMRIGHT, IntPtr)
                Return
            ElseIf bTop AndAlso bLeft Then
                m.Result = CType(HTTOPLEFT, IntPtr)
                Return
            ElseIf bTop AndAlso bRight Then
                m.Result = CType(HTTOPRIGHT, IntPtr)
                Return
            ElseIf bLeft Then
                m.Result = CType(HTLEFT, IntPtr)
                Return
            ElseIf bTop Then
                m.Result = CType(HTTOP, IntPtr)
                Return
            ElseIf bRight Then
                m.Result = CType(HTRIGHT, IntPtr)
                Return
            ElseIf bBottom Then
                m.Result = CType(HTBOTTOM, IntPtr)
                Return
            ElseIf bClient Then
                m.Result = CType(HTCLIENT, IntPtr)
                Return
            End If
        End If
        MyBase.WndProc(m)
    End Sub

    Private Sub SysMenu_Opening(sender As Object, e As CancelEventArgs)
        If Me.WindowState = FormWindowState.Maximized Then
            MaximizeItem.Text = "Restore"
        Else
            MaximizeItem.Text = "Maximize"
        End If
    End Sub

    Private Sub SysMenu_ItemClicked(sender As Object, e As ToolStripItemClickedEventArgs)
       SysMenu.Hide()
       If e.ClickedItem Is CloseItem Then
            Me.Close()
        ElseIf e.ClickedItem Is MinimizeItem Then
            Me.WindowState = FormWindowState.Minimized
        ElseIf e.ClickedItem Is MaximizeItem Then
            If Me.WindowState = FormWindowState.Normal Then
                Me.WindowState = FormWindowState.Maximized
            Else
                Me.WindowState = FormWindowState.Normal
            End If
        End If
    End Sub
End Class






Inheriting From Our Custom Form Class

We now need to close the class library project that we just built and create another new (Windows Form) project. This project is the project that we will make the forms of, inherit from our custom form class. So, start by creating a new Windows Form project and name it whatever you want.

The first thing we will need to do is add a reference to the (IronWorks.dll) file we created. On the VS menu click (Project -> Add Reference). When the (Reference Manager) window opens, select the (Browse) tab. Now click the (Browse...) button at the bottom of the window. Browse your way to the Debug folder inside your IronWorks project folder. It should be located in your Documents / Visual Studio 20XX folder. For example, mine is here...

C:\Users\YourUserName\Documents\Visual Studio 2015\Projects\IronWorks\IronWorks\bin\Debug

Once you locate the (IronWorks.dll) file in the Debug folder, double click on it to open it. Then just press the (Ok) button at the bottom of the (Reference Manager) window to add the reference.

Now we can make the main form (Form1) inherit from the (CustomForm) class in the IronWorks.dll. So, open the (Solution Explorer) tab and click the little icon at the top to (show all files) as seen in the image below.

Posted Image


Then open the "Form1.vb" node and double click on the "Form1.Designer.vb" file to open it as shown below.

Posted Image


The code in the "Form1.Designer.vb" file will look like this...
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Partial Class Form1
    Inherits System.Windows.Forms.Form

    'Form overrides dispose to clean up the component list.
    <System.Diagnostics.DebuggerNonUserCode()> _
    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        Try
            If disposing AndAlso components IsNot Nothing Then
                components.Dispose()
            End If
        Finally
            MyBase.Dispose(disposing)
        End Try
    End Sub

    'Required by the Windows Form Designer
    Private components As System.ComponentModel.IContainer

    'NOTE: The following procedure is required by the Windows Form Designer
    'It can be modified using the Windows Form Designer.  
    'Do not modify it using the code editor.
    <System.Diagnostics.DebuggerStepThrough()> _
    Private Sub InitializeComponent()
        components = New System.ComponentModel.Container()
        Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
        Me.Text = "Form1"
    End Sub

End Class


We are only interested in the third line...
Inherits System.Windows.Forms.Form

We want to change it to this...
Inherits IronWorks.CustomForm

Now you can go back to the Form's Designer window and presto, your form in the designer window will look like the image below. Look at the Properties tab for the form and you will see all the custom properties we added. Try changing some of them to see how it works. Run the application and see how it functions.

You should be aware though, we did not implement and design everything into this form class for handling some of the properties. For example, the (RightToLeft) or (RightToLeftLayout) property would need to be handles by us to draw the caption area in reverse. So, just be aware that there is still more work that can be done.

If you want to add any secondary forms to the project and want them to also inherit from the custom form class, you can just add another Form and follow the same steps as above to make that form inherit from the custom form class. For example, if you add (Form2), then just do those steps for the "Form2.Designer.vb" file instead of the "Form1.Designer.vb" file.

Posted Image


Optional.. Create Your Own Visual Studio Project Template

Now that you can do this, and if you wanted to, you could export the new Windows Form project as a Visual Studio Project Template and make your own Custom Form Template. A Project Template is what you see in the list of projects/applications when you open Visual Studio and choose to create a new project. You know, like (Windows Form Application), (WPF Application), (Class Library), and so on. Those are templates.

After creating a Project Template, it will/can be listed along with the rest of the templates as seen in the image below. This makes it easy for you or others to just open your custom form Template and be ready to go. There would be no messing around with the Form.Designer files to do.

If you are interested in doing that, then take a look at the links below.

How to: Create Project Templates
How to: Create Item Templates

Posted Image


Updated 5/1/2018 - Fixed bug with caption buttons

Is This A Good Question/Topic? 0
  • +

Replies To: Creating A Custom Form Class Library

#2 Simonetos The Greek   User is offline

  • New D.I.C Head

Reputation: 2
  • View blog
  • Posts: 28
  • Joined: 30-March 18

Posted 28 April 2018 - 02:19 AM

Dear friend IronRazer, first of all and for one more time, a big thank you for this great tutorial!!!

I would like to ask you, if it's possible to add a kind of custom shadow outside off the from's area. Something like this...

Posted Image

I did this based to an unfinished tutorial that I found somewhere on the web. It is opening a second borderless form, with black backcolor and 15% opacity, which "follows" every form on mousemove and resize actions. But it has many issues on using.

Is there any other way to do this with a rectangle for example? A rectangle which will be drawn out of the form's area?

Or, do you have any working example, tutorial or a simple idea how can we do this with a second form which will work as shadow?

This post has been edited by Simonetos The Greek: 28 April 2018 - 02:22 AM

Was This Post Helpful? 0
  • +
  • -

#3 Simonetos The Greek   User is offline

  • New D.I.C Head

Reputation: 2
  • View blog
  • Posts: 28
  • Joined: 30-March 18

Posted 28 April 2018 - 02:49 AM

And something else... About DoubleBuffered property. Asking here and there, I found a solution to exclude it from the PropertyGrid so developer can't see it at all. Here is the solution to my question which answered by user TnTinMn in stackoverflow.com...

Quote

TnTinMn: You could create a custom component designer for your Form, but that is a daunting task to just recreate the functionality of the inaccessible System.Windows.Forms.Design.FormDocumentDesigner. The simpler way is use the Form's Site property to access the designer services.

In this case, you need to override the ITypeDescriptorFilterService service of the designer host. This service is used by the designer for all type discovery/filtering operations and is not limited to a specific component.

The first step is to create a class that implements ITypeDescriptorFilterService. The following is one such implementation. It is a generic implementation that allows it to filter components of the specified type and takes list of property names that you want to exclude from the PropertyGrid display. The final item it requires is a reference to the existing service used by the designer host.
Friend Class FilterService(Of T) : Implements ITypeDescriptorFilterService
    Private namesOfPropertiesToRemove As String()

    Public Sub New(baseService As ITypeDescriptorFilterService, ParamArray NamesOfPropertiesToRemove As String())
        Me.BaseService = baseService
        Me.namesOfPropertiesToRemove = NamesOfPropertiesToRemove
    End Sub

    Public ReadOnly Property BaseService As ITypeDescriptorFilterService
    Public Function FilterAttributes(component As IComponent, attributes As IDictionary) As Boolean Implements ITypeDescriptorFilterService.FilterAttributes
        Return BaseService.FilterAttributes(component, attributes)
    End Function

    Public Function FilterEvents(component As IComponent, events As IDictionary) As Boolean Implements ITypeDescriptorFilterService.FilterEvents
        Return BaseService.FilterEvents(component, events)
    End Function

    Public Function FilterProperties(component As IComponent, properties As IDictionary) As Boolean Implements ITypeDescriptorFilterService.FilterProperties
        ' ref: ITypeDescriptorFilterService Interface: https://msdn.microsoft.com/en-us/library/system.componentmodel.design.itypedescriptorfilterservice(v=vs.110).aspx
        ' 
        ' The return value of FilterProperties determines if this set of properties is fixed.
        ' If this method returns true, the TypeDescriptor for this component can cache the 
        ' results. This cache is maintained until either the component is garbage collected or the Refresh method of the type descriptor is called.

        ' allow other filters 1st chance to modify the properties collection
        Dim ret As Boolean = BaseService.FilterProperties(component, properties)

        ' only remove properties if component is of type T
        If TypeOf component Is T AndAlso Not (properties.IsFixedSize Or properties.IsReadOnly) Then
            For Each propName As String In namesOfPropertiesToRemove
                ' If the IDictionary object does not contain an element with the specified key, 
                ' the IDictionary remains unchanged. No exception is thrown.
                properties.Remove(propName)
            Next
        End If
        Return ret
    End Function
End Class
Example Usage in Form:
Imports System.ComponentModel
Imports System.ComponentModel.Design

Public Class TestForm : Inherits Form
    Private host As IDesignerHost
    Private altTypeDescriptorProvider As FilterService(Of TestForm)

    ' spelling and character casing of removedPropertyNames is critical
    ' it is a case-sensative lookup
    Private Shared removedPropertyNames As String() = {"DoubleBuffered"}

    Public Overrides Property Site As ISite
        Get
            Return MyBase.Site
        End Get
        Set(value As ISite)
            If host IsNot Nothing Then
                UnwireDesignerCode()
            End If

            MyBase.Site = value
            If value IsNot Nothing Then
                host = CType(Site.GetService(GetType(IDesignerHost)), IDesignerHost)
                If host IsNot Nothing Then
                    If host.Loading Then
                        AddHandler host.LoadComplete, AddressOf HostLoaded
                    Else
                        WireUpDesignerCode()
                    End If
                End If
            End If
        End Set
    End Property

    Private Sub HostLoaded(sender As Object, e As EventArgs)
        RemoveHandler host.LoadComplete, AddressOf HostLoaded
        WireUpDesignerCode()
    End Sub

    Private Sub WireUpDesignerCode()
        AddFilter()
    End Sub

    Private Sub UnwireDesignerCode()
        If host IsNot Nothing Then
            RemoveFilter()
        End If
        host = Nothing
    End Sub

    Private Sub AddFilter()
        Dim baseFilter As ITypeDescriptorFilterService = CType(host.GetService(GetType(ITypeDescriptorFilterService)), ITypeDescriptorFilterService)
        If baseFilter IsNot Nothing Then
            ' remove existing filter service
            host.RemoveService(GetType(ITypeDescriptorFilterService))
            ' create our replacement service and add it to the host's services
            altTypeDescriptorProvider = New FilterService(Of TestForm)(baseFilter, removedPropertyNames)
            host.AddService(GetType(ITypeDescriptorFilterService), altTypeDescriptorProvider)
            TypeDescriptor.Refresh(Me.GetType) ' force a type description rescan 
        End If
    End Sub

    Private Sub RemoveFilter()
        If altTypeDescriptorProvider IsNot Nothing Then
            host.RemoveService(GetType(ITypeDescriptorFilterService))
            host.AddService(GetType(ITypeDescriptorFilterService), altTypeDescriptorProvider.BaseService)
            altTypeDescriptorProvider = Nothing
        End If
    End Sub
End Class
Now when you create a form that inherits from TestForm, the DoubleBuffered property will be excluded from the PropertyGrid display.

Was This Post Helpful? 0
  • +
  • -

#4 IronRazer   User is offline

  • Custom Control Freak
  • member icon

Reputation: 1500
  • View blog
  • Posts: 3,799
  • Joined: 01-February 13

Posted 28 April 2018 - 04:51 AM

Yes, I should have worded the part about the DoubleBuffered property a little different. I should have said that you can not hide the DoubleBuffered property using just the attributes as I was doing. I was just trying to keep the example a little more on the basic side and let others expand on it themselves.

However, using the code you have shown, you will still need/want to override the property and mark it with an obsolete attribute and stop it from being changed as I did. This is because the code you have shown will only hide the property in the Properties Tab but, the user will still have full access to it in the code view. Try going to the code view and typing "DoubleBuffered". It will still pop up in the Inellisense window and the user will still be able to change it. 8)

##############################

About the shadow, I will have to try a few things and see if one of them work. I agree that using a second form for a shadow is not a great solution.
Was This Post Helpful? 0
  • +
  • -

#5 IronRazer   User is offline

  • Custom Control Freak
  • member icon

Reputation: 1500
  • View blog
  • Posts: 3,799
  • Joined: 01-February 13

Posted 28 April 2018 - 04:35 PM

About the best way I can see to add a shadow is to add the CS_DROPSHADOW class style to the Form/Window class styles when it is being created. You can do that using the CreateParams override in the CustomForm class. It just requires adding the below code.
    Private Const CS_DROPSHADOW As Integer = &H20000

    Protected Overrides ReadOnly Property CreateParams As CreateParams
        Get
            Dim cp As CreateParams = MyBase.CreateParams
            If Not Me.DesignMode Then cp.ClassStyle = (cp.ClassStyle Or CS_DROPSHADOW) 'if we are not in the design mode, we add the CS_DROPSHADOW class style to the window's class styles
            Return cp
        End Get
    End Property


Was This Post Helpful? 0
  • +
  • -

#6 Simonetos The Greek   User is offline

  • New D.I.C Head

Reputation: 2
  • View blog
  • Posts: 28
  • Joined: 30-March 18

Posted 29 April 2018 - 02:28 AM

View PostIronRazer, on 28 April 2018 - 02:51 PM, said:

Yes, I should have worded the part about the DoubleBuffered property a little different. I should have said that you can not hide the DoubleBuffered property using just the attributes as I was doing. I was just trying to keep the example a little more on the basic side and let others expand on it themselves.

However, using the code you have shown, you will still need/want to override the property and mark it with an obsolete attribute and stop it from being changed as I did. This is because the code you have shown will only hide the property in the Properties Tab but, the user will still have full access to it in the code view. Try going to the code view and typing "DoubleBuffered". It will still pop up in the Inellisense window and the user will still be able to change it. 8)

##############################

About the shadow, I will have to try a few things and see if one of them work. I agree that using a second form for a shadow is not a great solution.

I am not trying to fix something, you did really great job with the tutorial, I just wrote this as something additional. And as you said, I still had to override the property and mark it with an obsolete attribute to stop it from being changed!!!

View PostIronRazer, on 29 April 2018 - 02:35 AM, said:

About the best way I can see to add a shadow is to add the CS_DROPSHADOW class style to the Form/Window class styles when it is being created. You can do that using the CreateParams override in the CustomForm class. It just requires adding the below code.
    Private Const CS_DROPSHADOW As Integer = &H20000

    Protected Overrides ReadOnly Property CreateParams As CreateParams
        Get
            Dim cp As CreateParams = MyBase.CreateParams
            If Not Me.DesignMode Then cp.ClassStyle = (cp.ClassStyle Or CS_DROPSHADOW) 'if we are not in the design mode, we add the CS_DROPSHADOW class style to the window's class styles
            Return cp
        End Get
    End Property


Yes I have already tried this... It just looks a bit, old fashion(?). This why I am trying for something else. By the way I have send you a message about it!!!

This post has been edited by Simonetos The Greek: 29 April 2018 - 02:29 AM

Was This Post Helpful? 0
  • +
  • -

#7 bambi1   User is offline

  • New D.I.C Head

Reputation: 4
  • View blog
  • Posts: 34
  • Joined: 09-March 12

Posted 30 April 2018 - 05:34 PM

mod: removed giant quote.

I CAN NOT GET IT TO WORK IT COMPLAINS AT CHANGING THE THIRD THE INHERITS ONE>|

This post has been edited by modi123_1: 30 April 2018 - 05:38 PM
Reason for edit:: Removed giant quote

Was This Post Helpful? 0
  • +
  • -

#8 IronRazer   User is offline

  • Custom Control Freak
  • member icon

Reputation: 1500
  • View blog
  • Posts: 3,799
  • Joined: 01-February 13

Posted 01 May 2018 - 09:54 AM

@ bambi1,

I don't seem to have any problem making more than 3 Forms inherit from the "IronWorks.CustomForm" class. Just for a quick test, I have made 10 different Forms inherit from the custom class in the same way I have explained to do in the tutorial.

So, double check that you have followed the instructions correctly. If you are sure you have, then you will need to explain more about what kind of error you are getting. What is the error message, what line do you get it on, and any other relative information..
Was This Post Helpful? 0
  • +
  • -

#9 bambi1   User is offline

  • New D.I.C Head

Reputation: 4
  • View blog
  • Posts: 34
  • Joined: 09-March 12

Posted 01 May 2018 - 01:19 PM

View PostIronRazer, on 01 May 2018 - 04:54 PM, said:

@ bambi1,

I don't seem to have any problem making more than 3 Forms inherit from the "IronWorks.CustomForm" class. Just for a quick test, I have made 10 different Forms inherit from the custom class in the same way I have explained to do in the tutorial.

So, double check that you have followed the instructions correctly. If you are sure you have, then you will need to explain more about what kind of error you are getting. What is the error message, what line do you get it on, and any other relative information..


Thanks a lot IronRazer, MY bad, I got it going good now. And I understand it a lot more.
Thanks again.
Was This Post Helpful? 0
  • +
  • -

#10 IronRazer   User is offline

  • Custom Control Freak
  • member icon

Reputation: 1500
  • View blog
  • Posts: 3,799
  • Joined: 01-February 13

Posted 01 May 2018 - 01:31 PM

Glad you have figured it out. 8)

As a side note, I just updated the code to fix a bug with the caption buttons. You may want to re-copy the code into your CustomForm class to fix that.
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1