1

This is what I have so far and working correctly:

A RangeBar Chart with two Series, that I fill with Points at runtime, based on the results of an Oracle query. Each Point represents a data object with a number of properties.

What I would like to add to this is the following:

For each visible Point on my chart, I want the user to be able to right click the Point in order to open a Menu with a number of options. These options should invoke certain functioncalls, with a property of the selected Point used as a parameter in that functioncall. In this function then, a new window is to be opened that would display some information based on the data object represented by the Point and the menu item that was clicked.

What have I tried so far? I started fooling around with a ContextMenu based on information in this topic: Adding a right click menu to an item:

ContextMenu cm = new System.Windows.Forms.ContextMenu();
cm.MenuItems.Add("Item 1", new EventHandler(Item1_Click));

But much to my disappointment, I discover that a ContextMenu can only be assigned to my Chart object, while I wished to assign it to an individual Point in my Series' Points collection:

chart.ContextMenu = cm; // This works
serie1.Points[DataObject.pointIndex].ContextMenu = cm; // This does not work unfortunately

Looking at the documentation of the Points collection assigned to a Chart Series, I find no mention of any sort of Menu whatsoever. Does this mean that what I'm looking for simply isn't feasible in a technical sense or am I overseeing something? And if so, would there be any other technical implementation that would deliver the functionality I'm looking for? (right now I'm thinking a ToolTip could cover some of my needs, but this wouldn't be nearly as elegant nor extensive as displaying the information in a new window)

J. Dough
  • 77
  • 7
  • This is "by-design". Too many `ContextMenu` objects will blow your app, because you will exhaust win32 handle limit. – vasily.sib Feb 04 '19 at 09:56
  • 1
    Also, there should be some-kind-of `ContextMenuOpening` event of `RangeBar` where you can configure your context menu. – vasily.sib Feb 04 '19 at 10:04
  • Thanks for your replies @vasily.sib. I checked the documentation for the `ContextMenuOpening` event and I understand how this could help with keeping the number of `ContextMenu` objects to a minimum (just one as a placeholder to adjust on the fly, instead of one per Point). What I don't know however is how to retrieve information regarding which exact Point on my chart was clicked at runtime. – J. Dough Feb 04 '19 at 12:53
  • 1
    If your points a big enough to hit reliably you should be able to use Chart.Hittest to get at the point. – TaW Feb 04 '19 at 13:05
  • get [Control.MousePosition](https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.control.mouseposition?view=netframework-4.7.2) in screen coordinates, then calculate client coordinates with [Control.PointToClient()](https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.control.pointtoclient?view=netframework-4.7.2) and finaly find your `RangeBar` Point by client coordinates (there should be a method for this) – vasily.sib Feb 04 '19 at 13:31
  • @vasily.sib : Since we should be in a MouseEvent we do not need any of this. Doing a HitTest will be all that is needed. – TaW Feb 04 '19 at 13:59
  • Thank both of you for your replies. I found some elaborate examples of `Hittest` which I should be able to nicely incorporate into my chart combined with `ContextMenuOpening`. Will update my post when finished :) – J. Dough Feb 04 '19 at 14:16

1 Answers1

2

Points are not Controls. But you can easily use just one Context menu on the chart, open it it at the right spot and and feed in the relevanty point data. All you need is doing a HitTest on the Chart and opening the ContextMenu at the mouse position:

private void chart1_MouseDown(object sender, MouseEventArgs e)
{
    if (e.Button.HasFlag(MouseButtons.Right))
    {
        var hitt = chart1.HitTest(e.X, e.Y);
        DataPoint dp = null;
        if (hitt.PointIndex >= 0)
        {
            dp = hitt.Series.Points[hitt.PointIndex];
        }

        ContextMenu cm = new System.Windows.Forms.ContextMenu();  // either reuse or dispose!
        cm.MenuItems.Add("Item 1 X:" + dp.XValue, new EventHandler(Item1_Click));
        cm.Tag = dp;
        cm.Show(chart1, e.Location);
    }
}

private void Item1_Click(object sender, EventArgs e)
{
    Console.WriteLine("Item1_Click");
    DataPoint dp = (sender as MenuItem).Parent.Tag as DataPoint;
    if  (dp != null) Console.WriteLine("Y:" + dp.YValues[0]);
}

If feed the DataPoint into the Tag property of the ContextMenu. Feel free to do your own processing..

TaW
  • 53,122
  • 8
  • 69
  • 111
  • You have a [Win32 Handle Leak](https://en.wikipedia.org/wiki/Handle_leak). You should dispose your `cm` properly. – vasily.sib Feb 05 '19 at 06:07
  • Also, calling `Console.WriteLine()` in a WinForms application is a tricky task that need additional interop call (`AllocConsole` from `kernel32.dll`). It might be easier to just `Debug.WriteLine()` or `Trace.WriteLine()` – vasily.sib Feb 05 '19 at 06:13
  • This solution is just showing the way. Obviously OP will do different things and not write to the console. Cleaning up or reusing the menue i also up to him; but thanks for the hint.. – TaW Feb 05 '19 at 07:25