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

Runtime Control Resizer

By , 19 Jan 2007
 

Sample Image - rtcontrolresizer.jpg

Introduction

An unfinished, simple class I started to write in response to a recent post on the microsoft.public.dotnet.languages.vb newsgroup.

Background

Not too long ago, I was browsing the VB.NET newsgroup like I normally do, and ran across a question that stumped me. The question was on how to provide the ability to resize a control during runtime. It seemed fairly straightforward, so I fired up C# (forgetting which newsgroup I read the question in!) and started typing up a quick solution - and it failed.

So I spent more and more time tweaking this late at night, and got most things worked out. Eventually, the OP stated he found a solution and wrote a CodeProject article about it. "Ah hah," I said, "I'll do the same!". And so I quit working on the code, and figured this would at least get someone else started. (Maybe, I'll finish it later - who knows?)

The Principle Components

The User Control

Nothing too complex here. I just needed a control that would represent the little white boxes that "float" around the outside of the control to be resized. The main job of the control is to just change the mouse cursor based on the position of the control (top left, top, etc.).

The Controller Class

Here, things get a little more verbose, but still not too complex. In a few sentences, the controller class first creates the necessary resize boxes (the user controls) and places them around the target control. The rest of the class simply handles the mouse-move events, and resizes the target control based on the movement of the specific resize box.

The Resize Box's Code

First off, I created an enum that listed all the available positions (relative to the target) that the resize box could have. Then, of course, I used this enum as a parameter in the control's constructor.

public enum BoxPosition
{
    Top,
    Bottom,
    Left,
    Right,
    TopLeft,
    TopRight,
    BottomLeft,
    BottomRight
}

public ResizeBox(BoxPosition position)
{
    InitializeComponent();
    this.Position = position;
}

OK, nothing complex there. Next up, I handled both the MouseEnter and MouseLeave events of the control in order to set the appropriate arrow cursor depending on the Position property (not shown) of the control. Sounds like a job for a switch statement to me!

private void ResizeBox_MouseEnter(object sender, EventArgs e)
{
    switch (this.Position)
    {
        case BoxPosition.Top:
            this.Cursor = Cursors.SizeNS;
            break;
        case BoxPosition.Bottom:
            this.Cursor = Cursors.SizeNS;
            break;
        case BoxPosition.Left:
            this.Cursor = Cursors.SizeWE;
            break;
        case BoxPosition.Right:
            this.Cursor = Cursors.SizeWE;
            break;
        case BoxPosition.TopLeft:
            this.Cursor = Cursors.SizeNWSE;
            break;
        case BoxPosition.BottomRight:
            this.Cursor = Cursors.SizeNWSE;
            break;
        case BoxPosition.TopRight:
            this.Cursor = Cursors.SizeNESW;
            break;
        case BoxPosition.BottomLeft:
            this.Cursor = Cursors.SizeNESW;
            break;
        default:
            this.Cursor = Cursors.No;
            break;
    }
}

private void ResizeBox_MouseLeave(object sender, EventArgs e)
{
    this.Cursor = Cursors.Default;
}

That pretty much takes care of the juicy bits for the user control - like I said, it's pretty simple.

The Controller Class

First of all, I need to create some variables to represent the actual resize boxes I'll place around the target.

private ResizeBox topBox;
private ResizeBox bottomBox;
private ResizeBox leftBox;
private ResizeBox rightBox;
private ResizeBox topLeftBox;
private ResizeBox topRightBox;
private ResizeBox bottomLeftBox;
private ResizeBox bottomRightBox;

In the constructor, the target control is passed as a parameter. I set this into a property, instantiate the ResizeBox objects, and map the the necessary events to the methods that will handle them. The constructor also provides an option to immediately show the resize handles.

public ResizeControl(Control target, Boolean showResizeBoxes)
{
    this._Target = target;

    target.Parent.Paint += new PaintEventHandler(Parent_Paint);

    topBox = new ResizeBox(ResizeBox.BoxPosition.Top);
    bottomBox = new ResizeBox(ResizeBox.BoxPosition.Bottom);
    leftBox = new ResizeBox(ResizeBox.BoxPosition.Left);
    rightBox = new ResizeBox(ResizeBox.BoxPosition.Right);
    topLeftBox = new ResizeBox(ResizeBox.BoxPosition.TopLeft);
    topRightBox = new ResizeBox(ResizeBox.BoxPosition.TopRight);
    bottomLeftBox = new ResizeBox(ResizeBox.BoxPosition.BottomLeft);
    bottomRightBox = new ResizeBox(ResizeBox.BoxPosition.BottomRight);

    this.topLeftBox.MouseDown += new MouseEventHandler(Boxes_MouseDown);
    this.topBox.MouseDown += new MouseEventHandler(Boxes_MouseDown);
    this.topRightBox.MouseDown += new MouseEventHandler(Boxes_MouseDown);
    this.leftBox.MouseDown += new MouseEventHandler(Boxes_MouseDown);
    this.rightBox.MouseDown += new MouseEventHandler(Boxes_MouseDown);
    this.bottomLeftBox.MouseDown += new MouseEventHandler(Boxes_MouseDown);
    this.bottomBox.MouseDown += new MouseEventHandler(Boxes_MouseDown);
    this.bottomRightBox.MouseDown += new MouseEventHandler(Boxes_MouseDown);

    this.topLeftBox.MouseMove += new MouseEventHandler(topLeftBox_MouseMove);
    this.topBox.MouseMove += new MouseEventHandler(topBox_MouseMove);
    this.topRightBox.MouseMove += new MouseEventHandler(topRightBox_MouseMove);
    this.leftBox.MouseMove += new MouseEventHandler(leftBox_MouseMove);
    this.rightBox.MouseMove += new MouseEventHandler(rightBox_MouseMove);
    this.bottomLeftBox.MouseMove += 
         new MouseEventHandler(bottomLeftBox_MouseMove);
    this.bottomBox.MouseMove += new MouseEventHandler(bottomBox_MouseMove);
    this.bottomRightBox.MouseMove += 
         new MouseEventHandler(bottomRightBox_MouseMove);

    if (showResizeBoxes)
        ShowResizeBoxes();
}

Next is the code for the main methods for the controller, the second of which you'll see is empty. Like I said, I never finished the class, just the main parts.

public void ShowResizeBoxes()
{
    PositionTopLeftBox();
    PositionTopBox();
    PositionTopRightBox();
    PositionLeftBox();
    PositionRightBox();
    PositionBottomLeftBox();
    PositionBottomBox();
    PositionBottomRightBox();
    Target.Parent.Controls.Add(topBox);
    Target.Parent.Controls.Add(bottomBox);
    Target.Parent.Controls.Add(leftBox);
    Target.Parent.Controls.Add(rightBox);
    Target.Parent.Controls.Add(topLeftBox);
    Target.Parent.Controls.Add(topRightBox);
    Target.Parent.Controls.Add(bottomLeftBox);
    Target.Parent.Controls.Add(bottomRightBox);
}

public void HideResizeBoxes()
{

}

As you can see, the ShowResizeBoxes() starts out by calling the position commands for all of the resize boxes and then adding the controls to the parent of the target control. They're basically all the same, so I'll just post the code for one.

private void PositionTopLeftBox()
{
    topLeftBox.Top = Target.Top - topLeftBox.Height - 1;
    topLeftBox.Left = Target.Left - topLeftBox.Width - 1;
}

As you saw above, the MouseDown events for all of the ResizeBoxes is mapped to the same method. This method just sets the mouse location point for tracking movement.

private Point mouseLocation;

private void Boxes_MouseDown(object sender, MouseEventArgs e)
{
    mouseLocation.X = e.X;
    mouseLocation.Y = e.Y;
}

And now for the heart of the class, the methods that actually resize the target control. Let's take the top-box as an example:

First and foremost, we need to make sure the left mouse button was the one that was clicked, so we use a simple if block. Next, I set three variables:

if (e.Button == MouseButtons.Left)
{
    Int32 newBoxTop = topBox.Top + (e.Y - mouseLocation.Y);
    Int32 oldTargetTop = Target.Top;
    Int32 newTargetHeight = Target.Height + 
         (Target.Top - (topBox.Top + topBox.Height + 1));

The first variable, newBoxTop, will be used to check whether the size of the control will increase or decrease in size. The second, oldTargetTop, just saves the current top value so later when we change the top value of the target, we will know how much we should increase/decrease the height value by. Finally, newTargetHeight is the value that the height value of the target will be after we resize it. I set it here so I can check its value to prevent the target from going below a certain size. The minimum height and width values, I had wanted to put in a property, but I was lazy and just hard-coded them in. Here's, the if block that makes sure we don't drop below the minimum size, and the code that sets the new values and re-positions the effected resize-boxes:

if (newTargetHeight > 15 || newBoxTop <= topBox.Top)
{
    Target.Top = newBoxTop + topBox.Height + 1;
    Target.Height += (oldTargetTop - Target.Top);
    topBox.Top = newBoxTop;
    PositionTopLeftBox();
    PositionTopRightBox();
    PositionLeftBox();
    PositionRightBox();
}
Target.Parent.Invalidate(new Rectangle(Target.Left - 6, 
       Target.Top - 6, Target.Width + 12, Target.Height + 12));

Here's the code for the entire method:

private void topBox_MouseMove(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Left)
    {
        Int32 newBoxTop = topBox.Top + (e.Y - mouseLocation.Y);
        Int32 oldTargetTop = Target.Top;
        Int32 newTargetHeight = Target.Height + 
              (Target.Top - (topBox.Top + topBox.Height + 1));
        
        if (newTargetHeight > 15 || newBoxTop <= topBox.Top)
        {
            Target.Top = newBoxTop + topBox.Height + 1;
            Target.Height += (oldTargetTop - Target.Top);
            topBox.Top = newBoxTop;
            PositionTopLeftBox();
            PositionTopRightBox();
            PositionLeftBox();
            PositionRightBox();
        }
        Target.Parent.Invalidate(new Rectangle(Target.Left - 6, 
               Target.Top - 6, Target.Width + 12, Target.Height + 12));
    }
}

The one call I didn't explain earlier is the Target.Parent.Invalidate(...) call. You see, I'm trapping the target's parent's Paint event in order to use some simple GDI+ to draw the dotted line connecting the ResizeBoxes.

public void Parent_Paint(object sender, PaintEventArgs e)
{
    Graphics g = e.Graphics;
    Pen pen = new Pen(Brushes.Black, 1);
    pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
    g.DrawRectangle(pen, new Rectangle(Target.Left - 3, 
          Target.Top - 3, Target.Width + 6, Target.Height + 6));
}

And that pretty much sums up the class! Basically, the user control handles the cursors, and the mouse-move events handle the actual resizing of the target control and re-positioning the resize boxes. Then, invalidating the region containing the target control forces the updating of the dotted line.

Using the Class

Nothing complex here, just create an instance of the class and pass the control you want to be resizable to the constructor. Setting the ShowResizeBoxes to show them immediately, or just calling it manually. Also, since the class implements IDisposable, using blocks could come in handy since the Dispose methods calls the HideResizeBoxes method.

new ResizeControl(this.button1, true);

ResizeControl rc = new ResizeControl(this.button1, false);
rc.ShowResizeBoxes();

using (new ResizeControl(this.button1, true))
{
    //Do whatever you want here
}

The Problems

Too many boxes!

The class doesn't work well when a control doesn't allow either the height or width property to be set. A good example of this is a single line textbox. Pass that as the target and then drag the bottom resize box down. You'll see what I mean!

Speedy Moves = Unpredictable!

The MouseMove/MouseDown events don't fire near often enough to track fast movements. As a result, the minimum height/width tests can be ignored on fast moves, causing super small controls that may not be recoverable in some cases. Also, quickly "growing" the control can cause missed events that mess up the GDI+ rectangle, and can also leave orphaned or misplaced resize boxes. It seems the boxes move, but the target never resizes and the other resize boxes don't reposition.

The Complete Code

Since I know a lot of people don't want to download a code file, for a quick look-see, I figured I'd include this:

using System;
using System.Drawing;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;

class ResizeControl : IDisposable
{
    #region "Private Controls"

    private ResizeBox topBox;
    private ResizeBox bottomBox;
    private ResizeBox leftBox;
    private ResizeBox rightBox;
    private ResizeBox topLeftBox;
    private ResizeBox topRightBox;
    private ResizeBox bottomLeftBox;
    private ResizeBox bottomRightBox;

    private class ResizeBox : UserControl
    {
        public enum BoxPosition
        {
            Top,
            Bottom,
            Left,
            Right,
            TopLeft,
            TopRight,
            BottomLeft,
            BottomRight
        }

        public ResizeBox(BoxPosition position)
        {
            InitializeComponent();
            this.Position = position;
        }

        private System.ComponentModel.IContainer components = null;

        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        private void InitializeComponent()
        {
            this.SuspendLayout();
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.BackColor = System.Drawing.Color.White;
            this.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
            this.Name = "ResizeBox";
            this.Size = new System.Drawing.Size(6, 6);
            this.MouseEnter += 
                 new System.EventHandler(this.ResizeBox_MouseEnter);
            this.MouseLeave += 
                 new System.EventHandler(this.ResizeBox_MouseLeave);
            this.ResumeLayout(false);
        }

        private BoxPosition _Position = BoxPosition.Top;
        public BoxPosition Position
        {
            get
            {
                return _Position;
            }
            set
            {
                _Position = value;
            }
        }

        private void ResizeBox_MouseEnter(object sender, EventArgs e)
        {
            switch (this.Position)
            {
                case BoxPosition.Top:
                    this.Cursor = Cursors.SizeNS;
                    break;
                case BoxPosition.Bottom:
                    this.Cursor = Cursors.SizeNS;
                    break;
                case BoxPosition.Left:
                    this.Cursor = Cursors.SizeWE;
                    break;
                case BoxPosition.Right:
                    this.Cursor = Cursors.SizeWE;
                    break;
                case BoxPosition.TopLeft:
                    this.Cursor = Cursors.SizeNWSE;
                    break;
                case BoxPosition.BottomRight:
                    this.Cursor = Cursors.SizeNWSE;
                    break;
                case BoxPosition.TopRight:
                    this.Cursor = Cursors.SizeNESW;
                    break;
                case BoxPosition.BottomLeft:
                    this.Cursor = Cursors.SizeNESW;
                    break;
                default:
                    this.Cursor = Cursors.No;
                    break;
            }
        }

        private void ResizeBox_MouseLeave(object sender, EventArgs e)
        {
            this.Cursor = Cursors.Default;
        }
    }

    #endregion

    #region "Public Methods"

    public ResizeControl(Control target, Boolean showResizeBoxes)
    {
        this._Target = target;

        target.Parent.Paint += new PaintEventHandler(Parent_Paint);

        topBox = new ResizeBox(ResizeBox.BoxPosition.Top);
        bottomBox = new ResizeBox(ResizeBox.BoxPosition.Bottom);
        leftBox = new ResizeBox(ResizeBox.BoxPosition.Left);
        rightBox = new ResizeBox(ResizeBox.BoxPosition.Right);
        topLeftBox = new ResizeBox(ResizeBox.BoxPosition.TopLeft);
        topRightBox = new ResizeBox(ResizeBox.BoxPosition.TopRight);
        bottomLeftBox = new ResizeBox(ResizeBox.BoxPosition.BottomLeft);
        bottomRightBox = new ResizeBox(ResizeBox.BoxPosition.BottomRight);

        this.topLeftBox.MouseDown += new MouseEventHandler(Boxes_MouseDown);
        this.topBox.MouseDown += new MouseEventHandler(Boxes_MouseDown);
        this.topRightBox.MouseDown += new MouseEventHandler(Boxes_MouseDown);
        this.leftBox.MouseDown += new MouseEventHandler(Boxes_MouseDown);
        this.rightBox.MouseDown += new MouseEventHandler(Boxes_MouseDown);
        this.bottomLeftBox.MouseDown += 
             new MouseEventHandler(Boxes_MouseDown);
        this.bottomBox.MouseDown += new MouseEventHandler(Boxes_MouseDown);
        this.bottomRightBox.MouseDown += 
             new MouseEventHandler(Boxes_MouseDown);

        this.topLeftBox.MouseMove += 
             new MouseEventHandler(topLeftBox_MouseMove);
        this.topBox.MouseMove += new MouseEventHandler(topBox_MouseMove);
        this.topRightBox.MouseMove += 
             new MouseEventHandler(topRightBox_MouseMove);
        this.leftBox.MouseMove += new MouseEventHandler(leftBox_MouseMove);
        this.rightBox.MouseMove += new MouseEventHandler(rightBox_MouseMove);
        this.bottomLeftBox.MouseMove += 
             new MouseEventHandler(bottomLeftBox_MouseMove);
        this.bottomBox.MouseMove += 
             new MouseEventHandler(bottomBox_MouseMove);
        this.bottomRightBox.MouseMove += 
             new MouseEventHandler(bottomRightBox_MouseMove);

        if (showResizeBoxes)
            ShowResizeBoxes();
    }

    public void Parent_Paint(object sender, PaintEventArgs e)
    {
        Graphics g = e.Graphics;
        Pen pen = new Pen(Brushes.Black, 1);
        pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
        g.DrawRectangle(pen, new Rectangle(Target.Left - 3, 
              Target.Top - 3, Target.Width + 6, Target.Height + 6));
    }

    public void ShowResizeBoxes()
    {
        PositionTopLeftBox();
        PositionTopBox();
        PositionTopRightBox();
        PositionLeftBox();
        PositionRightBox();
        PositionBottomLeftBox();
        PositionBottomBox();
        PositionBottomRightBox();
        Target.Parent.Controls.Add(topBox);
        Target.Parent.Controls.Add(bottomBox);
        Target.Parent.Controls.Add(leftBox);
        Target.Parent.Controls.Add(rightBox);
        Target.Parent.Controls.Add(topLeftBox);
        Target.Parent.Controls.Add(topRightBox);
        Target.Parent.Controls.Add(bottomLeftBox);
        Target.Parent.Controls.Add(bottomRightBox);
    }

    public void HideResizeBoxes()
    {
        topBox.Visible = false;
    }

    void IDisposable.Dispose()
    {
        HideResizeBoxes();
    }

    #endregion

    #region "Move Event Handlers"

    private Point mouseLocation;

    private void Boxes_MouseDown(object sender, MouseEventArgs e)
    {
        mouseLocation.X = e.X;
        mouseLocation.Y = e.Y;
    }

    private void topLeftBox_MouseMove(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left)
        {
            Int32 newBoxTop = topLeftBox.Top + (e.Y - mouseLocation.Y);
            Int32 oldTargetTop = Target.Top;
            Int32 newTargetHeight = Target.Height + 
                 (Target.Top - (topLeftBox.Top + topLeftBox.Height + 1));
            Int32 newBoxLeft = topLeftBox.Left + (e.X - mouseLocation.X);
            Int32 oldTargetLeft = Target.Left;
            Int32 newTargetWidth = Target.Width + (oldTargetLeft - Target.Left);

            if (newTargetWidth > 30 || newBoxLeft <= topLeftBox.Left)
            {
                Target.Left = newBoxLeft + topLeftBox.Width + 1;
                Target.Width += (oldTargetLeft - Target.Left);
                topLeftBox.Left = newBoxLeft;
                PositionTopLeftBox();
                PositionBottomLeftBox();
                PositionTopBox();
                PositionBottomBox();
                PositionLeftBox();
            }
            if (newTargetHeight > 15 || newBoxTop <= topBox.Top)
            {
                Target.Top = newBoxTop + topLeftBox.Height + 1;
                Target.Height += (oldTargetTop - Target.Top);
                topLeftBox.Top = newBoxTop;
                PositionTopLeftBox();
                PositionTopRightBox();
                PositionLeftBox();
                PositionRightBox();
                PositionTopBox();
            }
            Target.Parent.Invalidate(new Rectangle(Target.Left - 6, 
                   Target.Top - 6, Target.Width + 12, Target.Height + 12));
        }
    }

    private void topBox_MouseMove(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left)
        {
            Int32 newBoxTop = topBox.Top + (e.Y - mouseLocation.Y);
            Int32 oldTargetTop = Target.Top;
            Int32 newTargetHeight = Target.Height + 
                 (Target.Top - (topBox.Top + topBox.Height + 1));
            
            if (newTargetHeight > 15 || newBoxTop <= topBox.Top)
            {
                Target.Top = newBoxTop + topBox.Height + 1;
                Target.Height += (oldTargetTop - Target.Top);
                topBox.Top = newBoxTop;
                PositionTopLeftBox();
                PositionTopRightBox();
                PositionLeftBox();
                PositionRightBox();
            }
            Target.Parent.Invalidate(new Rectangle(Target.Left - 6, 
                   Target.Top - 6, Target.Width + 12, Target.Height + 12));
        }
    }

    private void topRightBox_MouseMove(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left)
        {
            Int32 newBoxTop = topRightBox.Top + (e.Y - mouseLocation.Y);
            Int32 oldTargetTop = Target.Top;
            Int32 newTargetHeight = Target.Height + 
                  (Target.Top - (topRightBox.Top + topRightBox.Height + 1));
            Int32 newBoxLeft = topRightBox.Left + (e.X - mouseLocation.X);
            Int32 newTargetWidth = topRightBox.Left - Target.Left - 1;

            if (newTargetWidth > 30 || newBoxLeft >= topRightBox.Left)
            {
                Target.Width = newTargetWidth;
                topRightBox.Left = newBoxLeft;
                PositionBottomRightBox();
                PositionTopBox();
                PositionBottomBox();
            }
            if (newTargetHeight > 15 || newBoxTop <= topRightBox.Top)
            {
                Target.Top = newBoxTop + topRightBox.Height + 1;
                Target.Height += (oldTargetTop - Target.Top);
                topRightBox.Top = newBoxTop;
                PositionTopLeftBox();
                PositionLeftBox();
                PositionRightBox();
            }
            Target.Parent.Invalidate(new Rectangle(Target.Left - 6, 
                   Target.Top - 6, Target.Width + 12, Target.Height + 12));
        }
    }

    private void leftBox_MouseMove(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left)
        {
            Int32 newBoxLeft = leftBox.Left + (e.X - mouseLocation.X);
            Int32 oldTargetLeft = Target.Left;
            Int32 newTargetWidth = Target.Width + (oldTargetLeft - Target.Left);

            if (newTargetWidth > 30 || newBoxLeft <= leftBox.Left)
            {
                Target.Left = newBoxLeft + leftBox.Width + 1;
                Target.Width += (oldTargetLeft - Target.Left);
                leftBox.Left = newBoxLeft;
                PositionTopLeftBox();
                PositionBottomLeftBox();
                PositionTopBox();
                PositionBottomBox();
            }
            Target.Parent.Invalidate(new Rectangle(Target.Left - 6, 
                   Target.Top - 6, Target.Width + 12, Target.Height + 12));
        }
    }

    private void rightBox_MouseMove(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left)
        {
            Int32 newBoxLeft = rightBox.Left + (e.X - mouseLocation.X);
            Int32 newTargetWidth = rightBox.Left - Target.Left - 1;

            if (newTargetWidth > 30 || newBoxLeft >= rightBox.Left)
            {
                Target.Width = newTargetWidth;
                rightBox.Left = newBoxLeft;
                PositionTopRightBox();
                PositionBottomRightBox();
                PositionTopBox();
                PositionBottomBox();
            }
            Target.Parent.Invalidate(new Rectangle(Target.Left - 6, 
                   Target.Top - 6, Target.Width + 12, Target.Height + 12));
        }
    }

    private void bottomLeftBox_MouseMove(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left)
        {
            Int32 newBoxTop = bottomLeftBox.Top + (e.Y - mouseLocation.Y);
            Int32 newTargetHeight = bottomLeftBox.Top - Target.Top - 1;
            Int32 newBoxLeft = bottomLeftBox.Left + (e.X - mouseLocation.X);
            Int32 oldTargetLeft = Target.Left;
            Int32 newTargetWidth = Target.Width + (oldTargetLeft - Target.Left);

            if (newTargetWidth > 30 || newBoxLeft <= bottomLeftBox.Left)
            {
                Target.Left = newBoxLeft + bottomLeftBox.Width + 1;
                Target.Width += (oldTargetLeft - Target.Left);
                bottomLeftBox.Left = newBoxLeft;
                PositionTopLeftBox();
                PositionTopBox();
                PositionBottomBox();
            }
            if (newTargetHeight > 15 || newBoxTop >= bottomLeftBox.Top)
            {
                Target.Height = newTargetHeight;
                bottomLeftBox.Top = newBoxTop;
                PositionBottomRightBox();
                PositionLeftBox();
                PositionRightBox();
            }
            Target.Parent.Invalidate(new Rectangle(Target.Left - 6, 
                   Target.Top - 6, Target.Width + 12, Target.Height + 12));
        }
    }

    private void bottomBox_MouseMove(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left)
        {
            Int32 newBoxTop = bottomBox.Top + (e.Y - mouseLocation.Y);
            Int32 newTargetHeight = bottomBox.Top - Target.Top - 1;

            if (newTargetHeight > 15 || newBoxTop >= bottomBox.Top)
            {
                Target.Height = newTargetHeight;
                bottomBox.Top = newBoxTop;
                PositionBottomLeftBox();
                PositionBottomRightBox();
                PositionLeftBox();
                PositionRightBox();
            }
            Target.Parent.Invalidate(new Rectangle(Target.Left - 6, 
                   Target.Top - 6, Target.Width + 12, Target.Height + 12));
        }
    }

    private void bottomRightBox_MouseMove(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left)
        {
            Int32 newBoxTop = bottomRightBox.Top + (e.Y - mouseLocation.Y);
            Int32 newTargetHeight = bottomRightBox.Top - Target.Top - 1;
            Int32 newBoxLeft = bottomRightBox.Left + (e.X - mouseLocation.X);
            Int32 newTargetWidth = bottomRightBox.Left - Target.Left - 1;

            if (newTargetWidth > 30 || newBoxLeft >= bottomRightBox.Left)
            {
                Target.Width = newTargetWidth;
                bottomRightBox.Left = newBoxLeft;
                PositionTopRightBox();
                PositionTopBox();
                PositionBottomBox();
            }
            if (newTargetHeight > 15 || newBoxTop >= bottomRightBox.Top)
            {
                Target.Height = newTargetHeight;
                bottomRightBox.Top = newBoxTop;
                PositionBottomLeftBox();
                PositionLeftBox();
                PositionRightBox();
            }
            Target.Parent.Invalidate(new Rectangle(Target.Left - 6, 
                   Target.Top - 6, Target.Width + 12, Target.Height + 12));
        }
    }

    #endregion

    #region "Positioning Commands"

    private void PositionTopLeftBox()
    {
        topLeftBox.Top = Target.Top - topLeftBox.Height - 1;
        topLeftBox.Left = Target.Left - topLeftBox.Width - 1;
    }

    private void PositionTopBox()
    {
        topBox.Top = Target.Top - topBox.Height - 1;
        topBox.Left = Target.Left + (Target.Width / 2) - (topBox.Width / 2);
    }

    private void PositionTopRightBox()
    {
        topRightBox.Top = Target.Top - topRightBox.Height - 1;
        topRightBox.Left = Target.Left + Target.Width + 1;
    }

    private void PositionLeftBox()
    {
        leftBox.Top = Target.Top + (Target.Height / 2) - (leftBox.Height / 2);
        leftBox.Left = Target.Left - leftBox.Width - 1;
    }

    private void PositionRightBox()
    {
        rightBox.Top = Target.Top + (Target.Height / 2) - (rightBox.Height / 2);
        rightBox.Left = Target.Left + Target.Width + 1;
    }

    private void PositionBottomLeftBox()
    {
        bottomLeftBox.Top = Target.Top + Target.Height + 1;
        bottomLeftBox.Left = Target.Left - leftBox.Width - 1;
    }

    private void PositionBottomBox()
    {
        bottomBox.Top = Target.Top + Target.Height + 1;
        bottomBox.Left = Target.Left + 
                 (Target.Width / 2) - (bottomBox.Width / 2);
    }

    private void PositionBottomRightBox()
    {
        bottomRightBox.Top = Target.Top + Target.Height + 1;
        bottomRightBox.Left = Target.Left + Target.Width + 1;
    }

    #endregion

    #region "Properties"

    private Control _Target;
    public Control Target
    {
        get
        {
            if (_Target == null)
                _Target = new Control();
            return _Target;
        }
    }

    #endregion
}

History

  • 1/19/2007 - First release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Seth Rowe
Web Developer
United States United States
Member
While young, at only 22 years of age, I constantly am trying to improve my programming skill set. I currently work for an IT technology leader as an ASP.NET/.NET developer in Columbus, Ohio. I was also awarded Microsoft MVP status for Visual Basic for the first time in 2008. It is a great honor to join the team of MVP's worldwide.
 
Apart from programming, I spend all my time with my lovely and supportive wife (who is carrying our first child). Besides from programming, you can also find me running around the virtual world of World of Warcraft.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralHmm: I downloaded and tried but...membertwesterd6 Jun '08 - 22:13 
Whenever I reize the control, the resize rectangle leaves artifacts/ghosts. The painting does not seem to be good. I don't know if it is invalidating improperly or what, but I couldn't ever use a control that ghosts so badly.
 
Doesn't anyone else see the ghosting when resizing?
GeneralRe: Hmm: I downloaded and tried but...membertwesterd6 Jun '08 - 22:18 
Ohh... and I see the handles "falling off" too.
 
WOW!
 
This is nowhere near ready. I like the idea, but... ummm... nooo!!!
 
I suggest reading up on GDI+ and work with the GraphicsPath directly.
 
Here is a great link:
 
http://www.bobpowell.net/[^]
GeneralRe: Hmm: I downloaded and tried but...memberSeth Rowe7 Jun '08 - 2:39 
LOL!
 
I'm glad you read the article clearly, it's clearly noted that I wrote this as a proof of concept and I never intended it to be ready for production. The items you listed are problems I already knew about and I attempted to say so in the article:
 
"An unfinished, simple class"
"And so I quit working on the code, and figured this would at least get someone else started"
"I never finished the class"
"that mess up the GDI+ rectangle, and can also leave orphaned or misplaced resize boxes"
 
I'm not too sure how I could mark this any more clearly that it's not ready to be used? I really suggest you read an article before posting comments saying the same thing that I do about it not working.
 
As far as finishing the class, I have no plans in the near or far future to do so. The purpose of the code was to prove out a possible method for handling runtime resizing for a person on the newsgroups, not to provide production code. And as far as I'm concerned, this article met my goals (proof of concept and a starting point for others) and I'm happy with it as it stands. If you would like to put in the effort to finishing the code, you certainly have a right to do so (as a matter of fact the code is here to encourage others to finish it if they deem it viable). If you do finish this, or come up with your own way, I highly suggest you create an article here and show others what you did, though if you finish someone else's code be sure to leave the appropriate acknowledgments.
 
Also, I am very famaliar with Bob's site, having suggested it to many people who are getting started with the System.Drawing namespace and the other GDI+ tasks. But as for myself I personally do not do any more than simple GDI+, since I am pretty much exclusive to web developement and rarely have a need for it.
 
Thanks,
 
Seth Rowe [MVP]
GeneralRe: Hmm: I downloaded and tried but...membertwesterd7 Jun '08 - 7:20 
> I'm glad you read the article clearly, it's clearly noted that I wrote this as a proof of concept and I never intended it to be ready for production. The items you listed are problems I already knew about and I attempted to say so in the article:
 
"An unfinished, simple class"
"And so I quit working on the code, and figured this would at least get someone else started"
"I never finished the class"
"that mess up the GDI+ rectangle, and can also leave orphaned or misplaced resize boxes"
 
I'm not too sure how I could mark this any more clearly that it's not ready to be used? I really suggest you read an article before posting comments saying the same thing that I do about it not working.
 

You are correct, I should have read the article more carefully. It was late and I was not in the best frame of mind. I am really not trying to put you down and I am sorry for phrasing my reply the way I did. It is nice to have people take the time to write and post articles here even if they are not complete.
 
As far as the issues I mentioned: ghosting and moving handles/ grips:
 

I believe the problem is related to how you isolated each handle (grip). GDI+ always works with the current snapshot/ Graphics object. In your code, you allow each grip to manage its own mouse events and - importantly - invalidate (redraw) the graphics area respective of each grip point. I belive this is the problem. The control should be draw as a unit, a complete area comprised of all the entire control and the area that includes all grip points. Therefore, the mouse move events should be calulating hit points to discern which grip point is selected. This should trigger the correct code to paint the correct cursor, calaculate the appropriate resize adjustments, and finally - should draw the area related to the entire area. I belive that what is happening is that mouse events for each grips cause on calculation to complete and then invalidate individually allowing the math calulations and overall drawing of the graphics to out of sync.
 
I suggest tracking an invisible area/region for the entire control including the grip points and performing adjustments for the entire area using one mouse down and one mouse move event for the entire control. Essentially, the control should be divided into 9 grid areas (rectangles) representing each region for each grip area. It appears to me that each grip rectangle is handled independantly of each other (separate mouse events) causing the issues identified.
GeneralRe: Hmm: I downloaded and tried but...memberSeth Rowe7 Jun '08 - 15:18 
No offense taken to your earlier comments, being in the online community as much as I am I'm used to "code grazing" and normally don't take it personally.
 
Thanks for the great comments about the GDI+ problems, hopefully they will help someone else use this code to it's full potential.
 
Thanks,
 
Seth Rowe [MVP]
GeneralRe: Hmm: I downloaded and tried but...membertwesterd8 Jun '08 - 0:35 
I FOUND THE ANSWER!!
 
Very long story short: This is a Windows problem. Any control (not a form) trying to resize within a MouseMove event that contains will receive two paint messages. It is virtually impossible using regular code to get rid of ghosting/ flicker. You can if you control the entire Graphics, but if you create a custom UserControl and drag other controls onto it - forget it!
 
However, double messages are NOT sent to forms... thus, the answer is to send messages that tell .NET to treat the control as a form, not a control.
 
This is where I found the answer:
 
http://slingkid.blogsome.com/2007/08/17/resizing-and-dragging/[^]
 
There is a link to a VS solution that demonstrates how to resize controls properly within the link above.
 
Here is the direct link to the solution:
 
http://teleios.bz/download/SizablePictureBox.zip[^]
 

Please, try it - it's absolutely beautiful!
 
I have now added this code to a UserControl and any UserControl implementing this code can now be resized and moved at runtime by the end-user flicker-free.
 
SWEET!!
GeneralRe: Hmm: I downloaded and tried but...memberSeth Rowe8 Jun '08 - 5:33 
Very interesting, thanks for providing the link so that others can get more use out of this article.
 
Thanks,
 
Seth Rowe [MVP]

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

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 19 Jan 2007
Article Copyright 2007 by Seth Rowe
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid