By: Paul S. Cilwa | Viewed: 12/7/2023 Posted: 6/23/2021 |
Page Views: 405 | |
Topics: #Organica #PostFilterProperties #Shell #UserControl | |||
Creating the basic SqueezeBox user control. |

Visual Basic comes with a number of splitable panel-type containers. However, in my initial experiments, I found they had three major problems with regard to this project.
- They are limited to, at most, two panels per container. Yes, you can put additional splitters into one of the panels, but as you do so...
- Drawing speed seems to be excessively slow.
- Nested splitters don't resize proportionally very well.
I also tried using a Table control, but it, too, became too sluggish; and although it's designed to keep rows and columns at sizes proportional to each other, in point of fact that didn't seem to work very well.
All of which led me to creating a specialized container control of my own. This new container, which I decided to call a SqueezeBox, can be divided into an arbitrary number of panels, arranged horizontally or vertically; and these panels can be given the percentage or size they require, and will arrange themselves accordingly if the SqueezeBox is resized.
Programmatically, in this step we'll learn
- How to create a user control and add it to our project
- How to suppress unwanted properties from appearing in the Properties box
Creating The Control

With your project open, use the Project..Add UserControl (Windows Forms) menu command. This will open the Add New Item dialog, pre-filled with the UserControl template. All you have to do is change the generic name to "SqueezeBox" and click the Add button.

Now the new (raw) control is added to the project and its designer window opened and ready to be modified.
Normally, at this point we would change at least some of the usercontrol's properties. And, in fact, eventually we'll be changing the BackColor properety to transparent. But for now, it will be easier to see the control to work with if we postpone that step.
Suppressing Unneeded Properties
All controls, including user-desgined and derived ones, come with a bunch of properties that pecic control might not need. The properties are still there in the base classes of course; but specifically, we'd like some of them to simply not appear in the design-time Properties box.
I've seen several ways to accomplish this; but by far the simplest is to override a specific method in the control's designer. Any customized designer code you care to provide can go into the same file as the control itself. When you first open the code window, there's no real code there.
Public Class SqueezeBox
End Class
But we'll add a class to inherit from the parent designer, and include the property overriding code
Imports System.ComponentModel
Imports System.Windows.Forms.Design
<Designer(GetType(SqueezeBoxDesigner))>
Public Class SqueezeBox
End Class
Public Class SqueezeBoxDesigner
Inherits ParentControlDesigner
Protected Overrides Sub PostFilterProperties(Properties As IDictionary)
MyBase.PostFilterProperties(Properties)
With Properties
.Remove("Anchor")
.Remove("AutoScroll")
.Remove("AutoScrollMargin")
.Remove("AutoScrollMinSize")
.Remove("AutoSize")
.Remove("AutoSizeMode")
.Remove("AutoValidate")
.Remove("BorderStyle")
.Remove("CausesValidation")
.Remove("Cursor")
.Remove("Dock")
.Remove("GenerateMember")
.Remove("Location")
.Remove("Margin")
.Remove("MaximumSize")
.Remove("MinimumSize")
.Remove("Size")
End With
End Sub
End Class
Public Class SqueezeBox
Public Enum Styles
Rows
Columns
End Enum
Const MinPanelCount = 2
Const MaxPanelCount = 6
Const DefaultBorderwidth = 20
Private IsLoaded As Boolean = False
Private DefaultColors(0 To 5) As Color
Private MyStyle As Styles = Styles.Columns
Private MyBorderWidth As Byte = DefaultBorderwidth
Private MyPanelCount As Byte = MinPanelCount
Private MyPanels As New List(Of SqueezePanel)
Public Sub New()
InitializeComponent()
DefaultColors(0) = Color.LightSkyBlue
DefaultColors(1) = Color.LightYellow
DefaultColors(2) = Color.LightCoral
DefaultColors(3) = Color.LightSalmon
DefaultColors(4) = Color.LightGreen
DefaultColors(5) = Color.LightBlue
End Sub
…
End Class
Event Handlers
We need two handlers for the Load
and Resize
events. Because
we won't know the property values until they've been loaded, we won't want to try to distribute
the child panels until after that has happened. A simple flag allows that.
What if there's a property you want to force to have a specific value, and not allow the user to change? For example, the Dock property. We want the SqueezeBox control to be Docked in its container. In fact, for it to work, it must have its Dock property set to "Fill".
Since the control's Dock property is to always be given the same value, we can hide the property name as we did, yet still use it programmatically—for example, when the control is being instantiated.
Private Sub SqueezeBox_Load(sender As Object, e As EventArgs) Handles Me.Load
IsLoaded = True
Dock = DockStyle.Fill
CreatePanels()
DistributePanels()
End Sub
Public Sub Control_Resize(sender As System.Object, e As System.EventArgs) Handles MyBase.Resize
DistributePanels()
End Sub
…
End Class
Custom Properties
"Style" and "BorderWidth" are not standard control properties, but our SqueezeBox will need them.
Wait—you say they are standard properties, because you've seen them before on other controls?
Well, it's true some other controls have Style and BorderWidth properties. But that doesn't make them inherited properties. The only thing standard about them is their names; and that's because the programmer who designed them chose to use names he or she'd seen before instead of making something up out of whole cloth. I strongly recommend taking the same approach any time you design a custom control.
To give a control a serializable property (one that retains its value between runs), you simply give it a pair of property procedures (Set and Get) and make them Public. (Private properties can only be used at run time.) And, in order to place the new properties in the appropriate category when using Category View to see them, simply add the appropriate preprocessor command as shown below.
<Category("Appearance")>
Public Property Style As Styles
Set(Value As Styles)
MyStyle = Value
If Value = Styles.Rows Then
Cursor = Cursors.SizeNS
Else
Cursor = Cursors.SizeWE
End If
CreatePanels()
End Set
Get
Return MyStyle
End Get
End Property
<Category("Appearance")>
Public Property BorderWidth As Byte
Get
Return MyBorderWidth
End Get
Set(Value As Byte)
MyBorderWidth = Value
DistributePanels()
End Set
End Property
<Category("Appearance")>
Public Property PanelCount As Byte
Set(Value As Byte)
Value = Math.Max(MinPanelCount, Math.Min(Value, MaxPanelCount))
MyPanelCount = Value
CreatePanels()
End Set
Get
Return MyPanelCount
End Get
End Property
…
End Class
By the way, notice how assigning a value to the Style
property also changes
the control's cursor. (That's because, when the mouse is over the SqueezeBox and not one
of the controls it will contain, we want the cursor to indicate that the space in between
is for resizing.
Placeholders
In order to get a clean build, we'll need to add stubs for two methods. Later, after we've
designed the SqueezePanel
control, we'll revisit these.
Private Sub CreatePanels()
If Not IsLoaded Then Exit Sub
Me.SuspendLayout()
'To be filled in later
ResumeLayout()
End Sub
Friend Sub DistributePanels()
Me.SuspendLayout()
If Not IsLoaded Then Exit Sub
'To be filled in later
ResumeLayout(True)
End Sub
End Class
Next
We shall definitely be coming back to this control. But since it is designed to work with an embedded control, we'll need to create that, next.