Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

WinForms Custom Container Control

0.00/5 (No votes)
6 Jan 2005 1  
A UserControl for WinForms VB.NET that has child controls that it contains and is scrollable. This example shows how to draw your own controls and build your custom container to list them.

Sample Image - CustomContainerControl.jpg

Introduction

Building a container control for WinForms was easier than I first imagined. Why did I build one? Because I needed a custom solution for viewing controls that were created, but could not be placed into any other third-party control.

It appears that Microsoft left out the Data Repeater that was in VB 6 for Windows, so now we have to make our own.

Making the Container

The container is just a UserControl. Some of the particulars are to set the AutoScroll=True and add some events for handling which control is selected as well as the count.

    Public ReadOnly Property Count() As Integer
        Get
            Return Me.Controls.Count
        End Get
    End Property
 
    Public ReadOnly Property SelectedItem() As ChildItem
        Get
            Return Me._SelectedChild
        End Get
    End Property

The Container control is loaded by passing it a strong-typed collection.

    Public Property Items() As SimpleItemCollection
        Get
            Return _Items
        End Get
        Set(ByVal Value As SimpleItemCollection)
            _Items = Value
            If Value Is Nothing Then
                Me.ClearAll()
            Else
                Me.LoadAll()
            End If
        End Set
    End Property

Once the collection is passed in, you can load the control using the LoadAll method in the custom control.

    Private Sub LoadAll()
        Dim n As Integer
        Dim bSuspend As Boolean
        Dim flipper As Boolean
        Dim counter As Integer


        _Loading = True
        Me.Controls.Clear()
        Me.SuspendLayout()
        RaiseEvent SelectedItemChanged(Nothing)
        _SelectedChild = Nothing

        _Top = 0
        For Each item As SimpleItem In Me._Items
            Dim itm As New ChildItem(item)
            itm.SuspendLayout()

            Me.Controls.Add(itm)

            With itm
                .Left = 0
                .Width = Me.Width
                .Top = _Top
            End With
            itm.ResumeLayout()

            _Top += itm.Height

            If Not bSuspend Then
                ' This loads just enough controls at first

                ' so it appears as if the control is doing something

                If _Top > Me.Height Then
                    Application.DoEvents()
                    Me.SuspendLayout()
                    bSuspend = True
                End If
            End If

            AddHandler itm.ItemSelected, AddressOf ChildSelected
            AddHandler itm.ItemDoubleClicked, AddressOf _
                   ItemDoubleClicked_Handler

            'Select the first item

            If Me.Count = 1 Then
                itm.SetSelected()
            End If
        Next
        Me.ResumeLayout()

        RaiseEvent CountChanged(Me.Count)

        _Loading = False
        Me.ResumeLayout()

        ' Hide the Horizontal scrollbar that shows 


        ' up sometimes when the child

        ' controls are not less than the size of the container.

        Me.AutoScrollHandler.VisibleAutoScrollHorizontal = False
    End Sub

Making the Child Control

The child control inherits from Control. The reason for doing so is the reduced overhead of the ScrollableControl and the ContainerControl. There might be better ways to build this control, but I've found this to be sufficient and quick displaying. If you use a UserControl as the child and set the Dock to Top, it eases the loading code, etc., but it takes forever to resize the control and doesn't paint as fast.

The child control is simple and powerful once you get the hang of painting to the control. Get familiar with using the Graphic class. Methods such as Drawstring and FillRectangle.

Create a class that Inherits from Control.

Add some Imports to the Control to manage the ComponentModel as well as the drawing.

Imports System.Drawing.Drawing2D
Imports System.ComponentModel

There are some key style settings you need to add that are documented very little on the web. These settings manage the drawing and scrolling for the controls.

    Public Sub New()
        MyBase.New()

        ' This call is required by the Component Designer.

        InitializeComponent()

        Me.BackColor = Color.White

        ' Set the value of the double-buffering style bits to true.

        ' This paints the control off screen so there's no flicker effect.

        Me.SetStyle(ControlStyles.DoubleBuffer _
          Or ControlStyles.UserPaint _
          Or ControlStyles.AllPaintingInWmPaint, _
          True)

        ' This enables mouse support such as the Mouse Wheel

        setstyle(ControlStyles.UserMouse, True)

        ' This will repaint the control whenever it is resized

        setstyle(ControlStyles.ResizeRedraw, True)

        Me.UpdateStyles()
    End Sub

When creating the control, which is handled by the Custom container, just pass in the Contact (which in this example is the class object that holds the data for the control).

    Public Sub New(ByVal item As SimpleItem)
        Me.New()
        _Contact = item
        Me.Height = 40
    End Sub

    Public ReadOnly Property Contact() As SimpleItem
        Get
            Return _Contact
        End Get
    End Property

Painting the child control is going to have to be managed by the code. Unlike the UserControl class, this control does not paint itself. You lose the simplicity, but you gain speed when loading hundreds of controls. Plus, it's cool to impress friends and neighbors with your code! Yeah right.

    Protected Overrides Sub OnPaint(ByVal pe As _
                       System.Windows.Forms.PaintEventArgs)
        MyBase.OnPaint(pe)

        Dim ft As Font
        ' Get the font of the Parent

        If Not Me.Parent Is Nothing Then
            ft = Me.Parent.Font
        Else
            ft = New Font("Tahoma", 8, GraphicsUnit.Point)
        End If

        ' Paint the First Name and Last Name

        pe.Graphics.DrawString(Contact.FirstName _
           & " " & Contact.LastName, ft, _
           New SolidBrush(Me.Parent.ForeColor), 28, 6)

    End Sub

Now this is just a simple and rude example. You can get as elaborate as you want with painting the control. This is how the big-boys (third-party) do it.

Managing which item is selected is just a matter of trapping for MouseDown and raising an event that tells the parent container, which in this instance is the Custom Container, that this item is selected to unselect all others and move Me into view.

    Private Sub pnlMain_MouseDown(ByVal sender As System.Object, _
         ByVal e As System.Windows.Forms.MouseEventArgs) _
               Handles MyBase.MouseDown
        If e.Button = MouseButtons.Left AndAlso AllowSelect Then
            Me.SetSelected()

            ' Repaint the control

            Invalidate()
        End If

    End Sub

    Public Sub SetSelected()
        RaiseEvent ItemSelected(Me)
        Selected = True
    End Sub

    Public Property Selected() As Boolean
        Get
            Return _Selected
        End Get
        Set(ByVal Value As Boolean)
            _Selected = Value
            If Value Then
                BackColor = Color.Gainsboro
            Else
                BackColor = Color.White
            End If
            Invalidate()
        End Set
    End Property

The example code has all you'll need to get started and expand upon.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here