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:

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);
}
}
}