Is it possible to add a ToolTip to the form titlebar?

Viewed 379

On a Form, the title of the form is the Text that is set in the Designer file. The problem I am having is that when the title is internationalized, the title gets shortened and an ellipsis is added. Is it possible to add a tooltip that will display when we hover over the title? So if I am hovering over "Form1", a tooltip should display that says "Form1".

Form is written in C#. Since the title is not a control in the form, I can't set the tooltip.

Any ideas?

Form in designer

2 Answers

One approach would be to do this:

using System.Drawing;
using System.Threading.Tasks;

public Form1()
{
    InitializeComponent();
    ImplementToolBarToolTip();
}
private readonly ToolTip _toolTip1 = new ToolTip();
private async void ImplementToolBarToolTip()
{
    while (!IsDisposed)
    {
        await Task.Delay(200);
        var right = Left + Width;
        var bottom = Top + 39;
        var x = Cursor.Position.X;
        var y = Cursor.Position.Y;
        if (IsDisposed) return;
        if (x > Left && x < right && y > Top && y < bottom)
        {
            _toolTip1.Show(Text, this, PointToClient(new Point(x, y)));
        }
        else _toolTip1.Hide(this);
    }
}

Note: If you would prefer not to show the tooltip when the user mouses over the Close, Minimize, or Maximise buttons on the far right, then subtract 149 from the right value:

var right = Left + Width - 149;

enter image description here

Also, for a more sophisticated approach to a related problem dealing with MDI child forms, see This post by Reza Aghaei, who has now also replied to this question with a refactored implementation.

I've already shared an example in a linked post to show how to raise Hover event for non-client area of a form, including its title-bar and show a tooltip for titlebar.

In this post, I'll refactor and extend the other answer a bit by adding support for the following events and properties:

  • NonClientMouseHover Raises when mouse hovers over the non-client area.
  • NonClientMouseLeave: Raises when mouse leaves the non-client area.
  • TitleRectangle: Rectangle of the titlebar, excluding system menu.

Then by using those events and properties, I'll show a tooltip for titlebar:

enter image description here

Easily by having a tooltip component on a form which drives from a BaseForm class and handling the NonClientMouseHover and NonClientMouseLeave:

public partial class SampleForm : BaseForm
{
    public SampleForm()
    {
        InitializeComponent();
    }

    private void SampleForm_NonClientMouseHover(object sender, EventArgs e)
    {
        if (TitleRectangle.Contains(Cursor.Position))
            toolTip1.Show(Text, this, TitleRectangle.Left - this.Left + 1,
                TitleRectangle.Top - this.Top - 2, 5000);
    }

    private void SampleForm_NonClientMouseLeave(object sender, EventArgs e)
    {
        if (!TitleRectangle.Contains(Cursor.Position))
            toolTip1.Hide(this);
    }
}

And here are the native methods and the base class.

Native Methods

These are the native methods which are being used in the base class to handle non-client area messages:

using System;
using System.Drawing;
using System.Runtime.InteropServices;
public static class NativeMethods
{
    public const int WM_NCMOUSEMOVE = 0xA0;
    public const int WM_NCMOUSEHOVER = 0x2A0;
    public const int WM_NCMOUSELEAVE = 0x2A2;
    public const int TME_HOVER = 0x1;
    public const int TME_LEAVE = 0x2;
    public const int TME_NONCLIENT = 0x10;

    [DllImport("user32.dll")]
    public static extern int TrackMouseEvent(ref TRACK_MOUSE_EVENT lpEventTrack);

    [DllImport("user32.dll")]
    public static extern bool GetTitleBarInfo(IntPtr hwnd, ref TITLEBARINFO pti);
    public static Rectangle GetTitleBarRectangle(IntPtr hwnd)
    {
        var info = new TITLEBARINFO() 
        { cbSize = (uint)Marshal.SizeOf(typeof(TITLEBARINFO)) };
        GetTitleBarInfo(hwnd, ref info);
        return info.rcTitleBar.ToRectangle();
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct TRACK_MOUSE_EVENT
    {
        public uint cbSize;
        public uint dwFlags;
        public IntPtr hwndTrack;
        public uint dwHoverTime;
        public static readonly TRACK_MOUSE_EVENT Empty;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct TITLEBARINFO
    {
        public const int CCHILDREN_TITLEBAR = 5;
        public uint cbSize;
        public RECT rcTitleBar;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = CCHILDREN_TITLEBAR + 1)]
        public uint[] rgstate;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
        public int Left, Top, Right, Bottom;
        public Rectangle ToRectangle() => Rectangle.FromLTRB(Left, Top, Right, Bottom);
    }
}

BaseForm

This is a base Form class which raise non-client area events:

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using static NativeMethods;
public class BaseForm : Form
{
    public event EventHandler NonClientMouseHover;
    public event EventHandler NonClientMouseLeave;
    public event EventHandler NonClientMouseMove;
    private TRACK_MOUSE_EVENT track = TRACK_MOUSE_EVENT.Empty;
    public Rectangle TitleRectangle => GetTitleBarRectangle(Handle);
    protected override void WndProc(ref Message m)
    {
        base.WndProc(ref m);
        if (m.Msg == WM_NCMOUSEMOVE)
        {
            track.hwndTrack = this.Handle;
            track.cbSize = (uint)Marshal.SizeOf(track);
            track.dwFlags = TME_HOVER | TME_LEAVE | TME_NONCLIENT;
            track.dwHoverTime = 500;
            TrackMouseEvent(ref track);
            NonClientMouseMove?.Invoke(this, EventArgs.Empty);
        }
        else if (m.Msg == WM_NCMOUSEHOVER)
        {
            NonClientMouseHover?.Invoke(this, EventArgs.Empty);
        }
        else if (m.Msg == WM_NCMOUSELEAVE)
        {
            NonClientMouseLeave?.Invoke(this, EventArgs.Empty);
        }
    }
}
Related