Guide to Building a VS 2008 User Control in VB

Quite often we have a need for a VB control that has some requirements that don't quite exist within the current control set. Recently a client required that all data-entry controls be on a single page, not across multiple tabs, and the page should scroll much like the page you're reading now.

Rather than doing the quick large Picturebox inside a smaller Picturebox with scrollers, and duplicating the same across multiple forms, I decided to create a quick user control to simplify the effort on the second and third forms.

Little did I know that I was in for a challenge, and spent much more time working on this than planned.

The First Obstacle

Not many people are aware that user controls have two working modes; Design mode and Running mode. The user-control code runs in both modes however Debugging only works in running mode; code breaks will not trigger during design time. This obviously leads to problems testing; however you often can replicate design time actions in the run time to fully debug specific options.

Most of the time you need to set up the control properly, so to start, add a Component class to your project and modify the code as below.

Imports System.ComponentModel
Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.ComponentModel.Design
Imports System.Windows.Forms
 
<Designer("System.Windows.Forms.Design.ParentControlDesigner,System.Design", GetType(IDesigner))> _
Public Class TestControl
    Inherits Control
 
End Class

This prepares the control for the designer, and allows you to add controls to it. Now that our base user control is ready, let's start with our experiments. (Thanks to Hannes for showing me this when I started).

Experiment 1

First try was a user control with a Picturebox inside a Picturebox. This works like a charm on the form.

In the class Designer we add a Picturebox, only one for now so that we can test. But aren't we doing a Picturebox inside a Picturebox? Yes we still going to use that method and the Component class is the first Object and the smaller holder of the two. Our Picturebox is the second and larger of the two, which will ultimately hold the final controls.

To start we need to initialize the controls.

    Public Sub New()
        ' This call is required by the Windows Form Designer.
        InitializeComponent()
 
        ' Add any initialization after the InitializeComponent() call.
 
        Me.Controls.Add(PictureBox1)
 
    End Sub

And now let's expose a few properties of the Picturebox so that we can play with it in the designer.

    Public Property Z_Width() As Integer
        Get
            Return PictureBox1.Width
        End Get
        Set(ByVal value As Integer)
            PictureBox1.Width = value
        End Set
    End Property
    Public Property Z_Height() As Integer
        Get
            Return PictureBox1.Height
        End Get
        Set(ByVal value As Integer)
            PictureBox1.Height = value
        End Set
    End Property
    Public Property Z_Top() As Integer
        Get
            Return PictureBox1.Top
        End Get
        Set(ByVal value As Integer)
            PictureBox1.Top = value
        End Set
    End Property
    Public Property Z_Left() As Integer
        Get
            Return PictureBox1.Left
        End Get
        Set(ByVal value As Integer)
            PictureBox1.Left = value
        End Set
    End Property

Lets test this quickly.

NOTE: even if we added the Scrollers at this time, they are not active in the designer and you would still need to adjust the position of the inner controls via the properties. So the Scrollers are left out until we know where we going to go.

Build the control, and then add it to a form, Adjust the Z_width and Z_Height so that our Picturebox is wider and higher than the control. Now add a few other controls to our control. Adjust Z_top and Z_left to move our inner control around.

Now we're shifting the Picturebox about; remember, negative values will pull the inner Picturebox up and left, exposing the hidden parts. However, there's a problem, none of the added controls are shifting around.

The problem is they are all children controls at the same level as the Picturebox, and not children of the Picturebox, and because of this they do not move with it.

Guide to Building a VS 2008 User Control in VB

Experiment 2:

Let's try to make those controls children of the Picturebox.

There's an event that is raised every time a control is added, so let's use it.

    Private Sub ControlScroll_ControlAdded(ByVal sender As Object, ByVal e As System.Windows.Forms.ControlEventArgs) Handles Me.ControlAdded
        e.Control.BringToFront()
            PictureBox1.Controls.Add(e.Control)
        End If
    End Sub

With this code, every time a control is added to our user control we add it to the Picturebox, and make it a child of the Picturebox.

Rebuild the control, add it to a form and add other controls to it. Test the Z_top and Z_Left; the controls move with it. Looks like we on track here. But wait! What happens when the form is forced to redraw, which can happen for any reason. When this happens all the Sub controls disappear.

WHY! This is a little difficult to explain, and only if you understand the Designer in VS.NET, will you fully grasp it. The designer code is automatically regenerated, and any user modifications directly to the designer code are not recommended. Now when we alter the parent of the control added to our user control, (by adding it to the Picturebox) the change in the parent is propagated to the form. This causes changes to the designer, and the lines of code in the designer that add these controls to our user control are removed. When the form is re-rendered, they are never added to our control.

So what now? We scrap the idea of using another control to handle the moving of the subcontrols and take care of those ourselves.

Final Task:

Now we know exactly where we are headed. First, we need to build a list of all the controls that are going to be added and moved around our control; we also need to remember how big the view space and the page space is, and a few other variables.

    Private Class My_Control
        Private _Point As Point
        Public Control As Control
        Public Property Location() As Point
            Get
                Return _Point
            End Get
            Set(ByVal value As Point)
                _Point = value
            End Set
        End Property
 
        Public Property X() As Integer
            Get
                Return _Point.X
            End Get
            Set(ByVal value As Integer)
                _Point.X = value
            End Set
        End Property
 
        Public Property Y() As Integer
            Get
                Return _Point.Y
            End Get
            Set(ByVal value As Integer)
                _Point.Y = value
            End Set
        End Property
    End Class
 
    Private PageSize As Size
    Private ViewSize As Size
    Private My_Controls As New List(Of My_Control)
    Private Scrolling As Boolean
    Private _DoLayout As Boolean

Now when a Control is added to our user control, we get all of its info and add it to our list of controls. We also store a Point reference that is where the control's home position is. When the Control is moved in the designer we store this position.

    Private Sub ControlScroll_ControlAdded(ByVal sender As Object, ByVal e As System.Windows.Forms.ControlEventArgs) Handles Me.ControlAdded
        e.Control.BringToFront()
        If CheckItem(e.Control) Then
            AddHandler e.Control.Move, AddressOf MoveAddedControl
            Dim TmpControl As New My_Control
            TmpControl.Control = e.Control
            TmpControl.Y = TmpControl.Control.Location.Y
            TmpControl.X = TmpControl.Control.Location.X
            My_Controls.Add(TmpControl)
            If _DoLayout Then
                CalculateViewDiff()
            End If
        End If
    End Sub
 
    Private Sub MoveAddedControl(ByVal sender As Object, ByVal e As System.EventArgs)
        If Not (Scrolling) Then
            ' Adjust the location only when the control is been moved outside this control
            For Each Item In My_Controls
                If Item.Control Is sender Then
                    Dim TmpLoc As Point
                    'If Item.Control.Location.Y < 0 Then
                    '    Item.Control.Top = 0
                    'End If
                    'If Item.Control.Location.X < 0 Then
                    '    Item.Control.Left = 0
                    'End If
                    TmpLoc.Y = Item.Control.Location.Y + VScrollBar1.Value
                    TmpLoc.X = Item.Control.Location.X + HScrollBar1.Value
                    Item.Location = TmpLoc
                End If
            Next
            If _DoLayout Then
                CalculateViewDiff()
            End If
        End If
    End Sub
 
    Private Sub ControlScroll_ControlRemoved(ByVal sender As Object, ByVal e As System.Windows.Forms.ControlEventArgs) Handles Me.ControlRemoved
        For Each Item In My_Controls
            If Item.Control Is e.Control Then
                My_Controls.Remove(Item)
                Exit For
            End If
        Next
        If _DoLayout Then
            CalculateViewDiff()
        End If
    End Sub

We must not forget to remove reference to any control when it is removed from our control too. This is important as we don't want to be trying to move a control that no longer exists.

You will notice reference to the _DoLayout variable and CalculateViewDiff sub. Let's look at those quickly.

When controls are being laid out they can be told to B hold rendering, and when to continue rendering. So we need to remember what state we are in and of course work accordingly.

    Public Overloads Sub ResumeLayout(ByVal PerformLayout As Boolean)
        _DoLayout = True
        Me.PerformLayout()
        Recalc_top()
        CalculateViewDiff()
    End Sub
 
    Public Overloads Sub ResumeLayout()
        _DoLayout = True
        Recalc_top()
        CalculateViewDiff()
    End Sub
 
    Public Overloads Sub SuspendLayout()
        _DoLayout = False
    End Sub
 
    Public Overloads Sub PerformLayout()
        If _DoLayout Then
            For Each Citem In My_Controls
                Citem.Control.PerformLayout()
            Next
        End If
    End Sub
 
    Public Overloads Sub PerformLayout(ByVal AffectedControl As Windows.Forms.Control, ByVal AffectedProperty As String)
        If _DoLayout Then
            For Each Citem In My_Controls
                If Citem.Control Is AffectedControl Then
                    Citem.Control.PerformLayout(AffectedControl, AffectedProperty)
                End If
            Next
        End If
    End Sub

The CalculateViewDiff function does most of the hard work. It works out how big the page needs to be to hold all of the controls in their current position, and which scrollers need to be visible.

    Private Sub CalculateViewDiff()
        Scrolling = True
        ' Lets calculate the size of the Back Page
        Dim MaxHeight As Integer = 0 
        Dim MinHeight As Integer = 0
        Dim MaxWidth As Integer = 0  
        Dim MinWidth As Integer = 0
        For Each Item In My_Controls
            If CheckItem(Item) Then
                If Item.Control.Left - Item.Control.Padding.Left - Item.Control.Margin.Left < MinWidth Then
                    MinWidth = Item.Control.Left - Item.Control.Padding.Left - Item.Control.Margin.Left
                End If
                If Item.Control.Top - Item.Control.Padding.Top - Item.Control.Margin.Top < MinHeight Then
                    MinHeight = Item.Control.Top - Item.Control.Padding.Top - Item.Control.Margin.Top
                End If
                If Item.Control.Left + Item.Control.Width + Item.Control.Padding.Right + Item.Control.Margin.Right > MaxWidth Then
                    MaxWidth = Item.Control.Left + Item.Control.Width + Item.Control.Padding.Right + Item.Control.Margin.Right
                End If
                If Item.Control.Top + Item.Control.Height + Item.Control.Padding.Bottom + Item.Control.Margin.Bottom > MaxHeight Then
                    MaxHeight = Item.Control.Top + Item.Control.Height + Item.Control.Padding.Bottom + Item.Control.Margin.Bottom
                End If
            End If
        Next
        PageSize.Height = MaxHeight - MinHeight
        PageSize.Width = MaxWidth - MinWidth
        ViewSize = Me.Size
        'Now are we bigger than the Control...
        'check the width first
        PictureBox1.Visible = False
        CheckWidth()
        ' then check the height
        If CheckHeight() Then
            ' then dbl check the width..
            CheckWidth()
        End If
        Scrolling = False
    End Sub

Rather than putting all the code here, a full working user control ready to add to your application is in the download.



About the Author

Richard Newcombe

Richard Newcombe has been involved in computers since the time of the Commodore 64. Today, he has excelled in programming, and designs. Richard is in his mid 30's and, if or when you looking for him look no further than his computer. Always willing to help and give advice where he can in regard to computer related subjects. At present he is working as a .NET 2008 Software Developer for Syncrony Web Services, South Africa.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Java developers know that testing code changes can be a huge pain, and waiting for an application to redeploy after a code fix can take an eternity. Wouldn't it be great if you could see your code changes immediately, fine-tune, debug, explore and deploy code without waiting for ages? In this white paper, find out how that's possible with a Java plugin that drastically changes the way you develop, test and run Java applications. Discover the advantages of this plugin, and the changes you can expect to see …

  • With 81% of employees using their phones at work, companies have stopped asking: "Is corporate data leaking from personal devices?" and started asking: "How do we effectively prevent corporate data from leaking from personal devices?" The answer has not been simple. ZixOne raises the bar on BYOD security by not allowing email data to reside on the device. In addition, Zix allows employees to maintain complete control of their personal device, therefore satisfying privacy demands of valued employees and the …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds