Click here to Skip to main content
Click here to Skip to main content

Simple Runtime Control Sizing and Dragging Class

, 29 Sep 2003
Rate this:
Please Sign up or sign in to vote.
Sample and brief description of simple class that enables sizing and dragging of controls on a form

Figure - Form showing selected Label control, following selection

Introduction

The PickBox class provides sizing handles that allow the positioning and sizing on simple controls on a containing form. This C# sample was adapted from an earlier version of the class written in VB (version 6). The sample was prepared using Borland's C# Builder IDE and exported to a VS project.

Using the code

The PickBox class exposes a "WireControl" method that attaches events to a passed control, implementing “pickbox” behavior. Clicking on a “wired” control displays eight sizing handles around the perimeter of the control and enables sizing and dragging of the control via mouse event handlers provided by the class instance (see commented code for details). The following snippet illustrates the use of the PickBox class and this function from within the Sample Form:

//(Excerpt from Winform.cs)
//
// Create an instance of the PickBox class
//
private PickBox pb = new PickBox();
public WinForm() // Sample Form's constuctor
{
    InitializeComponent();
    //
    // Provide a Click event handler for each control
    // that attaches a pick box to the control when clicked
    //
    foreach (Control c in this.Controls) {
        pb.WireControl(c);
    }
}

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.

//(Excerpt from PickBox.cs)
private void SelectControl(object sender, EventArgs e) {
    if (m_control is Control) {
        m_control.Cursor = oldCursor;
        // Remove event any event handlers appended to last control
        // by this class
        m_control.MouseDown -= new MouseEventHandler(this.ctl_MouseDown);
        m_control.MouseMove -= new MouseEventHandler(this.ctl_MouseMove);
        m_control.MouseUp -= new MouseEventHandler(this.ctl_MouseUp);
        m_control.Click -= new EventHandler(this.SelectControl);
        m_control = null;
    }
    m_control = (Control)sender;
    //Add event handlers for moving the selected control around
    m_control.MouseDown += new MouseEventHandler(this.ctl_MouseDown);
    m_control.MouseMove += new MouseEventHandler(this.ctl_MouseMove);
    m_control.MouseUp += new MouseEventHandler(this.ctl_MouseUp);
    //Add sizing handles to Control's container (Form or PictureBox)
    for (int i = 0; i<8; i++) {
        m_control.Parent.Controls.Add(lbl[i]);
        lbl[i].BringToFront();
    }
    //Position sizing handles around Control
    MoveHandles();
    //Display sizing handles
    ShowHandles();
    oldCursor = m_control.Cursor;
    m_control.Cursor = Cursors.SizeAll;
}

The sizing handles are Labels that are created, initialized and stored in an array of Label controls when the instance of the PickBox class is constructed. MouseDown, MouseMove, and MouseUp events service the array of Labels during control sizing operations.

Points of Interest

The class sample works well for simple applications, but may exhibit some interaction within applications employing more complicated, time-critical event handling. In it’s current form it provides for the selection of only one control at a time.

The PickBox sample is a simpler, and probably less versatile C# example of the functionality presented in the C++ sample “A Sizing/Moving widget” by Andrew JM Hall.

History

  • This is the initial submission of the sample.

License

This article, along with any associated source code and files, is licensed under A Public Domain dedication

Share

About the Author

Jim Korovessis
Program Manager General Dynamics Canada
Canada Canada
Manager, Customer Training in Ottawa, ON, Canada
www.gdcanada.com

Comments and Discussions

 
QuestionDoes PicBox works for the custom Usercontrols PinmemberSharmaShruthi7-Oct-13 20:10 
AnswerRe: Does PicBox works for the custom Usercontrols PinmemberJim Korovessis20-Oct-13 8:19 
Generalplease upload the source project PingroupTricube21-May-13 19:21 
QuestionThanks! PinmemberMember 96500768-Apr-13 9:59 
QuestionConstrain Proportions PinmemberMember 773856116-Jun-12 5:10 
QuestionHow to create multiple selection on panel. Pinmembertinku313-May-12 21:04 
GeneralThank Very Much Pinmembersanaz13x5-Jan-12 23:37 
SuggestionGreat article PinmemberSudarshan.chandan@gs.com13-Oct-11 22:34 
GeneralRe: Great article Pinmembersteffen_dec4-Jun-12 3:38 
QuestionGreat patent - Any license? PinmemberMember 76904269-Oct-11 13:50 
AnswerRe: Great patent - Any license? PinmemberJim Korovessis11-Oct-11 1:39 
GeneralMy vote of 5 PinmemberBharat Mallapur14-Apr-11 1:01 
AnswerUpdated with multiple selection Pinmemberecklerpa21-Apr-09 9:17 
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
 
namespace ScreenEditor
{
///
/// This class implements sizing and moving functions for
/// runtime editing of graphic controls
///

public class ObjectSelect
{
private class DummySelect
{
public Control dummyCtl;
public Label[] lbl = new Label[8];
}
//////////////////////////////////////////////////////////////////
// PRIVATE CONSTANTS AND VARIABLES
//////////////////////////////////////////////////////////////////
 
private const int BOX_SIZE = 8;
private Color BOX_COLOR = Color.White;
//private ContainerControl m_container;
private Control m_control;
private Label[] lbl = new Label[8];
private int startl;
private int startt;
private int startw;
private int starth;
private int startx;
private int starty;
private bool dragging;
private Cursor[] arrArrow = new Cursor[] {Cursors.SizeNWSE, Cursors.SizeNS,
Cursors.SizeNESW, Cursors.SizeWE, Cursors.SizeNWSE, Cursors.SizeNS,
Cursors.SizeNESW, Cursors.SizeWE};
private Cursor oldCursor;
private Rectangle objectContainer;
 
private const int MIN_SIZE = 20;
 
//DUMMY Select labels List
private List DummySelectList = new List();
 
// Constructor creates 8 sizing handles & wires mouse events
// to each that implement sizing functions
public ObjectSelect()
{
for (int i = 0; i < 8; i++)
{
lbl[i] = new Label();
lbl[i].TabIndex = i;
lbl[i].FlatStyle = 0;
lbl[i].BorderStyle = BorderStyle.FixedSingle;
lbl[i].BackColor = BOX_COLOR;
lbl[i].Cursor = arrArrow[i];
lbl[i].Text = "";
lbl[i].BringToFront();
lbl[i].MouseDown += new MouseEventHandler(this.lbl_MouseDown);
lbl[i].MouseMove += new MouseEventHandler(this.lbl_MouseMove);
lbl[i].MouseUp += new MouseEventHandler(this.lbl_MouseUp);
}
}
 
#region PUBLIC METHODS
 
// Wires a Click event handler to the passed Control
// that attaches a pick box to the control when it is clicked
public void WireControl(Control ctl)
{
ctl.Click += new EventHandler(this.SelectControl);
}
 
//Call this when removing an object from the container, this simply
//cleans up some event handlers from the system, make sure you call
//before actually destroying the Control.
public void UnWireControl(Control ctl)
{
if (m_control == ctl)
{
//m_control will be the only one having these handlers
ctl.MouseDown -= new MouseEventHandler(this.ctl_MouseDown);
ctl.MouseMove -= new MouseEventHandler(this.ctl_MouseMove);
ctl.MouseUp -= new MouseEventHandler(this.ctl_MouseUp);
for (int i = 0; i < 8; i++)
{
lbl[i].Visible = false;
}
m_control.Cursor = oldCursor;
m_control = null;
}
 
ctl.Click -= new EventHandler(this.SelectControl);
 
RemoveDummyFromList(ctl);
}
 
//This will establish a boundry for the objects within it
public void SetScreenContainerBounds(Rectangle ScreenContainerBounds)
{
objectContainer = ScreenContainerBounds;
}
 
//Capture the Arrow Keys keyboard presses and send them here
public void MoveSelectedObject(int ArrowDirection)
{
if (m_control == null)
return;
 
int t = m_control.Top;
int l = m_control.Left;
 
switch (ArrowDirection)
{
case (int)Keys.Left:
l = l - 1;
break;
case (int)Keys.Right:
l = l + 1;
break;
case (int)Keys.Up:
t = t - 1;
break;
case (int)Keys.Down:
t = t + 1;
break;
}
 
int holdT = t;
int holdL = l;
 
int w = m_control.Width;
int h = m_control.Height;
 
int RightBoundry = objectContainer.Width + objectContainer.Left;
int BottomBoundry = objectContainer.Height + objectContainer.Top;
int LeftBoundry = objectContainer.Left;
int TopBoundry = objectContainer.Top;
 
l = (l < LeftBoundry) ? LeftBoundry : ((l + w > RightBoundry) ? RightBoundry - w : l);
t = (t < TopBoundry) ? TopBoundry : ((t + h > BottomBoundry) ? BottomBoundry - h : t);
 
m_control.Left = l;
m_control.Top = t;
 
if (holdL != l || holdT != t)
return;//we have reached the border so discontinue
 
//Now move any dummy objects
foreach (DummySelect ds in DummySelectList)
{
l = ds.dummyCtl.Left;
t = ds.dummyCtl.Top;
w = ds.dummyCtl.Width;
h = ds.dummyCtl.Height;
 
switch (ArrowDirection)
{
case (int)Keys.Left:
l = l - 1;
break;
case (int)Keys.Right:
l = l + 1;
break;
case (int)Keys.Up:
t = t - 1;
break;
case (int)Keys.Down:
t = t + 1;
break;
}
 
RightBoundry = objectContainer.Width + objectContainer.Left;
BottomBoundry = objectContainer.Height + objectContainer.Top;
LeftBoundry = objectContainer.Left;
TopBoundry = objectContainer.Top;
 
l = (l < LeftBoundry) ? LeftBoundry : ((l + w > RightBoundry) ? RightBoundry - w : l);
t = (t < TopBoundry) ? TopBoundry : ((t + h > BottomBoundry) ? BottomBoundry - h : t);
 
ds.dummyCtl.Left = l;
ds.dummyCtl.Top = t;
}
 
MoveHandles();
ShowHandles();
}

//Get an array of the controls that have a selection band
internal Control[] GetSelectedControls()
{
int controlCount = DummySelectList.Count;
if (m_control != null)//shouldnt be null if this function is called
controlCount++;
 
Control [] cArray = new Control[controlCount];
int i = 0;
foreach (DummySelect ds in DummySelectList)
{
cArray[i] = ds.dummyCtl;
i++;
}
if (m_control != null)//shouldnt be null if this function is called
cArray[i] = m_control;
 
return cArray;
}
#endregion

#region PRIVATE METHODS
 
// Attaches a pick box to the sender Control
private void SelectControl(object sender, EventArgs e)
{
if ((Control)sender == m_control)
return;//user clicked on the same control
 
if (Control.ModifierKeys == Keys.Control)
RemoveDummyFromList((Control)sender);
else
RemoveDummyFromList(null);//null will remove all dummy's
 
if (m_control is Control)
{
m_control.Cursor = oldCursor;
 
//Remove event any pre-existing event handlers appended by this class
m_control.MouseDown -= new MouseEventHandler(this.ctl_MouseDown);
m_control.MouseMove -= new MouseEventHandler(this.ctl_MouseMove);
m_control.MouseUp -= new MouseEventHandler(this.ctl_MouseUp);
 
//Is the Control Key down?
if (Control.ModifierKeys == Keys.Control)
{
//("Control Key is down");
//Then add the control that was selected to the dummy list
//and put off-color labels on it
DummySelect ds = new DummySelect();
ds.dummyCtl = m_control;
for (int i = 0; i < 8; i++)
{
ds.lbl[i] = new Label();
ds.lbl[i].TabIndex = i;
ds.lbl[i].FlatStyle = 0;
ds.lbl[i].BorderStyle = BorderStyle.FixedSingle;
ds.lbl[i].BackColor = Color.CadetBlue;
ds.lbl[i].Text = "";
ds.dummyCtl.Parent.Controls.Add(ds.lbl[i]);
ds.lbl[i].BringToFront();
ds.lbl[i].Visible = true;
}
 
DummySelectList.Add(ds);
}
 
m_control = null;
}
 
m_control = (Control)sender;
 
//Add event handlers for moving the selected control around
m_control.MouseDown += new MouseEventHandler(this.ctl_MouseDown);
m_control.MouseMove += new MouseEventHandler(this.ctl_MouseMove);
m_control.MouseUp += new MouseEventHandler(this.ctl_MouseUp);
m_control.BringToFront();
 
//Add sizing handles to Control's container (Form or PictureBox)
for (int i = 0; i < 8; i++)
{
m_control.Parent.Controls.Add(lbl[i]);
lbl[i].BringToFront();
}
 
//Position sizing handles around Control
MoveHandles();
 
//Display sizing handles
ShowHandles();
 
oldCursor = m_control.Cursor;
m_control.Cursor = Cursors.Hand;
}
 
private void RemoveDummyFromList(Control control)
{
if (control != null)
{
DummySelect dsToRemove = null;
foreach (DummySelect ds in DummySelectList)
{
if (ds.dummyCtl == control)
{
for (int i = 0; i < 8; i++)
ds.dummyCtl.Parent.Controls.Remove(ds.lbl[i]);
 
dsToRemove = ds;
break;
}
}
if (dsToRemove != null)
DummySelectList.Remove(dsToRemove);
}
else// if control is null then remove all
{
foreach (DummySelect ds in DummySelectList)
{
for (int i = 0; i < 8; i++)
ds.dummyCtl.Parent.Controls.Remove(ds.lbl[i]);
}
DummySelectList.Clear();
}
}
 
public void Remove()
{
HideHandles();
m_control.Cursor = oldCursor;
}
 
private void ShowHandles()
{
if (m_control != null)
{
for (int i = 0; i < 8; i++)
{
lbl[i].Visible = true;
}
 
//Show any dummy's labels
foreach (DummySelect ds in DummySelectList)
{
for (int i = 0; i < 8; i++)
{
ds.lbl[i].Visible = true;
}
}
}
}
 
private void HideHandles()
{
for (int i = 0; i < 8; i++)
{
lbl[i].Visible = false;
}
 
//Hide any dummy's labels
foreach (DummySelect ds in DummySelectList)
{
for (int i = 0; i < 8; i++)
{
ds.lbl[i].Visible = false;
}
}
}
 
private void MoveHandles()
{
int sX = m_control.Left - BOX_SIZE;
int sY = m_control.Top - BOX_SIZE;
int sW = m_control.Width + BOX_SIZE;
int sH = m_control.Height + BOX_SIZE;
int hB = BOX_SIZE / 2;
int[] arrPosX = new int[] {sX+hB, sX + sW / 2, sX + sW-hB, sX + sW-hB,
sX + sW-hB, sX + sW / 2, sX+hB, sX+hB};
int[] arrPosY = new int[] {sY+hB, sY+hB, sY+hB, sY + sH / 2, sY + sH-hB,
sY + sH-hB, sY + sH-hB, sY + sH / 2};
for (int i = 0; i < 8; i++)
lbl[i].SetBounds(arrPosX[i], arrPosY[i], BOX_SIZE, BOX_SIZE);
 

foreach (DummySelect ds in DummySelectList)
{
sX = ds.dummyCtl.Left - BOX_SIZE;
sY = ds.dummyCtl.Top - BOX_SIZE;
sW = ds.dummyCtl.Width + BOX_SIZE;
sH = ds.dummyCtl.Height + BOX_SIZE;
hB = BOX_SIZE / 2;
arrPosX = new int[] {sX+hB, sX + sW / 2, sX + sW-hB, sX + sW-hB, sX + sW-hB, sX + sW / 2, sX+hB, sX+hB};
arrPosY = new int[] {sY+hB, sY+hB, sY+hB, sY + sH / 2, sY + sH-hB, sY + sH-hB, sY + sH-hB, sY + sH / 2};
for (int i = 0; i < 8; i++)
ds.lbl[i].SetBounds(arrPosX[i], arrPosY[i], BOX_SIZE, BOX_SIZE);
}
}
 
/////////////////////////////////////////////////////////////////
// MOUSE EVENTS THAT IMPLEMENT SIZING OF THE PICKED CONTROL
/////////////////////////////////////////////////////////////////
 
// Store control position and size when mouse button pushed over
// any sizing handle
private void lbl_MouseDown(object sender, MouseEventArgs e)
{
dragging = true;
startl = m_control.Left;
startt = m_control.Top;
startw = m_control.Width;
starth = m_control.Height;
HideHandles();
}
 
// Size the picked control in accordance with sizing handle being dragged
// 0 1 2
// 7 3
// 6 5 4
private void lbl_MouseMove(object sender, MouseEventArgs e)
{
int l = m_control.Left;
int w = m_control.Width;
int t = m_control.Top;
int h = m_control.Height;
int RightBoundry = objectContainer.Width + objectContainer.Left;
int BottomBoundry = objectContainer.Height + objectContainer.Top;
int LeftBoundry = objectContainer.Left;
int TopBoundry = objectContainer.Top;
 
if (dragging)
{
switch (((Label)sender).TabIndex)
{
case 0: // Dragging top-left sizing box
if (t >= TopBoundry)
{
t = startt + e.Y < startt + starth - MIN_SIZE ? startt + e.Y : startt + starth - MIN_SIZE;
h = startt + starth - m_control.Top;
}
if (l >= LeftBoundry)
{
l = startl + e.X < startl + startw - MIN_SIZE ? startl + e.X : startl + startw - MIN_SIZE;
w = startl + startw - m_control.Left;
}
break;
case 1: // Dragging top-center sizing box
if (t >= TopBoundry)
{
t = startt + e.Y < startt + starth - MIN_SIZE ? startt + e.Y : startt + starth - MIN_SIZE;
h = startt + starth - m_control.Top;
}
break;
case 2: // Dragging top-right sizing box
if (l + (startw + e.X) < RightBoundry)
w = startw + e.X > MIN_SIZE ? startw + e.X : MIN_SIZE;
if (t >= TopBoundry)
{
t = startt + e.Y < startt + starth - MIN_SIZE ? startt + e.Y : startt + starth - MIN_SIZE;
h = startt + starth - m_control.Top;
}
break;
case 3: // Dragging right-middle sizing box
if (l + (startw + e.X) < RightBoundry)
w = startw + e.X > MIN_SIZE ? startw + e.X : MIN_SIZE;
break;
case 4: // Dragging right-bottom sizing box
if (l + (startw + e.X) < RightBoundry)
w = startw + e.X > MIN_SIZE ? startw + e.X : MIN_SIZE;
if (t + (starth + e.Y) < BottomBoundry)
h = starth + e.Y > MIN_SIZE ? starth + e.Y : MIN_SIZE;
break;
case 5: // Dragging center-bottom sizing box
if (t + (starth + e.Y) < BottomBoundry)
h = starth + e.Y > MIN_SIZE ? starth + e.Y : MIN_SIZE;
break;
case 6: // Dragging left-bottom sizing box
if (l >= LeftBoundry)
{
l = startl + e.X < startl + startw - MIN_SIZE ? startl + e.X : startl + startw - MIN_SIZE;
w = startl + startw - m_control.Left;
}
if (t + (starth + e.Y) <= BottomBoundry)
h = starth + e.Y > MIN_SIZE ? starth + e.Y : MIN_SIZE;
break;
case 7: // Dragging left-middle sizing box
if (l >= LeftBoundry)
{
l = startl + e.X < startl + startw - MIN_SIZE ? startl + e.X : startl + startw - MIN_SIZE;
w = startl + startw - m_control.Left;
}
break;
}
 
l = (l < LeftBoundry) ? LeftBoundry : l;
t = (t < TopBoundry) ? TopBoundry : t;
m_control.SetBounds(l, t, w, h);
}
}
 
// Display sizing handles around picked control once sizing has completed
private void lbl_MouseUp(object sender, MouseEventArgs e)
{
dragging = false;
MoveHandles();
ShowHandles();
}
 
/////////////////////////////////////////////////////////////////
// MOUSE EVENTS THAT MOVE THE PICKED CONTROL AROUND THE FORM
/////////////////////////////////////////////////////////////////
 
// Get mouse pointer starting position on mouse down and hide sizing handles
private void ctl_MouseDown(object sender, MouseEventArgs e)
{
dragging = true;
startx = e.X;
starty = e.Y;
HideHandles();
}
 
// Reposition the dragged control
private void ctl_MouseMove(object sender, MouseEventArgs e)
{
if (dragging)
{
int l = m_control.Left + e.X - startx;
int holdL = l;
int t = m_control.Top + e.Y - starty;
int holdT = t;
int w = m_control.Width;
int h = m_control.Height;
 
int RightBoundry = objectContainer.Width + objectContainer.Left;
int BottomBoundry = objectContainer.Height + objectContainer.Top;
int LeftBoundry = objectContainer.Left;
int TopBoundry = objectContainer.Top;

l = (l < LeftBoundry) ? LeftBoundry : ((l + w > RightBoundry) ? RightBoundry - w : l);
t = (t < TopBoundry) ? TopBoundry : ((t + h > BottomBoundry) ? BottomBoundry - h : t);
 
m_control.Left = l;
m_control.Top = t;
 
if (holdL != l || holdT != t)
return;//we have reached the border so discontinue
 
foreach (DummySelect ds in DummySelectList)
{
l = ds.dummyCtl.Left + e.X - startx;
t = ds.dummyCtl.Top + e.Y - starty;
w = ds.dummyCtl.Width;
h = ds.dummyCtl.Height;
 
RightBoundry = objectContainer.Width + objectContainer.Left;
BottomBoundry = objectContainer.Height + objectContainer.Top;
LeftBoundry = objectContainer.Left;
TopBoundry = objectContainer.Top;
 
l = (l < LeftBoundry) ? LeftBoundry : ((l + w > RightBoundry) ? RightBoundry - w : l);
t = (t < TopBoundry) ? TopBoundry : ((t + h > BottomBoundry) ? BottomBoundry - h : t);
 
ds.dummyCtl.Left = l;
ds.dummyCtl.Top = t;
}
}
}
 
// Display sizing handles around picked control once dragging has completed
private void ctl_MouseUp(object sender, MouseEventArgs e)
{
dragging = false;
MoveHandles();
ShowHandles();
}
#endregion
}
}

GeneralPickBox and Pictureboxes PinmemberJuwi_uk4-Mar-09 5:04 
GeneralMultiple controls drag n drop. [modified] PinmemberVB 8.020-Feb-09 1:45 
GeneralGreate code Pinmemberammar7926-Jan-09 22:04 
GeneralAdaptation for multiple selection (more than one selected control). [modified] PinmemberDiego Osorio26-Dec-08 16:43 
GeneralRe: Adaptation for multiple selection (more than one selected control). Pinmemberammar7926-Jan-09 22:12 
GeneralRe: Adaptation for multiple selection (more than one selected control). PinmemberDiego Osorio27-Jan-09 1:23 
GeneralDragging between containers Pinmemberdkalyan12-May-08 18:30 
GeneralGreat Work BUT !!!! Pinmemberzooooooz10-May-08 2:11 
GeneralResize Control @ Runtime Pinmemberferozasi22-Apr-08 12:00 
GeneralMoving with keyboard Question Pinmemberblakadm13-Apr-08 23:53 
GeneralRe: Moving with keyboard Question Pinmemberjazzyvishal21-Jul-08 19:51 
Questionhow labels are selected which are created at button click event Pinmemberjassi4u2-Aug-07 19:43 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140827.1 | Last Updated 30 Sep 2003
Article Copyright 2003 by Jim Korovessis
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid