Click here to Skip to main content
15,893,381 members
Articles / Programming Languages / C#
Article

Simple Runtime Control Sizing and Dragging Class

Rate me:
Please Sign up or sign in to vote.
4.90/5 (73 votes)
29 Sep 2003Public Domain2 min read 228.2K   5.6K   99   65
Sample and brief description of simple class that enables sizing and dragging of controls on a form

Image 1

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:

C#
//(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.

C#
//(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


Written By
Program Manager General Dynamics Mission Systems Canada
Canada Canada
Manager, Customer Training in Ottawa, ON, Canada
www.gdcanada.com

Comments and Discussions

 
QuestionDelete control Issue back labels stays (runtime) Pin
Omkaara27-Nov-15 2:22
Omkaara27-Nov-15 2:22 
QuestionDoes PicBox works for the custom Usercontrols Pin
SharmaShruthi7-Oct-13 20:10
SharmaShruthi7-Oct-13 20:10 
AnswerRe: Does PicBox works for the custom Usercontrols Pin
Jim Korovessis20-Oct-13 8:19
Jim Korovessis20-Oct-13 8:19 
Generalplease upload the source project Pin
Tricube21-May-13 19:21
Tricube21-May-13 19:21 
QuestionThanks! Pin
Member 96500768-Apr-13 9:59
Member 96500768-Apr-13 9:59 
QuestionConstrain Proportions Pin
Member 773856116-Jun-12 5:10
Member 773856116-Jun-12 5:10 
QuestionHow to create multiple selection on panel. Pin
tinku313-May-12 21:04
tinku313-May-12 21:04 
GeneralThank Very Much Pin
sanaz13x5-Jan-12 23:37
sanaz13x5-Jan-12 23:37 
SuggestionGreat article Pin
Sudarshan Chandan13-Oct-11 22:34
Sudarshan Chandan13-Oct-11 22:34 
GeneralRe: Great article Pin
steffen_dec4-Jun-12 3:38
steffen_dec4-Jun-12 3:38 
QuestionGreat patent - Any license? Pin
Member 76904269-Oct-11 13:50
Member 76904269-Oct-11 13:50 
AnswerRe: Great patent - Any license? Pin
Jim Korovessis11-Oct-11 1:39
Jim Korovessis11-Oct-11 1:39 
GeneralMy vote of 5 Pin
Bharat Mallapur14-Apr-11 1:01
Bharat Mallapur14-Apr-11 1:01 
AnswerUpdated with multiple selection Pin
Patrick Eckler21-Apr-09 9:17
Patrick Eckler21-Apr-09 9:17 
GeneralPickBox and Pictureboxes Pin
Juwi_uk4-Mar-09 5:04
Juwi_uk4-Mar-09 5:04 
GeneralMultiple controls drag n drop. [modified] Pin
VB 8.020-Feb-09 1:45
VB 8.020-Feb-09 1:45 
GeneralGreate code Pin
ammar7926-Jan-09 22:04
ammar7926-Jan-09 22:04 
GeneralAdaptation for multiple selection (more than one selected control). [modified] Pin
Diego Osorio26-Dec-08 16:43
Diego Osorio26-Dec-08 16:43 
Hi all. Thank you Jim for your great work Wink | ;)

I'm developing a designer that use your code. But, I had a little issue: I need to select more than one control, to allow resizing and moving in group.

I send your code with my own implementation for that multiple selection (C#, VS 2005). I hope this help to someone (sorry for the long message):

/// [summary]
/// This class implements sizing and moving functions for
/// runtime editing of graphic controls
/// [/summary]
public class PickBox
{
    //////////////////////////////////////////////////////////////////
    // PRIVATE CONSTANTS AND VARIABLES
    //////////////////////////////////////////////////////////////////

    private const int BOX_SIZE = 8;
    private Color BOX_COLOR = Color.White;
    // private ContainerControl m_container;
    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 bool moved;
    private Cursor[] arrArrow = new Cursor[] {Cursors.SizeNWSE, Cursors.SizeNS,
        Cursors.SizeNESW, Cursors.SizeWE, Cursors.SizeNWSE, Cursors.SizeNS,
        Cursors.SizeNESW, Cursors.SizeWE};

    private const int MIN_SIZE = 20;

    //
    // Constructor creates 8 sizing handles & wires mouse events
    // to each that implement sizing functions
    //
    public PickBox()
    {
        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);
        }
    }

    //////////////////////////////////////////////////////////////////
    // 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);
    }

    private Hashtable SelectedControlCursors = new Hashtable();

    private List[Control] _SelectedControls = new List[Control]();
    /// [summary]
    /// Get the current selected controls.
    /// [/summary]
    public List[Control] SelectedControls
    {
        get
        {
            return _SelectedControls;
        }
    }

    /// [summary]
    /// Deselect all controls.
    /// [/summary]
    public void ClearSelection()
    {
        while (SelectedControls.Count > 0)
        {
            RemoveControl(SelectedControls[0]);
        }
        HideHandles();
    }

    /// [summary]
    /// Return true if the current selection has no controls.
    /// [/summary]
    private bool IsEmptySelection
    {
        get
        {
            return SelectedControls.Count == 0;
        }
    }

    /////////////////////////////////////////////////////////////////
    // PRIVATE METHODS
    /////////////////////////////////////////////////////////////////

    /// [summary]
    /// Add or remove a control from the current selection.
    /// [/summary]
    private void AddOrRemoveControl(Control c)
    {
        if (SelectedControls.Contains(c))
            RemoveControl(c);
        else
            AddControl(c);
    }

    private void AddControl(Control c)
    {
        //Add event handlers for moving the current selected controls around.
        c.MouseDown += new MouseEventHandler(this.ctl_MouseDown);
        c.MouseMove += new MouseEventHandler(this.ctl_MouseMove);
        c.MouseUp += new MouseEventHandler(this.ctl_MouseUp);

        // Remove click handler
        c.Click -= new EventHandler(this.SelectControl);

        // Register the current cursor of the added control for restoring it later.
        SelectedControlCursors.Add(c, c.Cursor);

        // Change cursor of the control to the "moving" cursor.
        c.Cursor = Cursors.SizeAll;

        // Add to current selection
        SelectedControls.Add(c);
    }

    private void RemoveControl(Control c)
    {
        // Restore cursor of the control.
        c.Cursor = (Cursor)SelectedControlCursors[c];
        SelectedControlCursors.Remove(c);

        //Remove event any pre-existing event handlers appended by this class
        c.MouseDown -= new MouseEventHandler(this.ctl_MouseDown);
        c.MouseMove -= new MouseEventHandler(this.ctl_MouseMove);
        c.MouseUp -= new MouseEventHandler(this.ctl_MouseUp);

        // Restore click handler
        c.Click += new EventHandler(this.SelectControl);

        // Remove from current selection
        SelectedControls.Remove(c);
    }

    /// [summary]
    /// Get the current selection boundary, on an empty rectangle if the current
    /// selection is also empty.
    /// [/summary]
    private Rectangle GetSelectionBounds()
    {
        if (SelectedControls.Count == 0)
        {
            return Rectangle.Empty;
        }

        int l = int.MaxValue;
        int r = int.MinValue;
        int t = int.MaxValue;
        int b = int.MinValue;
        foreach (Control c in SelectedControls)
        {
            l = Math.Min(l, c.Left);
            r = Math.Max(r, c.Right);
            t = Math.Min(t, c.Top);
            b = Math.Max(b, c.Bottom);
        }
        return new Rectangle(l, t, r - l, b - t);
    }

    //
    // Attaches a pick box to the sender Control
    //
    private void SelectControl(object sender, EventArgs e)
    {
        Control c = (Control)sender;

        AddOrRemoveControl(c);

        if (IsEmptySelection)
        {
            HideHandles();
            return;
        }

        //Add sizing handles to Control's container (Form or PictureBox)
        Control container = SelectedControls[0].Parent;
        for (int i = 0; i < 8; i++)
        {
            if (lbl[i].Parent == null) container.Controls.Add(lbl[i]);
            lbl[i].BringToFront();
        }

        //Position sizing handles around Control
        MoveHandles();

        //Display sizing handles
        ShowHandles();
    }

    private void ShowHandles()
    {
        if (!IsEmptySelection)
        {
            for (int i = 0; i < 8; i++)
            {
                lbl[i].Visible = true;
            }
        }
    }

    private void HideHandles()
    {
        for (int i = 0; i < 8; i++)
        {
            lbl[i].Visible = false;
        }
    }

    private void MoveHandles()
    {
        Rectangle bounds = GetSelectionBounds();

        int sX = bounds.Left - BOX_SIZE;
        int sY = bounds.Top - BOX_SIZE;
        int sW = bounds.Width + BOX_SIZE;
        int sH = bounds.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);
    }

    /////////////////////////////////////////////////////////////////
    // 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;
        Rectangle bounds = GetSelectionBounds();
        startl = bounds.Left;
        startt = bounds.Top;
        startw = bounds.Width;
        starth = bounds.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)
    {
        Rectangle bounds = GetSelectionBounds();
        int l = bounds.Left;
        int w = bounds.Width;
        int t = bounds.Top;
        int h = bounds.Height;
        if (dragging)
        {
            switch (((Label)sender).TabIndex)
            {
                case 0: // Dragging top-left sizing box
                    l = startl + e.X < startl + startw - MIN_SIZE ? startl + e.X : startl + startw - MIN_SIZE;
                    t = startt + e.Y < startt + starth - MIN_SIZE ? startt + e.Y : startt + starth - MIN_SIZE;
                    w = startl + startw - bounds.Left;
                    h = startt + starth - bounds.Top;
                    break;
                case 1: // Dragging top-center sizing box
                    t = startt + e.Y < startt + starth - MIN_SIZE ? startt + e.Y : startt + starth - MIN_SIZE;
                    h = startt + starth - bounds.Top;
                    break;
                case 2: // Dragging top-right sizing box
                    w = startw + e.X > MIN_SIZE ? startw + e.X : MIN_SIZE;
                    t = startt + e.Y < startt + starth - MIN_SIZE ? startt + e.Y : startt + starth - MIN_SIZE;
                    h = startt + starth - bounds.Top;
                    break;
                case 3: // Dragging right-middle sizing box
                    w = startw + e.X > MIN_SIZE ? startw + e.X : MIN_SIZE;
                    break;
                case 4: // Dragging right-bottom sizing box
                    w = startw + e.X > MIN_SIZE ? startw + e.X : MIN_SIZE;
                    h = starth + e.Y > MIN_SIZE ? starth + e.Y : MIN_SIZE;
                    break;
                case 5: // Dragging center-bottom sizing box
                    h = starth + e.Y > MIN_SIZE ? starth + e.Y : MIN_SIZE;
                    break;
                case 6: // Dragging left-bottom sizing box
                    l = startl + e.X < startl + startw - MIN_SIZE ? startl + e.X : startl + startw - MIN_SIZE;
                    w = startl + startw - bounds.Left;
                    h = starth + e.Y > MIN_SIZE ? starth + e.Y : MIN_SIZE;
                    break;
                case 7: // Dragging left-middle sizing box
                    l = startl + e.X < startl + startw - MIN_SIZE ? startl + e.X : startl + startw - MIN_SIZE;
                    w = startl + startw - bounds.Left;
                    break;
            }
            l = (l < 0) ? 0 : l;
            t = (t < 0) ? 0 : t;

            ChangeSelectionBounds(l, t, w, h);
        }
    }

    /// [summary]
    /// Change the current selection bounds (therefore, changing positions and sizes of each control
    /// in the selection).
    /// [/summary]
    private void ChangeSelectionBounds(int l, int t, int w, int h)
    {
        Rectangle oldB = GetSelectionBounds();

        int deltaX = l - oldB.Left;
        int deltaY = t - oldB.Top;
        int deltaW = w - oldB.Width;
        int deltaH = h - oldB.Height;

        // Change location and size of each select control.
        foreach(Control c in SelectedControls)
        {
            c.SetBounds(c.Left + deltaX, c.Top + deltaY, c.Width + deltaW, c.Height + deltaH);
        }
    }

    //
    // 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;
        moved = false;
        startx = e.X;
        starty = e.Y;
        HideHandles();
    }

    //
    // Reposition the dragged control
    // (Updated for global reposition -on each selected control)
    //
    private void ctl_MouseMove(object sender, MouseEventArgs e)
    {
        if (dragging)
        {
            int deltaX = e.X - startx;
            int deltaY = e.Y - starty;

            Rectangle bounds = GetSelectionBounds();
            Control parent = SelectedControls[0].Parent;

            if (bounds.Left + deltaX < 0)
                deltaX = bounds.Left * -1;
            if (bounds.Top + deltaY < 0)
                deltaY = bounds.Top * -1;
            if (bounds.Right + deltaX > parent.ClientRectangle.Width)
                deltaX = parent.ClientRectangle.Width - bounds.Right;
            if (bounds.Bottom + deltaY > parent.ClientRectangle.Height)
                deltaY = parent.ClientRectangle.Height - bounds.Bottom;

            foreach (Control c in SelectedControls)
            {
                c.Location = new Point(c.Left + deltaX, c.Top + deltaY);
            }

            if (deltaX != 0 || deltaY != 0)
            {
                moved = true;
            }
        }
    }

    //
    // Display sizing handles around picked control once dragging has completed
    //
    private void ctl_MouseUp(object sender, MouseEventArgs e)
    {
        if (dragging && !moved) RemoveControl((Control)sender);

        dragging = false;
        moved = false;
        MoveHandles();
        ShowHandles();
    }
}


Regards,

Diego

modified on Friday, December 26, 2008 10:49 PM

GeneralRe: Adaptation for multiple selection (more than one selected control). Pin
ammar7926-Jan-09 22:12
ammar7926-Jan-09 22:12 
GeneralRe: Adaptation for multiple selection (more than one selected control). Pin
Diego Osorio27-Jan-09 1:23
Diego Osorio27-Jan-09 1:23 
GeneralDragging between containers Pin
dkalyan12-May-08 18:30
dkalyan12-May-08 18:30 
GeneralGreat Work BUT !!!! Pin
Ziad Khoury10-May-08 2:11
Ziad Khoury10-May-08 2:11 
GeneralResize Control @ Runtime Pin
ferozasi22-Apr-08 12:00
professionalferozasi22-Apr-08 12:00 
GeneralMoving with keyboard Question Pin
blakadm13-Apr-08 23:53
blakadm13-Apr-08 23:53 
GeneralRe: Moving with keyboard Question Pin
jazzyvishal21-Jul-08 19:51
jazzyvishal21-Jul-08 19:51 

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

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