Solved

Detecting a click on a curve with GraphicsPath in C#

Posted on 2011-09-26
7
1,104 Views
Last Modified: 2012-05-12
Hello!

I am new to C# and working on my first project under .NET 4.0 and Windows Forms. What I am doing in the project is to make the first study for a GUI that will be part of an embedded software in a large device.

I have already done a form that draws a number of signals into a coordinate system. The curves get different colours to be distinguished propely. What I want to do is to react on a mouse double click that hits one of them. Until now, no clicking reaction was implemented. So, I just draw the curves with a simple m_Graphics.DrawCurve(pen, a_Point, .6f) so that the curves are somehow adaptive and not kinked.

According to some sites on the Web, the GraphicsPath class would be a solution. I am using now such a class. Not to have kinked lines, I want to use the PathPointType.Bezier (or Bezier3). But to be honest, I do not have the right understanding of it.

My approach
You can see what I have done in the code below. I tried to add a Bezier information in the way it was shown in some tutorials. I also tried to add only one PathPointType by writing for the second parameter ..., new byte[] { (byte)PathPointType.Bezier }, but all that resulted in an ArgumentException.

Question
How am I supposed to use the GraphicsPath or it's constructor so that I get smooth lines? I want to change the active signal according to a double-click on one of the waveforms.

We develop currently under Windows XP, but want to move next year to Windows 7.

Thank you for your help!
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class SignalsForm : MyForm
    {
[…]
        /// <summary>
        /// All the waveform curve objects.
        /// </summary>
        private GraphicsPath[] m_Curve = null;
[…]
        /// <summary>
        /// Signals information (name, units).
        /// </summary>
        private DAOSignalsInfo m_Info = null;
        /// <summary>
        /// Signals waveform information.
        /// </summary>
        private DAOSignals m_Signals = null;
[…]
        /// <summary>
        /// Graphics object used to draw on the background.
        /// </summary>
        private Graphics m_Graphics = null;
[…]
        /// <summary>
        /// Brush needed to write text.
        /// </summary>
        private SolidBrush m_Brush = null;
        /// <summary>
        /// Active waveform index.
        /// </summary>
        private ushort m_usCurrentWaveform = 0;
[…]
        /// <summary>
        /// Number of signals to be displayed in this form.
        /// </summary>
        private ushort m_usSignals = 0;

        /// <summary>
        /// Standard constructor
        /// </summary>
        public SignalsForm()
        {
            const string strMETHOD = "SignalsForm:SignalsForm: ";
[…]
            m_Brush = new SolidBrush(Color.Gray);   // Needed for labeling
            m_Info = new DAOSignalsInfo();          // Signal information
            m_Signals = new DAOSignals();           // Signal values

            m_usSignals = m_Info.GetNrOfSignals();
            // Each waveform representing a signal is drawn by a curve:
            m_Curve = new GraphicsPath[m_usSignals];
[…]

            // Now, draw the waveforms and the surrounding box so that  
            // excessive waveforms going out of bounds are not visible, 
            // and the coordinates:
            Pen pen = new Pen(Colors.NextColor());

            DrawWaveforms(pen);
            DrawSurroundingAreas(pen);
            DrawCoordinates(pen);
        }

        /// <summary>
        /// Draws the requested waveforms.
        /// </summary>
        /// <param name="pen">
        /// Pen needed to draw the curves.
        /// </param>
        private void DrawWaveforms(Pen pen)
        {
            float fFactor = Constants.m_fEXTREMUM_Y/pictureBox.Height;
            // ~150 pixels is the right side of the graph. Take a number a
            // little larger so that the waveforms exceed the graphics area.
            int iTimestamps = Math.Min(170, m_Signals.GetNrOfTimestamps());
            pen.Width = 2;

            for (ushort usSignal = 0; usSignal < m_usSignals; usSignal++)
            {
                List<PointF> l_Point = new List<PointF>();

                for (int iTimeIndex = 0; iTimeIndex < iTimestamps; iTimeIndex++)
                {
                    float fValue = m_Signals.GetSignals()[iTimeIndex][usSignal];

                    if (fValue > Constants.m_fMINIMUM)
                    {
                        l_Point.Add(new PointF(m_usPIXEL2TIME*iTimeIndex + m_usSPACE_RIGHT,
                                  m_fY0Offset - fFactor*fValue));
                    }
                }

                PointF[] a_Point = new PointF[l_Point.Count];

                for (int iTimeIndex = 0; iTimeIndex < a_Point.Count(); iTimeIndex++)
                    a_Point[iTimeIndex] = l_Point[iTimeIndex];

                m_Curve[usSignal] = new GraphicsPath(a_Point,
                    new byte[] { (byte) PathPointType.Start,
                        (byte) PathPointType.Bezier,
                        (byte) PathPointType.Bezier,
                        (byte) PathPointType.Bezier,
                        (byte) PathPointType.Line,
                        (byte) PathPointType.Line});

                // m_Curve[usSignal].AddBeziers(a_Point);
                // m_Graphics.DrawCurve(pen, a_Point, .6f);
                m_Graphics.DrawPath(pen, m_Curve[usSignal]);
                pen.Color = Colors.NextColor();
            }
        }

        /// <summary>
        /// Determines what happens when the picture box is double-clicked.
        /// </summary>
        /// <param name="sender">
        /// Sender object of the event.</param>
        /// <param name="eas">
        /// Generated event's object.
        /// </param>
        private void pictureBox_MouseDoubleClick(object sender, MouseEventArgs meas)
        {
            Point mouseLocation = meas.Location;

            SelectAccessedWaveform(mouseLocation);

            RefreshYAxis();
        }

        private void SelectAccessedWaveform(Point mouseLocation)
        {
            for (ushort usCurve = 0; usCurve < m_Curve.Count(); usCurve++)
            {
                if (m_Curve[usCurve].IsVisible(mouseLocation))
                {
                    m_usCurrentWaveform = usCurve;
                    break;
                }
            }
        }

        /// <summary>
        /// Redraws the border and refreshes the Y axis according to the current
        /// waveform.
        /// </summary>
        private void RefreshYAxis()
        {
            DrawSurroundingAreas();
            LabelYAxis();
        }
    }   // End of class SignalsForm
}

Open in new window

0
Comment
Question by:sae1962at
  • 3
  • 2
  • 2
7 Comments
 
LVL 96

Assisted Solution

by:Bob Learned
Bob Learned earned 33 total points
ID: 36669957
1) Have you looked at WPF as a solution?

2) The GraphicsPath may be a good choice for what you need.  You might want to attach a screen shot of what kind of graphic that you are working with...
0
 
LVL 1

Author Comment

by:sae1962at
ID: 36710032
1) WPF was my original choice, but the group decided to switch to the simpler Windows Forms.

2) The GraphicsPath seems to be right, but how can I get smooth lines? The DrawCurve has a third parameter, a float, that can be [0, 1]. If it is a larger value, the line becomes more and more smoother. Do you know a way to get a GraphicsPath easily smooth? Of course, a non-trivial way would be to determine a Bezier curve for the kinked set of lines, but that is exactly what I want to avoid with a useful answer to this question. :-)
0
 
LVL 1

Author Comment

by:sae1962at
ID: 36710050
0
How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

 
LVL 96

Expert Comment

by:Bob Learned
ID: 36711667
1) Are you using beziers with the GraphicsPath?

2) You can determine if a point is visible on the GraphicsPath with something like this:

bool isInPath = path.IsVisible(new Point(x, y));

Open in new window

0
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 36711983
You seem to want to combine DrawCurve() with a GraphicsPath.  Why not do just that?...

Just create the GraphicsPath and then use AddCurve().  Now you can draw the curve and use the hit-testing ability.  I would use IsOutlineVisible() with a thicker Pen, though, so that the user doesn't have to click exactly on a pixel to get a match.

Here's a simplified example:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Drawing2D;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {

        private GraphicsPath gp = null;
        private List<PointF> DataPoints = new List<PointF>();
        
        public Form1()
        {
            InitializeComponent();

            Random R = new Random();
            for (int i = 1; i <= 50; i++)
            {
                DataPoints.Add(new PointF(i * 10, R.Next(0, this.ClientRectangle.Height)));
            }
            gp = new GraphicsPath();
            gp.AddCurve(DataPoints.ToArray(), 0.5f);
        }

        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            if (this.gp != null)
            {
                e.Graphics.DrawPath(Pens.Black, gp);
            }
        }

        private void Form1_MouseDown(object sender, MouseEventArgs e)
        {
            if (this.gp != null)
            {
                using (Pen p = new Pen(Color.Black, 5))
                {
                    if (this.gp.IsOutlineVisible(new Point(e.X, e.Y), p))
                    {
                        Console.WriteLine("Hit");
                    }
                    else
                    {
                        Console.WriteLine("Miss");
                    }
                }
            }
        }
    }
}

Open in new window

0
 
LVL 1

Author Comment

by:sae1962at
ID: 36890201
Thank you for your precise answer, Idle_Mind! It is the answer to the main part of my question.

Concerning the click on a curve, visibility is not the solution, as all the curves that can be clicked on are visible. But I will try it again. Perhaps, I made something wrong. Currently, I am changing something else that has nothing to do with this question, so be patient...
0
 
LVL 85

Accepted Solution

by:
Mike Tomlinson earned 67 total points
ID: 36891427
Take your time and ask as many questions as needed.
0

Featured Post

What Is Threat Intelligence?

Threat intelligence is often discussed, but rarely understood. Starting with a precise definition, along with clear business goals, is essential.

Join & Write a Comment

Calculating holidays and working days is a function that is often needed yet it is not one found within the Framework. This article presents one approach to building a working-day calculator for use in .NET.
It was really hard time for me to get the understanding of Delegates in C#. I went through many websites and articles but I found them very clumsy. After going through those sites, I noted down the points in a easy way so here I am sharing that unde…
In this seventh video of the Xpdf series, we discuss and demonstrate the PDFfonts utility, which lists all the fonts used in a PDF file. It does this via a command line interface, making it suitable for use in programs, scripts, batch files — any pl…
Get a first impression of how PRTG looks and learn how it works.   This video is a short introduction to PRTG, as an initial overview or as a quick start for new PRTG users.

747 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

9 Experts available now in Live!

Get 1:1 Help Now