View Sidebar

A Million Little Pieces Of My Mind

Organica: SqueezeBox control

By: Paul S. Cilwa Viewed: 3/3/2024
Posted: 6/23/2021
Page Views: 479
Topics: #Organica #Shell #UserControl #PostFilterProperties
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

  1. How to create a user control and add it to our project
  2. 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.