10

This is my drawing code to draw a custom line with mouse onto a Chart. Can you please help me to do it proper way ?

namespace Grafi
    {
        public partial class Form1 : Form
        {

            bool isDrawing = false;
            Point prevPoint;

            public Form1()
            {
                InitializeComponent();
            }

            private void chartTemperature_MouseDown(object sender, MouseEventArgs e)
            {
                isDrawing = true;
                prevPoint = e.Location;
            }

            private void chartTemperature_MouseMove(object sender, MouseEventArgs e)
            {
                Pen p = new Pen(Color.Red, 2); 
                if (isDrawing)
                {
                    Graphics g = chartTemperature.CreateGraphics();    
                    g.DrawLine(p, prevPoint, e.Location);
                    prevPoint = e.Location;

                    numOfMouseEvents = 0;              
                }
                p.Dispose();
            }

            private void chartTemperature_MouseUp(object sender, MouseEventArgs e)
            {
                isDrawing = false;
            }
        }
    }

Problem is that when I resize form my line disappers. It disappers whenever onPaint event is raised.

Primoz
  • 4,079
  • 17
  • 56
  • 67
  • 1
    Can you explain, what exactly do you mean by *properly*? In *any* case, you will need to handle the `Mouse Up/Down/Move` events. – KMån Nov 12 '10 at 13:07
  • For clarification, this question is in response to an issue raised in answering a previous question: http://stackoverflow.com/questions/4164190/how-to-save-graphics-object-as-image-in-c/4164625#4164625. He wants to know the best way to modify the existing code to draw in the `Paint` event, instead of using `CreateGraphics`. – Cody Gray - on strike Nov 12 '10 at 13:24
  • There are various good sample painting samples on CodeProject. They range from the very simple to quite complex. Check some of them out. You'll see the different ways you can do this properly, though all the methods will involve saving your mouse move points in a collection and redrawing them in a paint event handler. – Paul Sasik Nov 12 '10 at 13:39
  • Just decided to whip up a sample based on the recorded strokes idea of painting. Please see the code in my answer below. (I actually tested it.) – Paul Sasik Nov 12 '10 at 14:02

4 Answers4

10

Try this... It is a stroke drawing method, implemented very simply and as close to your own code as possible. Stokes are individual collections of mouse movements. Every mouse move between down and up is recorded as a stroke, all the strokes are collected and then redrawn whenever the paint event is fired. This example is simple but could be a good starting point.

Note that you will have to add the paint handler for your chart object.

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Drawing2D;

namespace Grafi
{
    public partial class Form1 : Form
    {
        bool isDrawing;
        // our collection of strokes for drawing
        List<List<Point>> _strokes = new List<List<Point>>();
        // the current stroke being drawn
        List<Point> _currStroke;
        // our pen
        Pen _pen = new Pen(Color.Red, 2); 

        public Form1()
        {
            InitializeComponent();
        }

        private void chartTemperature_MouseDown(object sender, MouseEventArgs e)
        {
            isDrawing = true;
            // mouse is down, starting new stroke
            _currStroke = new List<Point>();
            // add the initial point to the new stroke
            _currStroke.Add(e.Location);
            // add the new stroke collection to our strokes collection
            _strokes.Add(_currStroke);
        }

        private void chartTemperature_MouseMove(object sender, MouseEventArgs e)
        {
            if (isDrawing)
            {
                // record stroke point if we're in drawing mode
                _currStroke.Add(e.Location);
                Refresh(); // refresh the drawing to see the latest section
            }
        }

        private void chartTemperature_MouseUp(object sender, MouseEventArgs e)
        {
            isDrawing = false;
        }

        private void chartTemperature_Paint(object sender, PaintEventArgs e)
        {
            // now handle and redraw our strokes on the paint event
            e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
            foreach (List<Point> stroke in _strokes.Where(x => x.Count > 1))
                e.Graphics.DrawLines(_pen, stroke.ToArray());
        }
    }
}
RAPTOR
  • 124
  • 1
  • 11
Paul Sasik
  • 79,492
  • 20
  • 149
  • 189
  • I tried your method, it works but my panel "blinks" when I'm drawing. It is probably due to the continuous Refresh calls (I use Invalidate(false) method). Do you know any workaround? – Nick Mar 13 '14 at 15:24
  • 1
    @Nick: Yes, there is. This is really a separate question but, you need to set the DoubleBuffered property of your control to true. Double buffering prevents flicker. See here: http://msdn.microsoft.com/en-us/library/system.windows.forms.control.doublebuffered(v=vs.110).aspx A note of caution though: The last time I did any custom drawing was with .NET 2.0. In that version setting the DoubleBuffer property was not as straightforward for certain controls. Form.DoubleBuffered was easy but some controls required a different call so watch out for that. – Paul Sasik Mar 13 '14 at 16:43
1

Do you have any problems with your current implementation? Does it work, or do you just want to make the code better for an already working function.

I think you logic looks just fine. However, I would add a using clause to the Pen like this:

private void chartTemperature_MouseMove(object sender, MouseEventArgs e)
{
  using( Pen p = new Pen(Color.Red, 2)){
    if (isDrawing)
    {
      Graphics g = chartTemperature.CreateGraphics();    
      g.DrawLine(p, prevPoint, e.Location);
      prevPoint = e.Location;

      numOfMouseEvents = 0;              
    }
  }
}

This way your Pen will be disposed even in case of any exceptions occuring after it's creation and your call to Dispose.

However, you can also think of making the Pen a class variable so you don't have to create and dispose it each time you move the mouse.

Øyvind Bråthen
  • 59,338
  • 27
  • 124
  • 151
  • Problem is that when I resize form my line disappers. It disappers on whenever onPaint event is raised. – Primoz Nov 12 '10 at 13:14
  • @Primoz - That is because you do not store the points everywhere. Make a collection of StartPoints and EndPoints, and on MouseUp you add the current startpoint, and endpoint to the collections. Then override the OnPaint event, and when the window is repainted, loop through the collection and paint the lines using the chart's graphics object. – Øyvind Bråthen Nov 12 '10 at 13:39
1

You need to store your line somewhere.

The steps you need to take are:

  1. Create somewhere to store your points in the main class, e.g . an ArrayList<ArrayList<Point>> - where each ArrayList<Point> contains the list of points in one line.
  2. wait for mousedown events, and create an array for a new line (e.g a new ArrayList<Point>) at the end of the list of lines
  3. wait for mousemoved events, and add a point to the last line in your list, whenever the mouse is dragged. ask to update your window here.
  4. in your paint, iterate through all lines, and draw each point of each line in the array.
  5. to clear the drawing, simply replace array with a blank list, and update the window.

If you don't store your lines somewhere, they will be lost. Does this make sense?

The other way of storing lines is by using a Canvas object, where the pixel-map of what is drawn is remembered and automatically drawn. If you don't mind not having your line data as vector points, and you might also want to use images or colours, then this might be a better approach.

Sanjay Manohar
  • 6,920
  • 3
  • 35
  • 58
0

I posted a solution a while back on how to draw a line using mouse movements. This should work for you.

  Point mAnchorPoint = new Point(200, 200);
  Point mPreviousPoint = Point.Empty;

  private void panel1_MouseMove(object sender, MouseEventArgs e)
  {
     if (mPreviousPoint != Point.Empty)
     {
        // Clear last line drawn
        ControlPaint.DrawReversibleLine(PointToScreen(mAnchorPoint), PointToScreen(mPreviousPoint), Color.Pink);
     }

     // Update previous point
     mPreviousPoint = e.Location;
     mPreviousPoint.Offset(myPanel1.Location);

     // Draw the new line
     ControlPaint.DrawReversibleLine(PointToScreen(mAnchorPoint), PointToScreen(mPreviousPoint), Color.Pink);
  }

Basically what you can do is draw a line every time the mouse moves. If there was a previous line and youre still moving the mouse, erase the line and draw the new one. Note that this example offsets based on a specific Panel (myPanel1 in this example). Adjust accordingly. If you resize the control, you will need to redraw the line using the anchor point previous point.

Community
  • 1
  • 1
SwDevMan81
  • 48,814
  • 22
  • 151
  • 184