Click here to Skip to main content
15,503,808 members
Articles / Multimedia / GDI+
Article
Posted 6 Jan 2005

Stats

139.5K views
2.8K downloads
65 bookmarked

WinForms Custom Container Control

Rate me:
Please Sign up or sign in to vote.
4.63/5 (20 votes)
6 Jan 20052 min read
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.

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

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

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

Image 2

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

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

VB
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).

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

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

VB
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


Written By
Software Developer (Senior) BOCA Software Technologies, Inc.
United States United States
.NET Developer in Garner, North Carolina. Specializing in WinForms development in C#, VB.Net.

CEO/Founder BOCA Software Technologies, Inc.

Comments and Discussions

 
GeneralJust a suggestion Pin
Diamonddrake24-Dec-10 7:46
Diamonddrake24-Dec-10 7:46 
I know this is 5 year old article, but i come across it working on a custom container control of my own. I was looking for a way to handle scrolling in an autoscroll panel that when child controls were selected I could still use the mouse wheel. This example, as with the C# autoscroll panel ex article both led me to believe there was no way to do this except the user32 api for sendmessage. That resulted in inconsistent increments between windows settings for the scroll wheel, and of course when the container or its children were selected they acted differently. I found I could override the onMouseWheel of the container and force it to use the API, but that didn't solve the problem it just made it less evident.

The answer was simply creating a public method that taxies mouseEventArgs to the container control's base class's OnMouseWheel method. Then overriding the child control's OnMouseWheel and calling the parent's MouseEventTaxi method.

Then you have a simple, Win API free, reliable and system setting compliant way of handling the scroll wheel in a custom container control

I am planning on writing an article about my control soon. Thanks for sharing your knowledge!
QuestionPanel class? Pin
Uwe Keim6-Jan-05 19:18
sitebuilderUwe Keim6-Jan-05 19:18 
AnswerRe: Panel class? Pin
Victor Boba10-Jan-05 16:33
Victor Boba10-Jan-05 16:33 
GeneralRe: Panel class? Pin
wumpus19-Feb-05 15:16
wumpus19-Feb-05 15:16 
QuestionRe: Panel class? Pin
MikeAngel13-Apr-12 8:46
MikeAngel13-Apr-12 8:46 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.