Home > other >  Can I show ToolTip for the nodes I am painting on a panel?
Can I show ToolTip for the nodes I am painting on a panel?

Time:02-17

I have a mesh system for a MMO and it's uses A* to find paths. Occasionally it fails because I have nodes that are badly placed. To fix this, I made a mesh visualiser. It works OKish - I can see that some nodes are badly placed. But I can't see which nodes.

Here is an example image showing a path.

Here is my code to show the nodes:

    foreach (var node in FormMap.Nodes)
    {

        var x1 = (node.Point.X * sideX);
        var y1 = (node.Point.Y * sideY);

        var x = x1 - nodeWidth / 2;
        var y = y1 - nodeWidth / 2;

        var brs = Brushes.Black;
        //if (node.Visited)
        //    brs = Brushes.Red;
        if (node == FormMap.StartNode)
            brs = Brushes.DarkOrange;
        if (node == FormMap.EndNode)
            brs = Brushes.Green;
        g.FillEllipse(brs, (float)x, (float)y, nodeWidth, nodeWidth);

I know I can redo this and make thousands of small buttons and add events for them but that seems overkill.

Is there any way I can add tooltips to the nodes I am painting on the panel?

CodePudding user response:

Yes, you can show a tooltip for your nodes that you have drawn on the drawing surface. To do so, you need to do the followings:

  • Implement hit-testing for your node, so you can get the node under the mouse position.
  • Create a timer and In mouse move event handler of the drawing surface, do hit-testing to find the hot item. If the hot node is not same as the current hot node, you stop the timer, otherwise, if there's a new hot item you start the timer.
  • In the timer tick event handler, check if there's a hot item, show the tooltip and stop the time.
  • In the mouse leave event of the drawing surface, stop the timer.

And here is the result, which shows tooltip for some points in a drawing:

enter image description here

The above algorithm, is being used in internal logic of ToolStrip control to show tooltip for the tool strip items (which are not control). So without wasting a lot of windows handle, and using a single parent control and a single tooltip, you can show tooltip for as many nodes as you want.

Code Example - Show Tooltip for some points in a drawing

Here is the drawing surface:

using System.ComponentModel;
using System.Drawing.Drawing2D;
public class DrawingSurface : Control
{
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    [Browsable(false)]
    public List<Node> Nodes { get; }
    public DrawingSurface()
    {
        Nodes = new List<Node>();
        ResizeRedraw = true;
        DoubleBuffered = true;
        toolTip = new ToolTip();
        mouseHoverTimer = new System.Windows.Forms.Timer();
        mouseHoverTimer.Enabled = false;
        mouseHoverTimer.Interval = SystemInformation.MouseHoverTime;
        mouseHoverTimer.Tick  = mouseHoverTimer_Tick;
    }
    private void mouseHoverTimer_Tick(object sender, EventArgs e)
    {
        mouseHoverTimer.Enabled = false;
        if (hotNode != null)
        {
            var p = hotNode.Location;
            p.Offset(16, 16);
            toolTip.Show(hotNode.Name, this, p, 2000);
        }
    }

    private System.Windows.Forms.Timer mouseHoverTimer;
    private ToolTip toolTip;
    Node hotNode;
    protected override void onm ouseMove(MouseEventArgs e)
    {
        base.OnMouseMove(e);
        var node = Nodes.Where(x => x.HitTest(e.Location)).FirstOrDefault();
        if (node != hotNode)
        {
            mouseHoverTimer.Enabled = false;
            toolTip.Hide(this);
        }
        hotNode = node;
        if (node != null)
            mouseHoverTimer.Enabled = true;
        Invalidate();
    }
    protected override void onm ouseLeave(EventArgs e)
    {
        base.OnMouseLeave(e);
        hotNode = null;
        mouseHoverTimer.Enabled = false;
        Invalidate();
    }
    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);
        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
        if (Nodes.Count >= 2)
            e.Graphics.DrawLines(Pens.Black,
            Nodes.Select(x => x.Location).ToArray());
        foreach (var node in Nodes)
            node.Draw(e.Graphics, node == hotNode);
    }
    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (mouseHoverTimer != null)
            {
                mouseHoverTimer.Enabled = false;
                mouseHoverTimer.Dispose();
            }
            if (toolTip != null)
            {
                toolTip.Dispose();
            }
        }
        base.Dispose(disposing);
    }
}

Here is the node class:

using System.Drawing.Drawing2D;
public class Node
{
    int NodeWidth = 16;
    Color NodeColor = Color.Blue;
    Color HotColor = Color.Red;
    public string Name { get; set; }
    public Point Location { get; set; }
    private GraphicsPath GetShape()
    {
        GraphicsPath shape = new GraphicsPath();
        shape.AddEllipse(Location.X - NodeWidth / 2, Location.Y - NodeWidth / 2,
            NodeWidth, NodeWidth);
        return shape;
    }
    public void Draw(Graphics g, bool isHot = false)
    {
        using (var brush = new SolidBrush(isHot ? HotColor : NodeColor))
        using (var shape = GetShape())
        {
            g.FillPath(brush, shape);
        }
    }
    public bool HitTest(Point p)
    {
        using (var shape = GetShape())
            return shape.IsVisible(p);
    }
}

And here is the example form, which has a drawing surface control on it:

protected override void onl oad(EventArgs e)
{
    base.OnLoad(e);
    drawingSurface1.Nodes.Add(new Node() { 
        Name = "Node1", Location = new Point(100, 100) });
    drawingSurface1.Nodes.Add(new Node() { 
        Name = "Node2", Location = new Point(150, 70) });
    drawingSurface1.Nodes.Add(new Node() { 
        Name = "Node3", Location = new Point(170, 140) });
    drawingSurface1.Nodes.Add(new Node() { 
        Name = "Node4", Location = new Point(200, 50) });
    drawingSurface1.Nodes.Add(new Node() { 
        Name = "Node5", Location = new Point(90, 160) });
    drawingSurface1.Invalidate();
}
  • Related