How can I treat the circle as a control after drawing it? - Moving and selecting shapes
Asked Answered
L

1

6

Actually, after clicking on each circle i want its color to be changed, for instance, i want it to turn into red,Overall, i wanna treat it as control.

i know how to draw the circles that represent the nodes of the graph when i Double click on the picturebox. i'm using the following code:

        public Form1()
    {
        InitializeComponent();

        pictureBox1.Paint += new PaintEventHandler(pic_Paint);
    }

    public Point positionCursor { get; set; }
    private List<Point> points = new List<Point>();
    public int circleNumber { get; set; }

    private void pictureBox1_DoubleClick(object sender, EventArgs e)
    {
        positionCursor = this.PointToClient(new Point(Cursor.Position.X - 25, Cursor.Position.Y - 25));

        points.Add(positionCursor);
        Label lbl = new Label();
        lbl.BackColor = Color.Transparent;
        lbl.Font = new Font("Arial", 7);

        lbl.Size = new Size(20, 15);

        if (circleNumber >= 10)
        {
            lbl.Location = new Point(points[circleNumber].X + 3, points[circleNumber].Y + 6);
        }
        else
        {
            lbl.Location = new Point(points[circleNumber].X + 7, points[circleNumber].Y + 7);
        }
        lbl.Text = circleNumber.ToString();
        pictureBox1.Controls.Add(lbl);
        circleNumber++;
        pictureBox1.Invalidate();
    }

    private void pic_Paint(object sender, PaintEventArgs e)
    {
        Graphics g = e.Graphics;
        g.SmoothingMode = SmoothingMode.AntiAlias;

        using (var pen = new Pen(Color.DimGray, 2))
        {
            foreach (Point pt in points)
            {
                g.FillEllipse(Brushes.White, pt.X, pt.Y, 25, 25);
                g.DrawEllipse(pen, pt.X, pt.Y, 26, 26);
            }
        }
    }

enter image description here

Lope answered 13/7, 2016 at 8:0 Comment(4)
if you need it to be a control, then make a control that draws a circle.. then you make the new control and place it on the form rather than draw a circleVaripapa
You need to hit test for circles and then if the hit test point is in a circle draw it in red color.Hasidism
@BugFinder,your opinion ,i make user control.Lope
@RezaAghaei,Sorry, I didn't.Lope
H
9

You need to perform a hit-test to check if a point is in a circle. As an option you can add a circle to a GraphicsPath and the use IsVisible method of the path to check if the point is in circle.

For example having a ponit p as top-left location of a circle with diameter d, you can check if current clicked point is in the circle or con this way:

var result = false;
using (var path = new GraphicsPath())
{
    path.AddEllipse(p.X, p.Y, d, d);
    result = path.IsVisible(e.Location);
}

Sample Code

I see you have asked multiple questions in this topic. So here I share some code to help you to be in right direction.

define variables for fill color, selected fill color, circle size, border width and etc, to be able to change them simply if you need.

List<Rectangle> Shapes = new List<Rectangle>();
int selectedIndex = -1;
Size size = new Size(25, 25);
Color fillColor = Color.White;
Color selectedfillCOlor = Color.Red;
Color borderColor = Color.Gray;
int borderWidth = 2;

DoubleClick

Here add circles to the Shapes list. It's enough to add the bounding rectangle of a circle to the list.

private void pic_MouseDoubleClick(object sender, MouseEventArgs e)
{
    var p = e.Location;
    p.Offset(-size.Width / 2, -size.Height / 2);
    Shapes.Add(new Rectangle(p, size));
    pic.Invalidate();
}

Click

Here perform hit-test to check if the point is in one of circles.Check if the Ctrl key is down when click, to make selection, then set the found index as selectedIndex to use it when painting.

private void pic_MouseClick(object sender, MouseEventArgs e)
{
    if (ModifierKeys != Keys.Control)
        return;
    selectedIndex = -1;
    for (int i = 0; i < Shapes.Count; i++)
    {
        using (var path = new GraphicsPath())
        {
            path.AddEllipse(Shapes[i]);
            if (path.IsVisible(e.Location))
                selectedIndex = i;
        }
    }
    pic.Invalidate();
}

Paint

Set SmoothingMode of graphics object to AntiAlias to have a more smooth drawing. Then draw shapes in a for loop and pay attention to selectedIndex to use a different fill color for selected shape.

To draw the text, yo don't need to use a label and you can simply draw text using TextRenderer class.

private void pic_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
    for (int i = 0; i < Shapes.Count; i++)
    {
        var selected = (selectedIndex == i);
        using (var brush = new SolidBrush(selected ? selectedfillCOlor : fillColor))
            e.Graphics.FillEllipse(brush, Shapes[i]);
        using (var pen = new Pen(borderColor, borderWidth))
            e.Graphics.DrawEllipse(pen, Shapes[i]);
        TextRenderer.DrawText(e.Graphics, (i + 1).ToString(),
                this.Font, Shapes[i], Color.Black,
                TextFormatFlags.VerticalCenter | TextFormatFlags.HorizontalCenter);
    }
}

Some Notes

  • It's better to encapsulate codes in a new control derived from PictureBox or derived Controland set DoubleBuffered to true.

  • It's a good option to encapsulate Circle in a Circle class which performs hit testing and rendering of a circle. Specially if you want to move them later or perform some other interactions or let each circle has it's own properties like color ,etc.

Sample Circle Class

Here is a sample circle class which can be a good start point.

public class Circle
{
    private Color selectedFillColor = Color.Red;
    private Color normalFillColor = Color.Red;
    private Color borderColor = Color.Red;
    private int borderWidth = 2;
    public Point Location { get; set; }
    public int Diameter { get; set; }
    public Rectangle Bounds
    {
        get
        {
            return new Rectangle(Location, new Size(Diameter, Diameter));
        }
    }
    public bool HitTest(Point p)
    {
        var result = false;
        using (var path = new GraphicsPath())
        {
            path.AddEllipse(Bounds);
            result = path.IsVisible(p);
        }
        return result;
    }
    public bool Selected { get; set; }
    public void Draw(Graphics g)
    {
        using (var brush = new SolidBrush(
            Selected ? selectedFillColor : normalFillColor))
            g.FillEllipse(brush, Bounds);
        using (var pen = new Pen(borderColor, 2))
            g.DrawEllipse(pen, Bounds);
    }
}
Hasidism answered 13/7, 2016 at 9:39 Comment(9)
thanks so much,your opinion ,Which of the books I read? Graphics Programming with GDI+ or Pro .NET 2.0 Graphics Programming.Do you have a better suggestion?Lope
You're welcome :) - That's a good book. But it may contain lots of information which you don't need in current task. So if you want to read the book or any articles, just read those parts which may be related to your task. Anyway, I also added a sample Circle class to the answer.Hasidism
Also for creating a user control and putting some logic on the user control, take a look at this post.Hasidism
@RezaAghaei,what is GraphicsPath? i don't get it.Externality
@Externality System.Drawing.Drawing2D.GraphicsPath. You can add using using System.Drawing.Drawing2D;.Hasidism
@Externality You may want to take a look at Draw multiple freehand Polyline or Curve drawing - Adding Undo Feature and How to drag and move shapes in C# Specially the last one which shares the idea of using Shape classes :) You may find those answers useful.Hasidism
An interesting answer you've got here! I have a question: Would it be bad (for example cause memory leaks) to store the GraphicsPath object in a class-level variable instead of disposing it? I was thinking this would be faster than constantly creating a new one for every hit test (of course you would have to create a new one when the control is resized).Porridge
@VisualVincent In that case, you should 1) Make your shapes IDisposable and in Dispose method dispose the cached path. Then you should dispose each shape in a suitable time, for example when the drawing surface disposes. Also you should not forget to dispose the shape when you remove it it from Shapes collection of drawing surface.*2)* Don't forget to invalidate the cached path if the shape moved or resized. →If you consider mentioned rules, then there would not be any problem. But these are too much for an example and makes it complicated for one who has no idea about it.Hasidism
@VisualVincent You're welcome. If you are interested in topic you may like to take a look at links which I shared in this comment.Hasidism

© 2022 - 2024 — McMap. All rights reserved.