Resolved Tooltip balloon arrow

cbreemer

Well-known member
Joined
Dec 1, 2021
Messages
184
Programming Experience
10+
I am using a tooltip with toolTip1.IsBalloon = true; and display it in a MouseMove event handler. I like the balloon style with its blue title, rounded corners and the arrow pointing at the mouse position (image 1). However when I move the mouse a little lower, the nice round corners disappear, and the arrow points downward, to a place I haven't even been (image 2). Anybody have an idea why this may be happening ? If there's a logic in it, it eludes me.

a1.jpg
a2.jpg
 
Solution
Anyway, to partly answer your question. It looks like there is something regarding the Mouse Leave event that seems to be part of the problem. In the code below, if you remove the #if NEVER and [/icode]#endif[/icode], you'll see the balloon switch positions as the mouse gets lower within the client area.
Please provide ALL the relevant code.
Sure. I thought someone would ask this 🙂 Nothing interesting or special about the toolTip handling though...

Tooltip handling:
In Form1.Designer.cs:

    this.toolTip1 = new System.Windows.Forms.ToolTip(this.components);

In Form1.cs:
  
private void pictureBox1_MouseLeave(object sender, EventArgs e)
{
     toolTip1.Hide(sender as PictureBox);
}

private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
    var bitmap = new Bitmap((sender as PictureBox).Image);
    var color = bitmap.GetPixel(e.X, e.Y);
    bool isGas;
    if ( color.ToArgb() == Color.Red.ToArgb() )
        isGas = true;
    else
    if ( color.ToArgb() == Color.Blue.ToArgb() )
        isGas = false;
    else
    {
        toolTip1.Hide(sender as PictureBox);
        return;
    }

    int index = (e.X - LEFTMARGIN ) / BARWIDTH;
    var row = grid.Rows[index];

    var date  = DateTime.Parse((string)row.Cells[DATE].Value).ToString("ddd dd MMM yyyy");
    var usage = isGas ? (row.Cells[GASD].Value as string).Trim() : (row.Cells[ELCD].Value as string).Trim();
    var meter = isGas ? (row.Cells[GAS] .Value as string).Trim() : (row.Cells[ELC] .Value as string).Trim();
    var unit  = isGas ? "m³" : "kWh";
    var title = isGas ? "Gas usage" : "Electricity usage";

    var msg = "\n"
            + $"Date   : {date}\n"
            + $"Usage : {usage} {unit}\n"
            + $"Meter : {meter}";

    toolTip1.IsBalloon = true;
    toolTip1.ToolTipTitle = title;
    toolTip1.Show(msg, sender as PictureBox, e.X, e.Y);
}
 
Last edited:
Apparently the balloon tooltip is not one of Microsoft's best efforts 🙄 IMHO it is bad enough that you cannot set the tooltip font unless you write a custom renderer, but it is insufferable that even this possibility is taken away for the balloon tooltip. Which, for all its sleek appearance, seems to have a bug or two of its own. And that, despite all the riches of .Net, one needs to result to pages of low-level Win32 programming to fix this. I start wondering what the tooltip actually does for you that a simple floating panel can't.
Ok, rant over.... thanks for the help.
 
Anyway, to partly answer your question. It looks like there is something regarding the Mouse Leave event that seems to be part of the problem. In the code below, if you remove the #if NEVER and [/icode]#endif[/icode], you'll see the balloon switch positions as the mouse gets lower within the client area.
 
Solution
Also recall that the original objectives of WinForms was to just provide a thin wrapper around the Win32 API common controls with the target of covering the 80% use case. Unfortunately, your use case doesn't fall within that 80%. The typical use case is just to pop up a tool tip over a form control to help the user fill in the fields, or to understand what some buttons or toolbar buttons are for.
 
Anyway, to partly answer your question. It looks like there is something regarding the Mouse Leave event that seems to be part of the problem. In the code below, if you remove the #if NEVER and [/icode]#endif[/icode], you'll see the balloon switch positions as the mouse gets lower within the client area.
I don't know what you are referring to here. Did you mean to post some code ?

Also recall that the original objectives of WinForms was to just provide a thin wrapper around the Win32 API common controls with the target of covering the 80% use case. Unfortunately, your use case doesn't fall within that 80%. The typical use case is just to pop up a tool tip over a form control to help the user fill in the fields, or to understand what some buttons or toolbar buttons are for.
It seems I am stretching the limits of the poor old toolTip... which I guess was never meant to be dynamic. Although I have to say it seems to cope fairly well - except from that silly change in appearance.
So I'll go with Kosta's solution. Or perhaps fall back on the non-balloon tooltip.
 
Sorry. Looks like I clicked in the wrong spot and didn't insert the code.
C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Drawing;
using System.Threading.Tasks;
using System.Threading;
using System.ComponentModel;

namespace WinForms
{
    class MainForm : Form
    {
        ToolTip _toolTip;

        MainForm()
        {
            Text = "Main";
            Size = new Size(800, 600);

            _toolTip = new ToolTip();
            _toolTip.IsBalloon = true;
            _toolTip.ToolTipTitle = "Location";

            CenterToScreen();
        }

        static Point Fudge = new Point(6, 30);

        protected override void OnMouseMove(MouseEventArgs e)
        {
            if (ClientRectangle.Contains(e.Location))
            {
                var location = e.Location;
                location.Offset(Fudge);
                _toolTip.Show($"{e.Location}  {location}", this, location);
            }
            else
            {
                _toolTip.Hide(this);
            }
            base.OnMouseMove(e);
        }

#if NEVER
        protected override void OnMouseLeave(EventArgs e)
        {
            _toolTip.Hide(this);
            base.OnMouseLeave(e);
        }
#endif

        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MainForm());
        }
    }
}
 
Sorry. Looks like I clicked in the wrong spot and didn't insert the code.
Wow, that works for me too ! When I leave out the MouseLeave trigger, the balloon tooltip behaves normally ! Many thanks for finding that out.
Luckily, I do not need any of MouseEnter, MouseLeave or MouseHover. Just MouseMove does the trick for me.
 
Last edited:
I've marked this as Resolved. Thanks again for your help 🙂👍
One thing I don't understand is why MouseLeave was firing at all, as my mouse never left the target picturebox ! And now that I added a MouseEnter, I see that being fired continuously as well. Then I found this post somewhere which confirms my findings :

Posted 6 Years Ago
View Quick Profile

Supreme Being

Supreme Being (4,274 reputation)
Supreme Being (4,274 reputation)
Supreme Being (4,274 reputation)
Supreme Being (4,274 reputation)
Supreme Being (4,274 reputation)
Supreme Being (4,274 reputation)
Supreme Being (4,274 reputation)
Supreme Being (4,274 reputation)
Supreme Being (4,274 reputation)

Group: Forum Members
Last Active: Last Month
Posts: 3,027, Visits: 3,672
Hi Lance,

Thank you for pointing out this problem. It turns out windows forms generates mouse enter / leave events for every mouse move which occurs in the hardware accelerated inner window of the control. We've implemented a workaround for this behavior and will publish a SP today or tomorrow. You can then test it and check whether it fixes the problem.

So it seems like hiding the toolTip in the MouseLeave was not as good an idea as I thought. Commenting out that single like make it work good as well.
 
Your MouseMove creates a new Bitmap object each move without disposing it, but it is not necessary to create one, you can cast the Image to Bitmap.
If it was necessary to create a bitmap, and it didn't change during MouseMove, then that should be created before that event.
 
Your MouseMove creates a new Bitmap object each move without disposing it, but it is not necessary to create one, you can cast the Image to Bitmap.
If it was necessary to create a bitmap, and it didn't change during MouseMove, then that should be created before that event.
Thanks John, good point. I had a vague feeling this could be more efficient 😀 I needed a bitmap only to get the pixel at the mouse position. Never realized that an Image could simply be casted as Bitmap ! That is a good thing to remember.
 
Back
Top Bottom