1

As the title says I just need to correctly resize and reposition the control if I drag the 2 ellipse handles inside the custom control I made. I can't seem to figure out how to do it.

enter image description here

In the GIF above the my control is the one with yellow background and it's having a problem when I start moving the 2nd handle outside the 4th quadrant. It needs to correctly reposition and resize the control.

Code:

public partial class FlexibleLineControl : Control
{
    public FlexibleLineControl()
    {
        // transparent control: https://www.c-sharpcorner.com/uploadfile/Nildo/making-transparent-control-using-gdi-and-C-Sharp-updated-to-net-3-5/
        SetStyle(ControlStyles.SupportsTransparentBackColor, true);
        this.BackColor = Color.PaleGoldenrod;

        InitializeComponent();

        this.Size = new Size(HandleSize.Width * 2, HandleSize.Height * 2);
        Handle1 = new Rectangle(new Point(0, 0), HandleSize);
        Handle2 = new Rectangle(new Point(HandleSize.Width, HandleSize.Height), HandleSize);
        GP1.AddEllipse(Handle1);
        GP2.AddEllipse(Handle2);
        DoubleBuffered = true;
    }

    private GraphicsPath GP1 = new GraphicsPath();
    private GraphicsPath GP2 = new GraphicsPath();
    private Size HandleSize = new Size(10, 10);

    public Rectangle Handle1;
    private Rectangle Handle2;

    private Point CurrMousePoint1;
    private bool DragState = false;
    private bool Handle1Dragged = false;
    private bool Handle2Dragged = false;

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);

        e.Graphics.FillEllipse(Brushes.Black, Handle1);
        e.Graphics.FillEllipse(Brushes.Black, Handle2);

        // get line points to draw line
        Point p1 = new Point(Handle1.Location.X + Handle1.Width / 2, Handle1.Location.Y + Handle1.Height / 2);
        Point p2 = new Point(Handle2.Location.X + Handle2.Width / 2, Handle2.Location.Y + Handle2.Height / 2);
        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
        e.Graphics.DrawLine(new Pen(Brushes.Black), p1, p2);
    }

    protected override void OnMouseDown(MouseEventArgs e)
    {
        if (GP1.IsVisible(e.Location))
        {
            DragState = true;
            Handle1Dragged = true;
            CurrMousePoint1 = this.Parent.PointToClient(Cursor.Position);
        }
        else if (GP2.IsVisible(e.Location))
        {
            DragState = true;
            Handle2Dragged = true;
            CurrMousePoint1 = this.Parent.PointToClient(Cursor.Position);
        }
    }

    protected override void OnMouseMove(MouseEventArgs e)
    {
        if (DragState)
        {
            Point newPoint = Parent.PointToClient(Cursor.Position);
            if (Handle1Dragged)
                Handle1.Location = new Point(Handle1.X + newPoint.X - CurrMousePoint1.X, Handle1.Y + newPoint.Y - CurrMousePoint1.Y);
            else if (Handle2Dragged)
                Handle2.Location = new Point(Handle2.X + newPoint.X - CurrMousePoint1.X, Handle2.Y + newPoint.Y - CurrMousePoint1.Y);
                

            // get new control size and location
            int width = Math.Abs(Handle1.Location.X - Handle2.Location.X);
            int height = Math.Abs(Handle1.Location.Y - Handle2.Location.Y);

            this.Size = new Size(width + HandleSize.Width, height + HandleSize.Height);


            CurrMousePoint1 = newPoint;
            Invalidate();
        }
    }

    protected override void OnMouseUp(MouseEventArgs e)
    {
        DragState = false;
        if (Handle1Dragged)
        {
            GP1.Reset();
            GP1.AddRectangle(Handle1);
            Handle1Dragged = false;
        }
        else if (Handle2Dragged)
        {
            GP2.Reset();
            GP2.AddRectangle(Handle2);
            Handle2Dragged = false;
        }
    }
}
user639467
  • 100
  • 1
  • 6
  • Almost everything that's in MouseMove is wrong. The Handles positions are fixed: `[(0, 0), (Width -10, Height - 10)]`. You have to move the container Control's shape and redefine its size. It's no different than drawing rectangles on a surface. See the `MouseMove` code here: [How to use the Paint event to draw shapes at mouse coordinates](https://stackoverflow.com/a/53708936/7444103) -- You don't need two GraphicsPath objects, just `Rectangle.Contains(point)` (since the two circles are quite small). If you use GraphicsPaths, you **must** dispose of these objects – Jimi Jul 01 '21 at 10:18
  • Forgot to mention: you shouldn't rely on the Parent to determine a new Location / Size, just the starting Cursor position - as reference - and the difference in distance when the mouse button is released. Note that the difference can be negative: you're not handling that. – Jimi Jul 01 '21 at 13:29
  • You said I should dispose my GraphicsPaths, so should I not make them as fields and only create them whenever there's a MouseDown event and dispose immediately at the end of the event? – user639467 Jul 01 '21 at 15:24
  • You can create the GraphicsPath objects in `OnHandleCreated` and dispose of them in `OnHandleDestroyed`. If you really need these objects: as mentioned, you can simply use the Rectangle shapes and check whether `[Rectangle].Contains([Point])`. The circles are small, so the area that's not part of the rectangle is pixel-sized and Users tends to move the Mouse to the center of the objects they want to interact with. – Jimi Jul 01 '21 at 15:28
  • alright thank you I will add that to my code. But i want to ask again because moving the handles is kinda giving a laggy and flickering feeling as you can see in the GIF of my answer (Especially if i drag it very fast it just lags and disappears then reappears when it slows down). What do you think is causing this? I tried the OptimizedDoubleBuffer SetStyle and it works but really messes up the graphics like it leaves a mark and distort the background – user639467 Jul 01 '21 at 15:39
  • Since you have set WS_EX_TRANSPARENT, change the styles in `SetStyle(ControlStyles.SupportsTransparentBackColor | ControlStyles.Opaque, true); SetStyle(ControlStyles.OptimizedDoubleBuffer, false);`: now you can drag your line over other Controls. Remove everything else. `ResizeRedraw` is removed since the line is resized and redrawn by dragging the handles. -- DoubleBuffering creates a really nasty effect in transparent Controls: `ControlStyles.Opaque` removes the background instead (coupled with CreateParams). -- Double buffer the Form or Parent instead. – Jimi Jul 01 '21 at 16:05

1 Answers1

2

It's working now thanks to the comments

enter image description here

Working code:

class FlexibleLineControl : Control
{
    public FlexibleLineControl()
    {
        this.Size = new Size(HandleSize.Width * 2, HandleSize.Height * 2);
        this.Location = new Point(0, 0);
        InitControl();
    }

    public FlexibleLineControl(Point p1, Point p2)
    {
        this.Location = new Point(Math.Min(p1.X, p2.X), Math.Min(p1.Y, p2.Y));
        int width = Math.Abs(p1.X - p2.X);
        int height = Math.Abs(p1.Y - p2.Y);

        this.Size = new Size(width + HandleSize.Width, height + HandleSize.Height);
        InitControl();
    }

    public Size HandleSize { get; set; } = new Size(10, 10);
    public Color HandleColor { get; set; } = Color.Black;
    public float LineWidth { get; set; } = 1;
    public Color LineColor { get; set; } = Color.Black;
    public Point LinePoint1 { get; set; }
    public Point LinePoint2 { get; set; }

    private GraphicsPath GP1 = new GraphicsPath();
    private GraphicsPath GP2 = new GraphicsPath();
    public Rectangle Handle1;
    private Rectangle Handle2;

    private Point CurrMousePoint1;
    private bool DragState = false;
    private bool Handle1Dragged = false;
    private bool Handle2Dragged = false;

    private void InitControl()
    {
        // transparent control: https://www.c-sharpcorner.com/uploadfile/Nildo/making-transparent-control-using-gdi-and-C-Sharp-updated-to-net-3-5/
        SetStyle(ControlStyles.SupportsTransparentBackColor, true);
        SetStyle(ControlStyles.ResizeRedraw, true);

        //Set style for double buffering
        SetStyle(ControlStyles.DoubleBuffer |
                    ControlStyles.AllPaintingInWmPaint |
                    ControlStyles.UserPaint, true);
        this.BackColor = Color.Transparent;
        this.DoubleBuffered = true;

        Handle1 = new Rectangle(new Point(0, 0), HandleSize);
        Handle2 = new Rectangle(new Point(this.Size.Width - HandleSize.Width, this.Size.Height - HandleSize.Height), HandleSize);
        LinePoint1 = new Point(Handle1.X + Handle1.Width / 2, Handle1.Y + Handle1.Height / 2);
        LinePoint2 = new Point(Handle2.X + Handle2.Width / 2, Handle2.Y + Handle2.Height / 2);

        GP1.AddEllipse(Handle1);
        GP2.AddEllipse(Handle2);
    }

    #region Move and Draw event overrides
    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);

        e.Graphics.FillEllipse(new SolidBrush(HandleColor), Handle1);
        e.Graphics.FillEllipse(new SolidBrush(HandleColor), Handle2);

        // get line points to draw line
        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
        e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
        e.Graphics.DrawLine(new Pen(Brushes.Black, LineWidth), LinePoint1, LinePoint2);
    }
    protected override void OnMouseDown(MouseEventArgs e)
    {
        if (GP1.IsVisible(PointToClient(Cursor.Position)))
        {
            DragState = true;
            Handle1Dragged = true;
            CurrMousePoint1 = this.Parent.PointToClient(Cursor.Position);
        }
        else if (GP2.IsVisible(e.Location))
        {
            DragState = true;
            Handle2Dragged = true;
            CurrMousePoint1 = this.Parent.PointToClient(Cursor.Position);
        }
    }
    protected override void OnMouseMove(MouseEventArgs e)
    {
        if (DragState)
        {
            Point newPoint = Parent.PointToClient(Cursor.Position);
            if (Handle1Dragged)
                Handle1.Location = new Point(Handle1.X + newPoint.X - CurrMousePoint1.X, Handle1.Y + newPoint.Y - CurrMousePoint1.Y);
            else if (Handle2Dragged)
                Handle2.Location = new Point(Handle2.X + newPoint.X - CurrMousePoint1.X, Handle2.Y + newPoint.Y - CurrMousePoint1.Y);

            // get new control size
            int width = Math.Abs(Handle1.X - Handle2.X);
            int height = Math.Abs(Handle1.Y - Handle2.Y);

            this.Size = new Size(width + HandleSize.Width, height + HandleSize.Height);

            // get new control location
            Point p1 = Handle1.Location;
            Point p2 = Handle2.Location;

            if (!(p1.X == p2.X || p1.Y == p2.Y))
            {
                this.Location = new Point(Location.X + Math.Min(Handle1.X, Handle2.X), Location.Y + Math.Min(Handle1.Y, Handle2.Y));

                // p1 top right, p2 bottom left
                if (p1.X > p2.X && p1.Y < p2.Y)
                {
                    p1 = new Point(this.Size.Width - this.HandleSize.Width, 0);
                    p2 = new Point(0, this.Size.Height - this.HandleSize.Height);
                }
                // p1 bottom right, p2 top left
                else if (p1.X > p2.X && p1.Y > p2.Y)
                {
                    p1 = new Point(this.Size.Width - this.HandleSize.Width, this.Size.Height - this.HandleSize.Height);
                    p2 = new Point();
                }
                // p1 top left, p2 bottom right
                else if (p1.X < p2.X && p1.Y < p2.Y)
                {
                    p1 = new Point();
                    p2 = new Point(this.Size.Width - this.HandleSize.Width, this.Size.Height - this.HandleSize.Height);
                }
                // p1 bottom left, p2 top right
                else if (p1.X < p2.X && p1.Y > p2.Y)
                {
                    p1 = new Point(0, this.Size.Height - this.HandleSize.Height);
                    p2 = new Point(this.Size.Width - this.HandleSize.Width, 0);
                }
            }
                
            Handle1.Location = p1;
            Handle2.Location = p2;

            LinePoint1 = new Point(Handle1.X + Handle1.Width / 2, Handle1.Y + Handle1.Height / 2);
            LinePoint2 = new Point(Handle2.X + Handle2.Width / 2, Handle2.Y + Handle2.Height / 2);

            CurrMousePoint1 = newPoint;
            Invalidate();
        }
    }
    protected override void OnMouseUp(MouseEventArgs e)
    {
        DragState = false;
        Handle1Dragged = false;
        Handle2Dragged = false;

        GP1.Reset();
        GP1.AddRectangle(Handle1);
        GP2.Reset();
        GP2.AddRectangle(Handle2);
    }
    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams cp = base.CreateParams;
            cp.ExStyle |= 0x20;
            return cp;
        }
    }
    #endregion

}
user639467
  • 100
  • 1
  • 6
  • 2
    It's very important that, in OnPaint, you create your graphic objects like this: `using (var brush = new SolidBrush(HandleColor)) { e.Graphics.FillEllipse(brush, Handle1); e.Graphics.FillEllipse(brush, Handle2); } using (var pen = new Pen(Color.Black, LineWidth)) { e.Graphics.DrawLine(pen, LinePoint1, LinePoint2); }`. You **must always** dispose of the graphic objects you create, these allocate unmanaged resources (and handles). – Jimi Jul 01 '21 at 16:06