65.9K
CodeProject is changing. Read more.
Home

VS.NET MDI Navigation

starIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIconemptyStarIcon

1.06/5 (14 votes)

Sep 12, 2004

1 min read

viewsIcon

92547

downloadIcon

471

Visual Studio .NET MDI navigation bar.

Introduction

This article is for those of you who want to use the VS.NET MDI style navigation bar.

Background

This control was made when I was thinking how I could make a good control in VB.NET and not the overrated C#.

Using the code

OK, here's the first part of the control: the VSMDINav class.

Public Class VSMDINav
    Inherits System.Windows.Forms.UserControl        
    Private Const m_BottomPadd = 5
    Private Const m_Height = 25
    Private Const m_DefaultWidth = 100
    Private Const m_CellPadding = 12
    Private m_OffColor As Color = SystemColors.Window
    Private m_OnColor As Color = SystemColors.Control
    Dim m_Panel As New Panel()
    Dim m_Navbuttons As New Panel()
    Dim m_NavBackbutton As New Panel()
    Dim m_NavNextbutton As New Panel()
    Dim m_NavClosebuttons As New Panel()
    Dim m_AutoHide As Boolean = True
    Dim m_AutoActivate As Boolean = True
    Dim m_ShowIcon As Boolean = False

OK, most of those variables are pretty much self explanatory. Now, here are the public properties.

 Public Property ShowIcon() As Boolean
        Get
            Return m_ShowIcon
        End Get
        Set(ByVal value As Boolean)
            m_ShowIcon = value
            SetShowIcon()
        End Set
    End Property

    Public Property AutoActivate() As Boolean
        Get
            Return m_AutoActivate
        End Get
        Set(ByVal value As Boolean)
            m_AutoActivate = value
            SetAutoActive()
        End Set
    End Property

    Public Property AutoHide() As Boolean
        Get
            Return m_AutoHide
        End Get
        Set(ByVal value As Boolean)
            m_AutoHide = value
        End Set
    End Property

    Public Property Tabhost() As Panel
        Get
            Return m_Panel
        End Get
        Set(ByVal value As Panel)
            m_Panel = value
        End Set
    End Property

Now begins the main coding. Here are the SetAutoActive() and the SetShowIcon() Subs.

    Private Sub SetAutoActive()
        Dim cPage As VSMDINavPage
        For Each cPage In m_Panel.Controls
            cPage.ShowIcon = m_ShowIcon
        Next
    End Sub

    Private Sub SetShowIcon()
        Dim cPage As VSMDINavPage
        For Each cPage In m_Panel.Controls
            cPage.AutoActivate = m_AutoActivate
        Next
    End Sub

Then this small piece of code to handle any resizing of the control:

Private Sub VSMDINav_Resize(ByVal sender As Object, _
       ByVal e As System.EventArgs) Handles MyBase.Resize
    Me.Height = m_Height
End Sub

Next we add the AddPage Sub, which basically adds more pages to the nav bar control.

Public Sub AddPage(ByVal sCaption As String, ByVal mForm As Form)
        'add a page

        Dim cPage As New VSMDINavPage()
        Dim PageRect As Rectangle = getlabelRect(sCaption)
        Debug.WriteLine("adding rect: " & PageRect.Left)
        cPage.Text = sCaption
        cPage.AttachedForm = mForm
        cPage.OffColor = m_OffColor
        cPage.OnColor = m_OnColor
        cPage.Left = PageRect.Left
        cPage.Top = PageRect.Top
        cPage.Width = PageRect.Width
        cPage.Height = PageRect.Height
        cPage.Host = Me
        cPage.BackColor = m_OffColor
        cPage.AutoActivate = m_AutoActivate
        cPage.ShowIcon = m_ShowIcon
        AddHandler mForm.Activated, AddressOf MDIActive
        AddHandler mForm.Closing, AddressOf MDIClose
        m_Panel.Controls.Add(cPage)
        cPage.SendToBack()
        m_Panel.Width = cPage.Left + cPage.Width
        checkAutohide()
    End Sub

Now for the reverse, removing pages:

 Public Sub RemovePage(ByVal mForm As Form)
        'remove all pages that have mForm as an AttachedForm


        Dim cPage As VSMDINavPage
        Dim cOldPage As VSMDINavPage
        Dim I As Integer

        For I = m_Panel.Controls.Count - 1 To 0 Step -1
            If TypeOf m_Panel.Controls(I) Is VSMDINavPage Then
                cPage = m_Panel.Controls(I)
                If cPage.AttachedForm.Handle.ToString = mForm.Handle.ToString Then
                    m_Panel.Controls.Remove(cPage)
                End If
            End If
        Next

        'move the pages into place

        If m_Panel.Controls.Count > 0 Then
            For Each cPage In m_Panel.Controls
                If cOldPage Is Nothing Then
                    cPage.Left = 0
                Else
                    If cPage.Left > 0 Then
                        cPage.Left = cOldPage.Left + cOldPage.Width
                    End If
                End If

                cOldPage = cPage
            Next
        End If

        checkAutohide()
    End Sub

And finally, what to do if a page is selected:

Public Sub SelectPage(ByVal mForm As Form)
        Dim cPage As VSMDINavPage
        Dim cOldPage As VSMDINavPage
        Dim I As Integer

        If m_Panel.Controls.Count > 0 Then
            For Each cPage In m_Panel.Controls
                cPage.Selected = False
            Next

            For Each cPage In m_Panel.Controls
                If cPage.AttachedForm.Handle.ToString = mForm.Handle.ToString Then
                    cPage.Selected = True
                    ForceRepaint(cPage)
                    Exit For
                End If
            Next
        End If
    End Sub

Now, we must add the MDIActivate and MDIClose, and 'check for updates' Subs:

Private Sub MDIActive(ByVal sender As Object, ByVal e As System.EventArgs)
        SelectPage(sender)
    End Sub

    Private Sub MDIClose(ByVal sender As Object, _
          ByVal e As System.ComponentModel.CancelEventArgs)
        RemovePage(sender)
    End Sub

    Private Sub checkAutohide()
        If m_AutoHide = False Then Exit Sub
        If m_Panel.Controls.Count = 0 Then
            Me.Visible = False
        Else
            Me.Visible = True
        End If
    End Sub

Finally, the finishing touches, repainting and getting the label titles.

Private Function getlabelRect(ByVal stext As String) As Rectangle
        Dim cPage As VSMDINavPage
        Dim Rect As Rectangle
        If m_Panel.Controls.Count = 0 Then
            Rect = New Rectangle(0, 0, GetPageWidth(stext), m_Panel.Height)
        Else
            For Each cPage In m_Panel.Controls
                Rect = New Rectangle(cPage.Left + cPage.Width, _
                       cPage.Top, GetPageWidth(stext), m_Panel.Height)
            Next
        End If
        getlabelRect = Rect
    End Function

    Private Function GetPageWidth(ByVal stext As String) As Integer
        Dim G As Graphics
        Dim sSizeF As SizeF
        If stext.Length = 0 Then
            GetPageWidth = m_DefaultWidth
            Exit Function
        End If

        G = Me.CreateGraphics
        sSizeF = G.MeasureString(stext, Me.Font)
        GetPageWidth = sSizeF.Width + m_CellPadding
        G.Dispose()
    End Function


    Public Function ForceRepaint(ByVal selPage As VSMDINavPage)
        Dim cPage As VSMDINavPage

        Me.Invalidate()

        For Each cPage In m_Panel.Controls
            cPage.Selected = False
        Next

        For Each cPage In m_Panel.Controls
            cPage.Invalidate()
        Next

        selPage.Selected = True
    End Function

Here, we will add all the important paint stuff:

 Private Sub Panel1_Paint(ByVal sender As System.Object, _
               ByVal e As System.Windows.Forms.PaintEventArgs)
        'paint the nav buttons

        Dim BottomRect As New Rectangle(0, Height - m_BottomPadd, Width, Height)
        'Paint the backing

        e.Graphics.FillRectangle(New SolidBrush(m_OffColor), e.ClipRectangle)
        e.Graphics.FillRectangle(New SolidBrush(m_OnColor), BottomRect)
    End Sub

    Private Sub VSMDINav_Paint(ByVal sender As Object, _
            ByVal e As System.Windows.Forms.PaintEventArgs) _
            Handles MyBase.Paint
        Dim BottomRect As New Rectangle(0, Height - m_BottomPadd, Width, Height)
        e.Graphics.FillRectangle(New SolidBrush(m_OffColor), e.ClipRectangle)
        e.Graphics.FillRectangle(New SolidBrush(m_OnColor), BottomRect)
    End Sub

Now, to close it up:

Private Sub VSMDINav_Load(ByVal sender As System.Object, _
         ByVal e As System.EventArgs) Handles MyBase.Load

    End Sub
End Class

Now that you know all that, just bundle this all in one last class... da da da daaaa... the VSMDINavPage class.

Public Class VSMDINavPage
    Inherits UserControl

#Region " Local Storage and Public Declares "


    Private m_OffColor As Color = SystemColors.Window
    Private m_OnColor As Color = SystemColors.Control
    Private m_AttachedForm As Form = Nothing
    Private m_Selected As Boolean = False
    Private m_Host As VSMDINav
    Dim m_ShowIcon As Boolean = False
    Dim m_AutoActivate As Boolean = False

#End Region

#Region " Property Declares "

    Public Property AutoActivate() As Boolean
        Get
            Return m_AutoActivate
        End Get
        Set(ByVal value As Boolean)
            m_AutoActivate = value

        End Set
    End Property

    Public Property ShowIcon() As Boolean
        Get
            Return m_ShowIcon
        End Get
        Set(ByVal value As Boolean)
            m_ShowIcon = value
        End Set
    End Property

    Public Property Host() As VSMDINav
        Get
            Return m_Host
        End Get
        Set(ByVal value As VSMDINav)
            m_Host = value
        End Set
    End Property

    Public Property Selected() As Boolean
        Get
            Return m_Selected
        End Get
        Set(ByVal value As Boolean)
            m_Selected = value
        End Set
    End Property

    Public Property OffColor() As Color
        Get
            Return m_OffColor
        End Get
        Set(ByVal value As Color)
            m_OffColor = value
        End Set
    End Property

    Public Property OnColor() As Color
        Get
            Return m_OnColor
        End Get
        Set(ByVal value As Color)
            m_OnColor = value
        End Set
    End Property

    Public Property AttachedForm() As Form
        Get
            Return m_AttachedForm
        End Get
        Set(ByVal value As Form)
            m_AttachedForm = value
        End Set
    End Property

#End Region

    Private Sub DoAutoActivate()
        If m_AutoActivate = False Then Exit Sub
        If m_AttachedForm Is Nothing Then Exit Sub

        m_AttachedForm.BringToFront()

    End Sub

    Private Sub VSMDINavPage_Paint(ByVal sender As Object, _
            ByVal e As System.Windows.Forms.PaintEventArgs) _
            Handles MyBase.Paint
        Dim TabRect As New Rectangle(0, 3, Me.Width, Me.Height)
        Dim cColor As Color
        Dim ctextFont As Font
        Dim ctextColor As Color = SystemColors.ControlDark
        Dim Ioffset As Integer = 2
        Dim cPage As VSMDINavPage
        Dim cOldPage As VSMDINavPage
        Dim X As Integer
        Dim Y As Integer
        Dim sSizeF As SizeF

        'paint the background

        e.Graphics.FillRectangle(New SolidBrush(m_OffColor), e.ClipRectangle)
        If m_Selected = True Then
            cColor = m_OnColor
        Else
            cColor = m_OffColor
        End If

        e.Graphics.FillRectangle(New SolidBrush(cColor), TabRect)

        If m_Selected = True Then
            'draw the 3d edge

            cColor = SystemColors.ControlText
            ctextFont = New Font(Host.Font, FontStyle.Bold)
            ctextColor = SystemColors.ControlText
            Ioffset = 0
        Else
            'draw a flat line at the right edge

            cColor = SystemColors.ControlDark

            ctextFont = New Font(Host.Font, FontStyle.Regular)
            ctextColor = SystemColors.ControlDark
            Ioffset = 6
        End If

        'draw spep line

        e.Graphics.DrawLine(New Pen(cColor, 3), Width, _
                  TabRect.Top, Width, TabRect.Height - 1)

        If Me.Text.Length > 0 Then

            sSizeF = e.Graphics.MeasureString(Me.Text, Host.Font)
            X = Me.Width / 2
            X = X - sSizeF.Width / 2

            Y = Me.Height / 2
            Y = Y - sSizeF.Height / 2
            If m_Selected = True Then
                e.Graphics.DrawString(Me.Text, ctextFont, _
                       New SolidBrush(ctextColor), X - 3, Y)
            Else
                e.Graphics.DrawString(Me.Text, ctextFont, _
                           New SolidBrush(ctextColor), X, Y)
            End If

        End If
        ctextFont.Dispose()

        'remove the line on the previous page

        If Host.Tabhost.Controls.Count < 2 Then Exit Sub

        For Each cPage In Host.Tabhost.Controls

            If cPage.Selected = True Then Exit For
            cOldPage = cPage
        Next
        If cOldPage Is Nothing Then
        Else
            RemoveOldLine(cOldPage)
        End If

    End Sub

    Private Sub RemoveOldLine(ByVal cOldPage As VSMDINavPage)
        Dim G As Graphics = cOldPage.CreateGraphics
        Dim TabRect As New Rectangle(0, 3, cOldPage.Width, cOldPage.Height)
        G.DrawLine(New Pen(m_OffColor, 3), Width, _
             TabRect.Top, Width, TabRect.Height - 1)
    End Sub

    Private Sub VSMDINavPage_Click(ByVal sender As Object, _
                ByVal e As System.EventArgs) Handles MyBase.Click
        m_Selected = True
        Me.Invalidate()
        m_Host.ForceRepaint(Me)
        DoAutoActivate()
    End Sub
End Class

Well, that's about it. Sorry if you don't understand, but if you can't then don't read an advanced article.

Points of Interest

In spite of what many people told me, this control was made in VB.NET, not the overrated C#. They told me it would be near impossible to make it in VB.NET, but it's been done and quite easily. In short, C# is highly overrated.

History

This was made for the express purpose of proving you don't need C# for advanced control creation.