Runtime Control Sizing and Dragging Class
This article extends the
Simple Runtime Control Sizing
and Dragging Class by Jim Korovessis, as explained in his article at Code Project.
The PickBox class provides sizing handles that allow the positioning and sizing on simple controls on a containing form.
Jim used C# to implement this class; however, I am using a VB.Net version of that class in my implementation. You can download sample VB.NET project, used in this tutorial.
The major functionality that I have written is identifying Panels and GroupBoxes as Containers
and adding/removing controls to them by dragging in/out controls.
The ControlPickBox class exposes a "WireControl" method that attaches events to a passed control,
implementing "pickbox" behavior.
A control that is wired, when clicked, displays sizing handles around its edges enabling sizing and dragging of the control.
This class is limited to allow dragging and resizing of one control at a time.
To use this class, one needs to only create an object of the class and call WireControl method
for the controls to wire.
Private
CP As New
ControlPickBox
Private
Sub Form1_Load(ByVal
sender As System.Object, _
ByVal
e As System.EventArgs)
Handles MyBase.Load
For
Each TempControl As
Control In Me.Controls
CP.WireControl(TempControl)
Next
End
Sub
The code above wires all the controls on our form enabling us to resize and move them at run-time.
One can use this technique to allow dynamic positioning of the controls as well as complete dynamic layout design of the form.
The "WireControl" method attaches a Click event handler to each passed control.
When called the event handler then attaches the "pickbox",
made up of eight Label controls that act as sizing handles, to the clicked control.
In addition, mouse event handlers are attached to the control allowing for dragging of the control on its parent form.
In order to use the class allowing panels and group-boxes, Jim's functionality needed a slight bit of adjustment.
Change;
AddHandler
m_control.Parent.Click, AddressOf
Me.Parent_Click
To;
AddHandler
m_control.FindForm.Click, AddressOf
Me.Parent_Click
The reason for this is that a controls Parent can be a Panel or a GroupBox.
The Parent_Click method clears selection - i.e. when the user clicks the form,
we clears any sizing handles around a selected control, and this we don't want to do when clicking a GroupBox
or a Panel parent of a control, in which case we just want to shift the sizing handles to the new control pressed.
Let us now get down to identifying the parent control and adding out selected control
to that control when dragged into it.
Private
Sub ctl_MouseUp(ByVal
sender As Object,
ByVal e As
MouseEventArgs)
dragging = False
AfterMove_IdentifyParent(m_control.FindForm)
Dim i As
Integer
For i = 0 To 8 -
1 Step i + 1
m_control.Parent.Controls.Add(lbl(i))
lbl(i).BringToFront()
Next
MoveHandles()
ShowHandles()
End
Sub
Ctl_MouseUp method is executed when the mouse button is released,
i.e. the control is released. At this point we set our control at the new position,
so this is the place that we need to identify our Parent and add the control accordingly.
When checking if a control is over another control,
we use a loop on the Controls collection and check if a control comes on top of another.
If so, then we add it to that control, and then again recall the method checking for the controls inside that container control.
In other words, when a control is dragged, it can end up inside a panel, or inside a panel that is
inside another panel that may be inside a group-box.
Private
Sub AfterMove_IdentifyParent(ByRef
searchIn As Object)
Dim repeat As
Boolean = False
Dim tempContainer As
Control = Nothing
For Each
tempControl As Control
In searchIn.Controls
If tempControl.Equals(m_control)
Then
Else
If tempControl.GetType.ToString()
_
=
"System.Windows.Forms.Panel"
Then
Dim topInt
As Integer =
tempControl.Bounds.Top
Dim bottomInt
As Integer =
tempControl.Bounds.Bottom
Dim leftInt
As Integer =
tempControl.Bounds.Left
Dim rightInt
As Integer =
tempControl.Bounds.Right
If m_control.Location.X >=
leftInt Then
If m_control.Location.X
<= rightInt Then
If
m_control.Location.Y >= topInt Then
If
m_control.Location.Y <= bottomInt Then
Dim
myControl As New
Control
myControl = m_control
myControl.Parent.Controls.Remove(m_control)
myControl.Location =
New _
Point(myControl.Location.X – leftInt _
,
myControl.Location.Y - topInt)
tempControl.Controls.Add(myControl)
m_control = myControl
If
tempControl.Controls.Count > 1 Then
repeat = True
tempContainer = tempControl
Exit
For
Else
repeat = False
Exit
For
End
If
End
If
End
If
End
If
End
If
ElseIf
tempControl.GetType.ToString() _
=
"System.Windows.Forms.GroupBox"
Then
Dim topInt
As Integer =
tempControl.Bounds.Top
Dim bottomInt
As Integer =
tempControl.Bounds.Bottom
Dim leftInt
As Integer = tempControl.Bounds.Left
Dim rightInt
As Integer =
tempControl.Bounds.Right
If m_control.Location.X >=
leftInt Then
If m_control.Location.X
<= rightInt Then
If
m_control.Location.Y >= topInt Then
If
m_control.Location.Y <= bottomInt Then
Dim
myControl As New
Control
myControl = m_control
myControl.Parent.Controls.Remove(m_control)
myControl.Location = _
New
Point(myControl.Location.X – _
leftInt ,
myControl.Location.Y - topInt)
tempControl.Controls.Add(myControl)
m_control = myControl
If
tempControl.Controls.Count > 1 Then
repeat = True
tempContainer = tempControl
Exit
For
Else
repeat = False
Exit
For
End
If
End
If
End
If
End
If
End
If
End If
End If
Next
If repeat = True
Then
AfterMove_IdentifyParent(tempContainer)
Else
End If
End
Sub
The code above searches through the control collection for Panels and GroupBoxes
and checks if the control is dropped onto either of them.
If so, then it adds the control to that container, and recalls the same method for that container.
Otherwise it exits the method.
In the sample attached with this project, you'll see a GroupBox and a Panel object on the form.
Drag the group-box into the Panel, which'll move inside the panel,
and then drag-in a text-box and drop it over the group-box.
The application will add that text-box into the Group-Box successfully.
Now for the next part; to bring out the control from its container.
When the user selects the control, we don't want it to be just moveable inside the container,
but also be drag-able outside the container.
To do this we need to remove the control from the container and add it to the form.
To accomplish this, I have modified the MouseDown event handler;
Private
Sub ctl_MouseDown(ByVal
sender As Object,
ByVal e As
MouseEventArgs)
dragging = True
If m_control.Parent.GetType.ToString <> _
"System.Windows.Forms.Form"
Then
Dim frmParent
As Form = m_control.FindForm
Dim tempControl
As Control = m_control
tempControl.Location = ctl_FindLocation()
tempControl.Parent.Controls.Remove(m_control)
frmParent.Controls.Add(tempControl)
tempControl.BringToFront()
End If
start_X = e.X
start_Y = e.Y
HideHandles()
End
Sub
As you can see, when the mouse button is pressed I am checking if the parent of the control is not a Form.
If it isn't, I get a new location for the control, remove it from the container, and add it to the form.
Why do we need to get a new location?
Because the location property is defined according to the container.
Suppose a button is located at X=5 and Y = 10 in a Panel Control.
If we were to move it to the form, then the location would need to be justified according to the form which would be
X=5 + Panel.X and Y = 10 + Panel.Y.
That is simple for a single container, but since we are allowing container within container,
we need to be cautious about it.
Private
Function ctl_FindLocation()
As Point
Dim m_location As
Point
If m_control.Parent.GetType.ToString = _
"System.Windows.Forms.Panel"
Then
Dim tempParentControl
As Control = m_control.Parent
Dim ParentForm
As System.Windows.Forms.Form
ParentForm = m_control.FindForm
Dim tempX
As Integer = m_control.Location.X
tempX =
tempX + tempParentControl.Location.X
Dim tempY
As Integer = m_control.Location.Y
tempY =
tempY + tempParentControl.Location.Y
tempParentControl = tempParentControl.Parent
While Not
tempParentControl.Equals(ParentForm)
tempX = tempX + tempParentControl.Location.X
tempY = tempY + tempParentControl.Location.Y
tempParentControl = tempParentControl.Parent
End While
m_location = New Point(tempX, tempY)
ElseIf m_control.Parent.GetType.ToString = _
"System.Windows.Forms.GroupBox"
Then
Dim tempParentControl
As Control = m_control.Parent
Dim ParentForm
As System.Windows.Forms.Form
ParentForm = m_control.FindForm
Dim tempX
As Integer = m_control.Location.X
tempX =
tempX + tempParentControl.Location.X
Dim tempY
As Integer = m_control.Location.Y
tempY =
tempY + tempParentControl.Location.Y
tempParentControl = tempParentControl.Parent
While Not
tempParentControl.Equals(ParentForm)
tempX = tempX + tempParentControl.Location.X
tempY = tempY + tempParentControl.Location.Y
tempParentControl = tempParentControl.Parent
End While
m_location = New Point(tempX, tempY)
Else
m_location = m_control.Location
End If
Return m_location
End
Function
The method above calculates the new location for our control. What it does is;
Control.X = Control.X + ControlParent.X
Control.Y = Control.Y + ControlParent.Y
Remove Control from ControlParent
Add Control to ControlParent.Parent
If ControlParent.Parent Is Not a Form, then repeat.
That's all there is to it. Try out the sample application attached with this tutorial.
You can reach me at msalmank@gmail.com.
Feel free to send me questions or feedback. I would like to express a special thanks to Jim Korovessis
for his wonderful class that made this article possible.
|