The Amazing GroupBox, Part 2

by Hannes du Preez

Gain the ability to contain controls and add a little preview for your control in design time.

Welcome to the second installment of this series. Because the normal GroupBox is a Container control, you obviously need to have that ability in yours as well; otherwise, it is not a GroupBox replacement at all. In this article, you will cover the ability to contain controls, as well as adding a little preview for oyur control in design time. Interesting stuff, so let me not waste any more of your time.

Adding Container Capabilities

All you need to do, believe it or not, is to add one line of code to your UserControl. Yes, that's right; one line of code!

And here is the magical line of code:

<Designer("System.Windows.Forms.Design.ParentControlDesigner, _
   System.Design", GetType(IDesigner))> _

This line should be just above the Class statement.

Okay, make it two!

You would also need to include the following Import:

Imports System.ComponentModel.Design

Changing the design

If you have read the first part, you would know that I put a Label and a Panel on the UserControl. The original plan was to have the Panel host all the child controls, but, it is not really necessary to have the Panel here because the UserControl is already a Scrollable Control, and provides the Scrolling capabilities already. All you need to do is to delete the Panel from the UserControl, and comment out all the references you have made to it, in code.

The label should be used only to display the title of the GroupBox; it would be senseless to allow the label to host controls as well, and it would look ugly. So, the plan is to have the remaining body of the UserControl host the children. Two things you have to remember here are the following:

  1. The added control(s) should not be allowed to have any part of it, inside the Heading label, nor outside the left edge of the UserControl.
  2. When the add controls are moved, they should not be able to have any part of it inside the Heading label, nor outside the left edge of our UserControl.

You simply can set the AutoScroll property of the UserControl to enable scrolling for controls outside the bottom and right edges of the UserControl.

Handling Control Placement

UserControl_ControlAdded event

To handle point number 1, you need to use the UserControl's built-in ControlAdded-event. This event is not specific to UserControl; any container control would have this property. This is a nice way to determine when a control has been added, what type of control it was, and the added control's location. Add the following to your UserControl:

Private Sub HTG_Group_ControlAdded(ByVal sender As Object, _
   ByVal e _
   As System.Windows.Forms.ControlEventArgs) _
   Handles Me.ControlAdded
   tmrGB.Enabled = True
   AddHandler e.Control.Move, AddressOf MoveAddedControl
   gbAddedControl = e.Control

End Sub

In this code segment, you enabled a Timer (which you will add with point number 2) and you used the BringToFront property of the added control to bring it to the front of its container. You then added an event handler named MoveAddedControl; this will handle the location properties of your added control. Lastly, you set a variable named gbAddedControl equals to the added control. This variable you will refer to in the Timer's Tick event, with point number 2. Add this Private variable with the type of Control to your UserControl.


The MoveAddedControl looks like the following:

Private Sub MoveAddedControl(ByVal sender As Object, _
                             ByVal e As System.EventArgs)
   If gbAddedControl.Top <= lblGBHead.Bottom Then _
      gbAddedControl.Top = _
   (lblGBHead.Top + gbAddedControl.Top) + 10
   If gbAddedControl.Left <= 0 Then gbAddedControl.Left = 1
End Sub

Here, you determine whether the added control's top is anywhere inside your label; if it is, you shift it down. You also handled the left property of the added control; this ensures that the added control will not be outside of the container.

Handling Child Control Movement

As mentioned earlier, you need to add a Timer to your control. Add it now, and set the following properties.

The Timer's Tick event looks like the following:

Private Sub tmrGB_Tick(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles tmrGB.Tick
   If gbAddedControl.Top <= lblGBHead.Bottom Then _
      gbAddedControl.Top = _
   (lblGBHead.Top + gbAddedControl.Top) + 10
   If gbAddedControl.Left <= 0 Then gbAddedControl.Left = 1
   tmrGB.Enabled = False
   End Sub

Here, you used the same logic as in the MoveAddedControl event, and you disabled the Timer again because it only needed to fire once.

If you build your project now, and add the new UserControl to the test form (frmTest), you should be able to add child controls in design time as well as run time to your UserControl. You may be greeted with a picture similar to Figure 1:

Figure 1: Adding Child Controls at Design Time

Just in case you are wondering what the code would look like to add controls during runtime, you can add this quick and dirty example in frmTest's Form_Load event:

Private Sub frmTest_Load(ByVal sender As Object, ByVal e As _
   System.EventArgs) Handles Me.Load
   Dim TempButton(49) As Button
   TempButton(0) = New Button
   TempButton(0).Top = 67
   TempButton(0).Left = 1
   TempButton(0).Text = "Button1"
   For i As Integer = 1 To 49
      TempButton(i) = New Button
      TempButton(i).Text = "Down " & CStr(i + 1)
      TempButton(i).Top = TempButton(i - 1).Top + _
      TempButton(i - 1).Height + 2
      TempButton(i).Left = 1

   For j As Integer = 1 To 49
      TempButton(j) = New Button
      TempButton(j).Text = "Right " & CStr(j + 1)
      TempButton(j).Top = 67
      TempButton(j).Left = TempButton(j - 1).Left + _
      TempButton(j - 1).Width + 2
End Sub

All this code achieves is to add 50 buttons underneath each other, and 50 buttons to the right of each other. Do you notice the automatic scrollbars? If you added this exact code, you should have a form that looks similar to Figure 2.

Figure 2: Adding Child Controls at Run Time

Adding a Preview in Design Mode Feature

While playing around with the UserContol's Design attributes, I decided to implement another, well, actually optional feature (here), but still a very useful feature to include. I'm talking about adding a preview in Design mode feature. What it basically is this: You can, from design time, preview the UserControl, meaning, you can run it from design time. Not on the form, but as a separate window. This means that you quickly can test what the UserControl would look and act like once run.

The first step is to change the existing <Design attribute to "inherit" from your own custom designer.

Change the existing Design Attribute at the top of your UserControl to the following:

<Designer(GetType(HTG_Group.HTG_GroupDesigner), _
GetType(IDesigner))> _
Public NotInheritable Class HTG_Group


You need to create our yown designer code. This must override the default UserControl designer; that is why you will add the new designer code as a class within the UserControl, a Friend class. Scroll to the bottom your UserControl's code, and just above the End Class statement, add the following:

Friend Class HTG_GroupDesigner
   Inherits System.Windows.Forms.Design.ControlDesigner

   Private MyVerbs As _

   Private Sub ScrollVerb(ByVal sender As Object, _
                          ByVal e As EventArgs)
      Dim HTG As New HTG_Group
      HTG.Location = New Point(200, 300)
      HTG.tmrGB.Enabled = False
   End Sub

   Protected Overrides Sub OnContextMenu(ByVal x As Integer, _
   ByVal y As Integer)
      MyBase.OnContextMenu(x, y)
   End Sub

   Public Overrides ReadOnly Property Verbs() As _
         If MyVerbs Is Nothing Then
            MyVerbs = New DesignerVerbCollection
            MyVerbs.Add(New System.ComponentModel.Design. _
               DesignerVerb("Scroll", _
            New EventHandler(AddressOf ScrollVerb)))
         End If
         Return MyVerbs
      End Get
   End Property

End Class

Class HTG_GroupDesigner inherits from ControlDesigner; this gives it the same capabilities as a normal object designer. Now, to get the preview during the Form's design phase, you need a way to trigger the preview. I thought it best to add another item into the default object menu you get when you right-click on an object, on a form, during design time. The normal menu looks like the following:


Figure 3: The Normal UserControl Menu

Okay, so how do I add my own menu item in there?

The solution is relatively easy. You need to set up a Designer Verb, with an associated event, and text; that is all [_Smiley.gif]. In the code segment above, you will notice that I made an object called MyVerbs, of type DesignerVerbCollection. This will be your Scroll Menu Item, and event, meaning, this will be your Preview ability. The property called (Verbs) creates the Menu item, along with its associated event handler, named ScrollVerb. In ScrollVerb, you simply create a new HTG_Group object (or whatever you've called your GroupBox), and set the SetTopLevel property to true; this will cause a separate window to open up, inside design view; this window is now your preview. Funky huh? Yes, indeed [_Smiley.gif].

For your own Contextmenu to work with the built-in menu, you simply need to add a Contextmenu object to your Control; name it cmGB. You don't have to add any items; the only thing that is needed is the empty Contextmenu and the OnContextMenu override as illustrated in the segment of code above. Add it now.

If you were to right-click on the control now, you would get a menu, similar to the following (see Figure 4):


Figure 4: Your Added Menu Item

Upon clicking the Scroll Menu item, a screen that resembles Figure 5 would appear:


Figure 5: Preview Without a Means of Closing

Closing Properly

You have to remember here, that if you don't provide a means of closing the preview, it will still remain in memory and give a window similar to what you see in Figure 6:


Figure 6: The Unnecessary Empty Taskbar Window

What I chose to do was to add a button to the UserControl, to help you close the preview. Now, this button needn't be visible always; you are going to add a couple of properties to it, so that when you are 100% certain the UserControl will look and function appropriately, you can choose to not show the button anymore.

Adding the close button

Add a button to the UserControl, and set the following properties:

Property Value
Name btnClosePrev
Anchor Top, Right
Text X

Add the following default values for your button.

Private gbPrevLoc As Point = New Point(252, 3)
Private gbPrevSize As New Size(23, 23)
Private gbPrevVis As gbEVis = gbEVis.True

Now, add the Close Button's Properties:

Public Property PreviewButtonLocation() As Point
      Return gbPrevLoc
   End Get
   Set(ByVal value As Point)
      gbPrevLoc = value
      btnClosePrev.Location = New Point(value)
   End Set
End Property

Public Property PreviewButtonSize() As Size
      Return gbPrevSize
   End Get
   Set(ByVal value As Size)
      gbPrevSize = value
      btnClosePrev.Size = New Size(gbPrevSize)
   End Set
End Property

Public Property PreviewbuttonVisible() As gbEVis
      Return gbPrevVis
   End Get
   Set(ByVal value As gbEVis)
      gbPrevVis = value
      Select Case gbPrevVis
         Case 0
            btnClosePrev.Visible = True
         Case 1
            btnClosePrev.Visible = False
      End Select
   End Set
End Property

These properties handle the Location, size, and Visibility properties of the button.

Build your project now, and if you were to follow the exact same steps, as you did in the previous code example, you would be greeted with a screen such as you see in Figure 7:


Figure 7: A Close Button Added

You are now able to close the preview window properly.

Why the <Design Settings Can't Work Together

At the time of this writing, I haven't yet found a way to get the two <Designer attributes to work together. To be honest, though, I don't think that this is possible. The reason why I say this is that the UserControl Designer can only inherit from one base at a time; that is the only logical explanation I could think of. Because I'm such a perfectionist, I would have loved to have the Preview and Container capabilities at the same time, but for now, you'll have to settle for it as it is, as the goal of this article and this article series is just to introduce you to the full power of the UserControl, and to create your own fully functional GroupBox.


Thank you for taking time to follow this article and series. I hope that you have benefited from this article, and enjoyed it as much as I have. I am including examples of both methods I covered in this article; feel free to play around with them. In the next article, you will tidy up loose ends, and see what other features you can add. Until next time, have fun!

This article was originally published on Tuesday Jul 22nd 2008
Mobile Site | Full Site