Search code examples
c#.netwinformsgdi+picturebox

Creating Custom Picturebox with Draggable and Resizable Selection Window


I'm using the following code to draw a selection rectangle over a picturebox and allow the user to select and drag it to the desired position.

What I intend to achieve is to allow the user to adjust the rectangle size by implementing option to adjust the rectangle size. Currently I have managed to achieve the following.

How to solve this issue?

enter image description here

public class DraggablePictureBox : PictureBox
{
    Boolean hit1 = false, hit2 = false;
    public Boolean notagimg = true;
    public Boolean editedflag = false;
    public Boolean notext = false;
    public Boolean tdrawflag = false, tdrawflag2 = false;
    Bitmap l;
    public Form1 LaunchOrigin2 { get; set; }
    public Point point = new Point(0, 0);
    public Point point2 = new Point(0, 0);
    public int sizemode = 1;
    public DraggablePictureBox()
    {
        this.Invalidate();
    }
    protected override void OnMouseMove(MouseEventArgs e)
    {
        this.Cursor = Cursors.Arrow;
        if (e.Button == MouseButtons.Left)
        {
            point = e.Location;
            base.OnMouseDown(e);
            this.Invalidate();
        }
    }
    protected override void OnMouseDown(MouseEventArgs e)
    {
        PointF x = new PointF(e.X, e.Y);
        Pen p = new Pen(Brushes.Red, 2f);
        RectangleF rect2 = new RectangleF(1, 1, 1, 1);
        RectangleF rect = new RectangleF(1, 1, 1, 1);
        if (e.Button == MouseButtons.Left)
        {
            this.Cursor = Cursors.Hand;
            //Creating Rectangles to check to find the selected object
            if (notext == false)
            {
                rect = new RectangleF(point, new Size(400,400));
            }
            if (rect.Contains(x) && notext == false)
            {
                hit1 = true;
            }
            if (hit1 == true )
            {
                this.Invalidate();
            }
            this.Invalidate();
        }
    }
    protected override void OnMouseUp(MouseEventArgs e)
    {
        base.OnMouseUp(e);
        tdrawflag = false;
    }
        if (e.Button == MouseButtons.Left)
        {
            point = e.Location;
            base.OnMouseDown(e);
            this.Invalidate();
        }
    }
    protected override void OnPaint(PaintEventArgs pe)
    {
        base.OnPaint(pe);
        Pen p = new Pen(Brushes.Red, 5);
        p.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
        Pen p2 = new Pen(Brushes.LightYellow, 5);
        p2.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
        if (!hit1)
        {
            pe.Graphics.DrawRectangle(p, new Rectangle(point, new Size(400, 400)));
        }
        else
        {
            pe.Graphics.DrawRectangle(p2, new Rectangle(point, new Size(400, 400)));
            hit1 = false;
        }
    }
}

Solution

  • You have different options:

    • You can draw a resizable frame on the picture box
    • You can create a resizable control and add it to picture box

    In this answer, I've taken the second option to be able to use built-in sizing features of the controls. Here is a screen capture which shows how it looks like in action:

    enter image description here

    Example - Creating a Frame Control

    As an example, I'll create a resizable control and will add it to the picture box.

    using System;
    using System.Drawing;
    using System.Windows.Forms;
    public class FrameControl : Control
    {
        public FrameControl()
        {
            SetStyle(ControlStyles.SupportsTransparentBackColor, true);
            DoubleBuffered = true;
            ResizeRedraw = true;
            BackColor = Color.Transparent;
        }
        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            using (var p = new Pen(Color.Black, 4))
            {
                p.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
                e.Graphics.DrawRectangle(p, 0, 0, Width - 1, Height - 1);
            }
        }
        const int WM_NCHITTEST = 0x84;
        const int WM_SETCURSOR = 0x20;
        const int WM_NCLBUTTONDBLCLK = 0xA3;
        protected override void WndProc(ref Message m)
        {
            int borderWidth = 10;
            if (m.Msg == WM_SETCURSOR)  /*Setting cursor to SizeAll*/
            {
                if ((m.LParam.ToInt32() & 0xffff) == 0x2 /*Move*/)
                {
                    Cursor.Current = Cursors.SizeAll;
                    m.Result = (IntPtr)1;
                    return;
                }
            }
            if ((m.Msg == WM_NCLBUTTONDBLCLK)) /*Disable Mazimiz on Double click*/
            {
                m.Result = (IntPtr)1;
                return;
            }
            base.WndProc(ref m);
            if (m.Msg == WM_NCHITTEST)
            {
                var pos = PointToClient(new Point(m.LParam.ToInt32() & 0xffff,
                    m.LParam.ToInt32() >> 16));
                if (pos.X <= ClientRectangle.Left + borderWidth &&
                    pos.Y <= ClientRectangle.Top + borderWidth)
                    m.Result = new IntPtr(13); //TOPLEFT
                else if (pos.X >= ClientRectangle.Right - borderWidth &&
                    pos.Y <= ClientRectangle.Top + borderWidth)
                    m.Result = new IntPtr(14); //TOPRIGHT
                else if (pos.X <= ClientRectangle.Left + borderWidth &&
                    pos.Y >= ClientRectangle.Bottom - borderWidth)
                    m.Result = new IntPtr(16); //BOTTOMLEFT
                else if (pos.X >= ClientRectangle.Right - borderWidth &&
                    pos.Y >= ClientRectangle.Bottom - borderWidth)
                    m.Result = new IntPtr(17); //BOTTOMRIGHT
                else if (pos.X <= ClientRectangle.Left + borderWidth)
                    m.Result = new IntPtr(10); //LEFT
                else if (pos.Y <= ClientRectangle.Top + borderWidth)
                    m.Result = new IntPtr(12); //TOP
                else if (pos.X >= ClientRectangle.Right - borderWidth)
                    m.Result = new IntPtr(11); //RIGHT
                else if (pos.Y >= ClientRectangle.Bottom - borderWidth)
                    m.Result = new IntPtr(15); //Bottom
                else
                    m.Result = new IntPtr(2); //Move
            }
        }
    }
    

    Then add the control to the picture box:

    var s = 100;
    var c = new FrameControl();
    c.Size = new Size(s, s);
    c.Location = new Point((pictureBox1.Width - s) / 2, (pictureBox1.Height - s) / 2);
    pictureBox1.Controls.Add(c);
    

    To add a fancy effect of filling outside of the frame with semi-transparent color:

    private void pictureBox1_Paint(object sender, PaintEventArgs e)
    {
        e.Graphics.ExcludeClip(pictureBox1.Controls[0].Bounds);
        using (var b = new SolidBrush(Color.FromArgb(100, Color.Black)))
            e.Graphics.FillRectangle(b, pictureBox1.ClientRectangle);
    }
    

    As you can see in the paint event, you can find the FrameControl using pictureBox1.Controls[0]. So you can find its location and size.

    You can encapsulate all the logic of the picture box in a derived picture box.

    Note: Flicker-free rendering

    If you experience flickering when moving the frame, use the following code in your form:

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams cp = base.CreateParams;
            cp.ExStyle |= 0x02000000;   // WS_EX_COMPOSITED       
            return cp;
        }
    }