Creating Custom Picturebox with Draggable and Resizable Selection Window
Asked Answered
F

1

2

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;
        }
    }
}
Ful answered 13/12, 2018 at 7:32 Comment(10)
a) forget about cropping. What you want is a selection tool. What to do with the selection later doesn't matter yet. b) where is the mousemove event? without it you can't show intermediate steps.. c) for a rubberband example see here or hereOkhotsk
@Okhotsk I think you should have linked your Resizable Grid as an example (yep, I like that thing :)Cling
Thanks for your kind words. Yup, having an actual persistent control instead of a Rectangle also is an interesting alternative.Okhotsk
@Okhotsk I have not implemented those events yet.Ful
@Okhotsk I'm looking for some arrow or size adjustment at each 4 corners or something similar to how we use the selection tool in photoshop. The initial selection rectangle will be predetermined and the user will only be able to adjust its size.Ful
@Okhotsk Actually there is a mouse move event.Please see the update.Ful
OK; I can't analyize the code or test it as I am too tied up. What does and what doesn't work? You will need to detect these cases, either in the mousedown or, better in the mousemove (so you can change the cursor): a) button pressed -> some action. b) inside -> offer movement c) on the border: decide which and offer resize d) outside -> nothing. Maybe the link Jimi offered is helpful, indeed..Okhotsk
@Okhotsk No problem. Please give me an update when you get free time.Ful
Nah, do try it yourself. Studying the code from jim's link is a good idea, imo.Okhotsk
@Okhotsk Please see the update.I'm drawing rectangles at 4 corners to allow the user to click and resize.Now i need to change the cursor when the pointer reaches the corners.The pointer changes only for the bottom right corner,even though the code enters rect.contains in other cases.Ful
S
10

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;
    }
}
Shod answered 14/12, 2018 at 5:47 Comment(20)
Thanks a lot.This is exactly what i was looking for.There seems to be a small bug in your code,The size adjustment option is not correct at the top right corner of the image.Ful
You're welcome. Make sure you have used the latest code. I'll check it as well.Shod
Just one more thing.I need to identify the location and size of the selected area,how can i achieve this.Ful
Fixed the adjustment. Also read the last paragraph about size and location of the frame control.Shod
I have accepted the answer.How can i change the cursor to move cursor when the user tries to move the control? I have tried the following on mousemove event of picturebox` if( pictureBox1.Controls[0].Bounds.Contains(e.Location)) { this.Cursor = Cursors.SizeAll; } ` But this does not seem to work.Ful
@Ful You will like may answer here.Shod
Thanks... Can you please take a look at this problem when the zoom mode is set https://mcmap.net/q/1021508/-translate-rectangle-position-in-a-picturebox-in-zoom-mode/848968Ful
About setting the cursor, let me know if you have any question about the linked post or if you find it useful.Shod
I did not see your last comment.I'm checking it out now.Ful
It works just fine :) But the ButtonDoubleClick event still maximizes the control inspite handling the MouseDoubleClick event.Ful
Are you sure you have handled WM_NCLBUTTONDBLCLK message?Shod
Yeah,just like in your code.Removed this line though base.WndProc(ref m);Ful
The linked post is working properly, i double checked it. For your case I also edited the answer and put message handling code in a correct order.Shod
I'm encountering some new issues.Kindly check the update.Ful
@Ful Please consider posting a new question as the current updates has nothing to do with the original question. The original question is asking about the frame and the frame control is doing well in showing the frame. The fancy background is just an add-on in the answer.Shod
It would be great if you do so, because it's making the Q/A confusing :)Shod
However, please consider a minimal reproducible example in your new question as I don't have any problem with the original answer.Shod
will do shortly.Ful
I have updated the question with the complete example.Please take a look.Ful
Opps, I just saw your new question!Shod

© 2022 - 2024 — McMap. All rights reserved.