By: Paul S. Cilwa | Viewed: 12/7/2023 Posted: 6/24/2021 Updated: 8/7/2021 |
Page Views: 957 | |
Topics: #Organica #Shell #UserControl | |||
Creating the basic SqueezePanel user control and tying it into the SqueezeBox container. |

Now that we have the basic SqueezeBox control working (and no, we aren't done with it, yet), we are going to create a minimal, special control to reside within it. This control, called the SqueezePanel, is not a general-purpose control—That is, because of its specialization, there's truly no reason why anyone would ever want to use it in a stand-alone fashion. And, therefore, we don't want it to show up in the Toolbox—in fact, we'll want the SqueezeBox to create these panels automatically, when the SqueezeBox itself is deposited onto a form (or another container control). And the easiest way to make this happen, is to create the new control as a class member of SqueezeBox!.
A Nested Class
To define a nested class, all you have to do is place a regular class definition into an existing class definition. In my code, I added it just before the SqueezeBox class's End Class statement. The first few lines declare the class and attach a designer to it. (So, yes, we'll also have to add a simple designer; we'll get to that shortly.)
<Designer(GetType(SqueezeBoxDesigner))>
Public Class SqueezeBox
…
<Designer(GetType(SqueezePanelDesigner))>
Public Class SqueezePanel
Inherits System.Windows.Forms.UserControl
…
End Class
End Class
Like the SqueezeBox
, the SqueezePanel
has a Style
property, which can take any value from its Styles
Enum. However, where
the outer SqueezeBox
style can be Horizontal or Vertical, the SqueezePanel
styles have the following meanings, which will impact the meaning of the upcoming Extent
property.
- The
Fixed
style indicates thatExtent
is a specific number of pixels. Percent
means that Extent is a percentage of the SqueezeBox's available area.Automatic
means theSqueezePanel
will adjust to fill the available space.Extent
can be read but not changed except by the resizing of theSqueezeBox
itself.
Public Enum Styles
Fixed
Percent
Automatic
End Enum
…
End Class
End Class
We next declare a few local variables and the New()
event handler.
Most of these have to do with the panel's Extent
, the panel's only adjustable
dimension.
Friend MyParent As SqueezeBox
Private MyStyle As Styles
Private MyExtent As Int16
Private MyMinExtent As Int16
Private MyMaxExtent As Int16
Public Sub New(aParent As SqueezeBox)
Me.MyParent = aParent
Me.Name = "SqueezePanel"
Me.Margin = New System.Windows.Forms.Padding(0)
Me.Padding = New System.Windows.Forms.Padding(10)
Me.Cursor = Cursors.Default
End Sub
…
End Class
End Class
The property management code for the Style
property is straightforward enough.
<Category("Appearance")>
Public Property Style As Styles
Get
Return MyStyle
End Get
Set(Value As Styles)
MyStyle = Value
End Set
End Property
…
End Class
End Class
Next, we need a little private function to make sure any value assigned to Extent
isn't out of range, with the range defined as being between 1 and some arbitrary number.
However, the programmer using the SqueezeBox
control may not want to set a minimum,
and/or a maximum; in which case MyMinExtent
and MyMaxExtent
will still contain
zero values.
Private Function InRange(ByVal Value As Int16) As Int16
If MinExtent > 0 Then
Value = Math.Max(Value, MinExtent)
End If
If MaxExtent > 0 Then
Value = Math.Min(Value, MaxExtent)
End If
Return Value
End Function
…
End Class
End Class
The programmer will logically expect the size of the panel to change should they change the
Extent
value. However, it's important to remember that Extent
is not a direct
provider of the control's Size
, which is a property of the type of a special class of its own
(also called Size
).
Also, remember Extent
can either refer to a number of pixels or a percentage of the
available space. So, when a new value is assigned to Extent
, it is converted as needed
(in a function to be described shortly) to
a Size
object, which is then passed on to the base class's property.
(To prevent anyone's circumventing this mechaniism, we will hide the Size
property from the designer.)
<Category("Appearance")>
Public Property Extent As Int16
Get
Return MyExtent
End Get
Set(Value As Int16)
MyExtent = InRange(Value)
MyBase.Size = GetSize()
Invalidate()
End Set
End Property
…
End Class
End Class
The property management code for MinExtent
and MaxExtent
includes an interesting line (italicized):
<Category("Appearance")>
Public Property MinExtent As Int16
Get
Return MyMinExtent
End Get
Set(Value As Int16)
MyMinExtent = Value
Extent = Extent
Invalidate()
End Set
End Property
<Category("Appearance")>
Public Property MaxExtent As Int16
Get
Return MyMaxExtent
End Get
Set(Value As Int16)
MyMaxExtent = Value
Extent = Extent
Invalidate()
End Set
End Property
…
End Class
End Class
This line, Extent = Extent
, looks faintly recursize, or at least redundant.
However, remember the Extent
property code ensures that Extent
falls within
the range specified by MinExtent
and MaxExtent
, which may change when those
properties are changed. It also translates Extent
's value to Size
. So
the interesting line makes perfect sense: "Take the current value of Extent
,
and assign it to itself, which will then subject it to possible new limitations and resize the panel
as neccessary."
In previous code (property management for Extent
),
we invoked a to-be-implemented function called GetSize()
.
The purpose of this function is simple: To return a Size
object appropriate to the requested Extent
. What makes this
interesting is that the construction of this object varies depending
on the parent's (SqueezeBox
) Style
, that is,
whether this panel is being displayed as a row, or as a column.
Friend Function GetSize() As Size
Select Case MyParent.Style
Case SqueezeBox.Styles.Columns
Return New Size(GetExtentAsPixels(), MyParent.Height)
Case Else
Return New Size(MyParent.Width, GetExtentAsPixels())
End Select
End Function
…
End Class
End Class
However, this doesn't do the actual conversion of Extent
to a pixel count. That's handled by a separate function,
GetExtentAsPixels()
. Note the use of a Percentage
class function, described in a previous step.
Also, note that this returns only the BorderWidth
if the
panel's Style
is Automatic
. That's because the dimensions
of an automatic panel must be set by the containing SqueezeBox
itself, after tallying the Extent
s of all the other panels.
Friend Function GetExtentAsPixels() As Int16
Dim x As Int16 = MyExtent
If Style = Styles.Automatic Then
x += MyParent.BorderWidth
End If
Select Case Style
Case SqueezeBox.Styles.Columns
If Style = Styles.Percent Then
x = Percentage.CalcValue(x, MyParent.Width)
End If
Case Else
If Style = Styles.Percent Then
x = Percentage.CalcValue(x, MyParent.Height)
End If
End Select
Return x
End Function
End Class
End Class
Setting Extents
in the SqueezeBox
Class
In the SqueezeBox
class, we left stubs for CreatePanels()(
and
DistributePanels()
. Now we can fill them in. Here is the code for
CreatePanels()
:
Private Sub CreatePanels()
If Not IsLoaded Then Exit Sub
Me.SuspendLayout()
Dim P As SqueezePanel
For Each P In MyPanels
Me.Controls.Remove(P)
Next
MyPanels = New List(Of SqueezePanel)
For i As Byte = 1 To MyPanelCount
P = New SqueezePanel(Me)
With P
.Style = SqueezePanel.Styles.Fixed
.Extent = 100
.Location = New Point((i - 1) * (.Extent + BorderWidth), 0)
.Margin = New System.Windows.Forms.Padding(0, 0, BorderWidth, 0)
.BackColor = DefaultColors(i - 1)
End With
Me.Controls.Add(P)
MyPanels.Add(P)
Next
ResumeLayout()
End Sub
At this point, and remembering to Build the project first so that the most recent version
of the SqueezeBox
and SqueezePanel
code, if we open our project's
Frame
form, we may see something like this:

Why don't the new panels extend the full height of the containing SqueezeBox
?
Well, that's because, in Design Mode, the Visual Studio environment has an initial, arbitrary size
for forms that is subsequently updated by the values you've given the form's properties.
The method that is invoked when the form is resized is DistributePanels()
,
currently, a mere stub. It's time to expand it.
Friend Sub DistributePanels()
Me.SuspendLayout()
If Not IsLoaded Then Exit Sub
Dim P As SqueezePanel
Dim AutoP As SqueezePanel = Nothing
Dim PrevP As SqueezePanel = Nothing
Dim TotalExtent As Int16 = 0
For Each P In MyPanels
TotalExtent += IIf(P.Style = SqueezePanel.Styles.Automatic, 0, P.GetExtentAsPixels() + BorderWidth)
If P.Style = SqueezePanel.Styles.Automatic Then AutoP = P
Next P
If AutoP IsNot Nothing Then
AutoP.Extent = Width - TotalExtent - BorderWidth
End If
Dim i As Byte = 0
For Each P In MyPanels
i += 1
If PrevP Is Nothing Then
P.Location = New Point(0, 0)
Else
Dim L As New Point(PrevP.Location)
If Style = Styles.Columns Then
L.X += PrevP.Size.Width + BorderWidth
Else
L.Y += PrevP.Size.Height + BorderWidth
End If
P.Location = L
End If
P.Size = P.GetSize
PrevP = P
Next
ResumeLayout(True)
End Sub
This can be tested by again Building the project and opening (or tabbing to) the
Frame
designer.

And ya know what? Since we now have the panels displaying in the SqueezeBox, we can go ahead
in the form designer and set its BackColor
to Transparent.

Evenly Distributed by Percentage
So now we have four columns displayed, each of a fixed width.
They do not spread across the SqueezeBox
. But that's
because they are
all Fixed
style. There are two ways to make sure the panels fill
the available space: One is to have one panel be Automatic
;
the other
is to make all the panels be Percent
style and set the Extent
to
25% each (four times 25% is 100%).
Private Sub CreatePanels()
If Not IsLoaded Then Exit Sub
Me.SuspendLayout()
Dim P As SqueezePanel
For Each P In MyPanels
Me.Controls.Remove(P)
Next
MyPanels = New List(Of SqueezePanel)
For i As Byte = 1 To MyPanelCount
P = New SqueezePanel(Me)
With P
.Style = SqueezePanel.Styles.Percent
.Extent = 25
.Location = New Point((i - 1) * (.Extent + BorderWidth), 0)
.Margin = New System.Windows.Forms.Padding(0, 0, BorderWidth, 0)
.BackColor = DefaultColors(i - 1)
End With
Me.Controls.Add(P)
MyPanels.Add(P)
Next
ResumeLayout()
End Sub

Real-Life Example
In reality, and specifically in the Organica project of which the SqueezeBox and SqueezePanels are a part, we'll want a combination of all three styles.
Private Sub CreatePanels()
If Not IsLoaded Then Exit Sub
Me.SuspendLayout()
Dim P As SqueezePanel
For Each P In MyPanels
Me.Controls.Remove(P)
Next
MyPanels = New List(Of SqueezePanel)
For i As Byte = 1 To MyPanelCount
P = New SqueezePanel(Me)
With P
Select Case i
Case 1
.Style = SqueezePanel.Styles.Percent
.Extent = 10
Case 2
.Style = SqueezePanel.Styles.Fixed
.Extent = 150
Case 3
.Style = SqueezePanel.Styles.Automatic
Case Else
.Style = SqueezePanel.Styles.Percent
.Extent = 10
End Select
.Location = New Point((i - 1) * (.Extent + BorderWidth), 0)
.Margin = New System.Windows.Forms.Padding(0, 0, BorderWidth, 0)
.BackColor = DefaultColors(i - 1)
End With
Me.Controls.Add(P)
MyPanels.Add(P)
Next
ResumeLayout()
End Sub

Next
Next we'll want to expose SqueezePanel
properties to the designer.