Search code examples
c#winformsgraphicsgdi+

How to draw a small dot on every click of the mouse in a PictureBox


I have a WinForms program where the user clicks on a PictureBox control.
I want a small red dot (a few pixels across) every time the user clicks.
I also don't want any of the previous dots to go away.

I know I will need a generic list of ellipses and rectangles, but am not sure how to execute this. How would I go about doing this?

In my program a pictureBox1_Click method handles mouse click events and returns the position of the clicks.
A pictureBox1_Paint method handles the graphics to be drawn on these points.


Solution

  • You have to create a container that can reference the Points collection and add one point to the collection each time you click on a paint-able Control.

    Maybe, you want to also create different kinds of drawing points, based on some conditions or requirements.
    Thus, you need to store also these extra properties, not just a point coordinate.
    If so, you need a specialized object that can expose these properties when needed.

    So, here's a custom Class object with some simple properties, that lets you define a Point's Color and Size. For each of its Points collection.
    It also implements the IDisposable interface, because we need to create a Pen object for each Point we draw. And a Pen object needs to be disposed (implements IDisposable).

    To perform the drawing, you just need to call Control.Invalidate() (pictureBox1.Invalidate() in the example). This causes the repainting of the invalidated parts of the control, raising the OnPaint() event.
    Each point (that needs to be repainted) is drawn using e.Graphics.DrawEllipse().

    You can test it this way:

    With predefined properties, using just a Mouse pointer coordinate:

    myPoints.Add(new MyPoints.DrawingPoint(e.Location));

    With specific properties when something different is needed:

    With a size of 8x8 pixels
    newPoint.Dot = new Rectangle(e.Location, new Size(8, 8)));.

    With an orange Pen of 2 pixels in size
    newPoint.DrawingPen = new Pen(Color.Orange, 2);

    Add this new Point to the collection
    myPoints.DrawingPoints.Add(newPoint);

    The Clear() method is used to Dispose() the current list of Points and create a new, empty, List:
    MyPoints.Clear();

    Sample implementation:

    MyPoints myPoints = new MyPoints();
    
    private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
    {
        //Use default property values
        //myPoints.Add(new MyPoints.DrawingPoint(e.Location));
    
        MyPoints.DrawingPoint newPoint = new MyPoints.DrawingPoint();
        newPoint.Dot = new Rectangle(e.Location, new Size(4, 4));
        newPoint.DrawingPen = new Pen(Color.Red, 2);
        myPoints.DrawingPoints.Add(newPoint);
        (sender as Control).Invalidate();
    }
    
    private void pictureBox1_Paint(object sender, PaintEventArgs e)
    {
        foreach (MyPoints.DrawingPoint mypoint in myPoints.DrawingPoints) {
            e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
            e.Graphics.DrawEllipse(mypoint.DrawingPen, mypoint.Dot);
        }
    }
    
    private void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
        myPoints.Dispose();
    }
    

    The Point objects collection Class container:

    internal class MyPoints : IDisposable
    {
        bool IsDisposed = false;
        public MyPoints() => DrawingPoints = new List<DrawingPoint>();
    
        public List<DrawingPoint> DrawingPoints { get; set; }
        public void Add(DrawingPoint NewPoint)
        {
            if (NewPoint.Dot.Size.Width > 1 && NewPoint.Dot.Size.Height > 1) {
                DrawingPoints.Add(NewPoint);
            }
        }
    
        public void Clear()
        {
            Dispose();
            DrawingPoints.Clear();
            DrawingPoints = new List<DrawingPoint>();
        }
    
        public void Remove(Point point)
        {
            Remove(DrawingPoints.Select((p, i) => { if (p.Dot.Contains(point)) return i; return -1; }).First());
        }
    
        public void Remove(int Index)
        {
            if (Index > -1) {
                DrawingPoints[Index].Delete();
                DrawingPoints.RemoveAt(Index);
            }
        }
    
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    
        protected void Dispose(bool IsSafeDisposing)
        {
            if (IsSafeDisposing && (!IsDisposed) && (DrawingPoints.Count > 0)) {
                foreach (DrawingPoint dp in DrawingPoints)
                    if (dp != null) dp.Delete();
            }
        }
    
        public class DrawingPoint
        {
            Pen m_Pen = null;
            Rectangle m_Dot = Rectangle.Empty;
    
            public DrawingPoint() : this(Point.Empty) { }
            public DrawingPoint(Point newPoint)
            {
                m_Pen = new Pen(Color.Red, 1);
                m_Dot = new Rectangle(newPoint, new Size(2, 2));
            }
    
            public Pen DrawingPen { get => m_Pen; set => m_Pen = value; }
            public Rectangle Dot { get => m_Dot; set => m_Dot = value; }
            public void Delete()
            {
                if (m_Pen != null) m_Pen.Dispose();
            }
        }
    }
    

    This is how it can work, changing its properties when required:

    DottedColors1