I found I was able to accomplish this by deriving from Form and not using ToolStripDropDown at all. This class emulates a tooltip's functionality and allows custom fade in/fade out parameters. You can either subscribe a control or any class that defines and implements ITooltipTarget for MouseEnter and MouseLeave.
public abstract class CustomTooltip : Form
{
#region Static
protected static readonly int FadeInterval = 25;
protected static readonly IntPtr HWND_TOPMOST = (IntPtr)(-1);
private const int SWP_NOSIZE = 0x0001;
private const int SWP_NOMOVE = 0x0002;
private const int SWP_NOACTIVATE = 0x0010;
private const int WS_POPUP = unchecked((int)0x80000000);
private const int WS_EX_TOPMOST = 0x00000008;
private const int WS_EX_NOACTIVATE = 0x08000000;
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
#endregion
protected Dictionary<object, object> subscriptions;
protected Timer popupTimer;
protected Timer fadeTimer;
protected bool isFading = false;
protected int fadeDirection = 1;
[DefaultValue(500)]
/// <summary>
/// Delay in milliseconds before the tooltip is shown. 0 means no delay.
/// </summary>
public int PopupDelay
{
get
{
return _popupDelay;
}
set
{
_popupDelay = value;
if (value > 0)
popupTimer.Interval = value;
else
popupTimer.Interval = 1;
}
}
private int _popupDelay = 500;
[DefaultValue(0)]
/// <summary>
/// How long to spend fading in and out in milliseconds. 0 means no fade.
/// </summary>
public int FadeTime
{
get
{
return _fadeTime;
}
set
{
_fadeTime = value;
}
}
private int _fadeTime = 0;
public virtual new object Tag
{
get
{
return base.Tag;
}
set
{
base.Tag = value;
OnTagChanged(EventArgs.Empty);
}
}
public CustomTooltip()
{
this.SetStyle(ControlStyles.Selectable, false);
subscriptions = new Dictionary<object, object>();
popupTimer = new Timer();
popupTimer.Interval = PopupDelay;
popupTimer.Tick += new EventHandler(popupTimer_Tick);
fadeTimer = new Timer();
fadeTimer.Interval = FadeInterval;
fadeTimer.Tick += new EventHandler(fadeTimer_Tick);
this.Visible = false;
this.ShowInTaskbar = false;
this.FormBorderStyle = FormBorderStyle.None;
this.ControlBox = false;
this.StartPosition = FormStartPosition.Manual;
}
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.Style |= WS_POPUP;
cp.ExStyle |= WS_EX_TOPMOST | WS_EX_NOACTIVATE;
return cp;
}
}
protected override bool ShowWithoutActivation
{
get
{
return true;
}
}
protected virtual void Subscribe(Control control, object tag)
{
subscriptions.Add(control, tag);
control.MouseEnter += new EventHandler(Item_MouseEnter);
control.MouseLeave += new EventHandler(Item_MouseLeave);
}
protected virtual void Subscribe(ITooltipTarget item, object tag)
{
subscriptions.Add(item, tag);
item.MouseEnter += new EventHandler(Item_MouseEnter);
item.MouseLeave += new EventHandler(Item_MouseLeave);
}
public virtual void Unsubscribe(Control control)
{
control.MouseEnter -= new EventHandler(Item_MouseEnter);
control.MouseLeave -= new EventHandler(Item_MouseLeave);
subscriptions.Remove(control);
}
public virtual void Unsubcribe(ITooltipTarget item)
{
item.MouseEnter -= new EventHandler(Item_MouseEnter);
item.MouseLeave -= new EventHandler(Item_MouseLeave);
subscriptions.Remove(item);
}
public void ClearSubscriptions()
{
foreach (object o in subscriptions.Keys)
{
if (o is Control)
Unsubscribe((Control)o);
else if (o is ITooltipTarget)
Unsubscribe((ITooltipTarget)o);
}
}
protected virtual void OnTagChanged(EventArgs e)
{
}
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
}
protected override void OnMouseEnter(EventArgs e)
{
base.OnMouseEnter(e);
Item_MouseLeave(null, EventArgs.Empty);
}
private void Item_MouseEnter(object sender, EventArgs e)
{
Tag = subscriptions[sender];
popupTimer.Start();
}
private void Item_MouseLeave(object sender, EventArgs e)
{
if (FadeTime > 0)
FadeOut();
else
this.Hide();
popupTimer.Stop();
}
protected virtual void FadeIn()
{
isFading = true;
Opacity = 0;
fadeDirection = 1;
fadeTimer.Start();
}
protected virtual void FadeOut()
{
isFading = true;
Opacity = 1;
fadeDirection = -1;
fadeTimer.Start();
}
private void popupTimer_Tick(object sender, EventArgs e)
{
if (isFading)
this.Hide();
if (FadeTime > 0)
FadeIn();
Location = new Point(Cursor.Position.X, Cursor.Position.Y + Cursor.Size.Height);
SetWindowPos(Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
Show();
popupTimer.Stop();
}
private void fadeTimer_Tick(object sender, EventArgs e)
{
if (Opacity == 0 && fadeDirection == -1)
{
isFading = false;
fadeTimer.Stop();
this.Hide();
}
else if (Opacity == 1 && fadeDirection == 1)
{
fadeTimer.Stop();
isFading = false;
}
else
{
double change = ((double)fadeTimer.Interval / (double)FadeTime) * (double)fadeDirection;
Opacity += change;
}
}
}
public interface ITooltipTarget
{
event EventHandler MouseEnter;
event EventHandler MouseLeave;
}
To use the above classes, you just need to derive from CustomTooltip to make your own custom drawn tooltip. The derived class would use the Tag property to determine the content displayed. For example, if I want a tooltip that associates an Image with an object and draws that image, I'd do something like:
public class CustomImageTooltip : CustomTooltip
{
public Image Image
{
get
{
if (Tag is Image)
return Tag as Image;
else
return null;
}
}
public CustomImageTooltip()
{
InitializeComponent();
this.SetStyle(ControlStyles.DoubleBuffer |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.UserPaint, true);
}
public void Subscribe(Control control, Image image)
{
base.Subscribe(control, image);
}
public void Subscribe(ITooltipTarget item, Image image)
{
base.Subscribe(item, image);
}
protected override void OnTagChanged(EventArgs e)
{
base.OnTagChanged(e);
if (Image != null)
this.Size = Image.Size;
}
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
g.Clear(Color.White);
if (Image != null)
g.DrawImage(
Image,
new RectangleF(0, 0, ClientSize.Width, ClientSize.Height),
new RectangleF(0, 0, Image.Size.Width, Image.Size.Height),
GraphicsUnit.Pixel
);
g.DrawRectangle(Pens.Black, 0, 0, ClientRectangle.Width - 1, ClientRectangle.Height - 1);
}
}
And in order to use this CustomImageTooltip class in your application, you'd need to just subscribe and unsubscribe to a single instance of the class:
// Constructor
customImageTooltip = new CustomImageTooltip();
foreach (CustomObject o in myCustomObjects)
{
customImageTooltip.Subscribe(o, o.Image);
}
// Destructor
foreach (CustomObject o in myCustomObjects)
{
customImageTooltip.Unsubscribe(o);
}