我试图通过调用“ToolTip.Show(String, IWin32Window, Point)”来显示工具提示,但我想像 Windows 资源管理器那样做——在光标的左下角显示工具提示。
我可以通过“MousePosition”获得鼠标位置,但我怎样才能获得它的左下角位置呢?
谢谢,
如果没有人想出更好的答案,你可以试试这个:
toolTip1.Show("Am I where you want me to be?", this, this.PointToClient(MousePosition).X,
this.PointToClient(MousePosition).Y + Cursor.Size.Height * 2);
通过使用 x/y 参数调整文本位置。它适用于我的机器,但我不确定它在不同设置下的外观。
ToolTip 有趣的提示:将这一行放在 Form 的 MouseMove 事件中。
唯一的方法是扫描光标 MASK 并找到光标掩码中最后设置的像素与光标 Y 热点之间的距离,我今天必须这样做,所以这里是代码:
#define useUnsafe
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System;
using System.Windows.Forms;
namespace Utils
{
/// <summary>
/// Provides extension methods for the Cursor class
/// </summary>
/// <remarks>By Aaron Murgatroyd</remarks>
public static class CursorExtensionMethods
{
#region API Functions
/// <summary>
/// Contains the icon information for a Windows API icon
/// </summary>
private struct IconInfo
{
public bool fIcon;
public int xHotspot;
public int yHotspot;
public IntPtr hbmMask;
public IntPtr hbmColor;
}
/// <summary>
/// Gets the icon information for a Windows API icon
/// </summary>
/// <param name="hIcon">The icon to get the info for</param>
/// <param name="pIconInfo">The object to receive the info</param>
/// <returns>True on success, false on failure</returns>
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetIconInfo(IntPtr hIcon, ref IconInfo pIconInfo);
[DllImport("gdi32.dll")]
static extern bool DeleteObject(IntPtr hObject);
#endregion
#region Private Static Methods
/// <summary>
/// Scans bits in bitmap data for a set or unset bit
/// </summary>
/// <param name="byteData">The pointer to the first byte of the first scanline</param>
/// <param name="start">The vertical position to start the scan</param>
/// <param name="lineInc">The number of bytes to move per line</param>
/// <param name="maxLines">The number of lines to scan</param>
/// <param name="set">True to scan for set bits, false to scan for unset bits</param>
/// <param name="fromBottom">True to scan from the bottom of the bitmap, false to scan from the top</param>
/// <returns>The number of lines scanned before a bit was found, or -1 if none found before reaching max lines</returns>
#if useUnsafe
private static unsafe int ScanBits(IntPtr byteData, int start, int lineInc, int maxLines, bool set, bool fromBottom)
#else
private static int ScanBits(IntPtr byteData, int start, int lineInc, int maxLines, bool set, bool fromBottom)
#endif
{
// Calculate the starting byte of the first scanline
#if useUnsafe
byte* lbLine = ((byte*)byteData) + (start * lineInc);
#else
int lbLine = ((int)(byteData) + (start * lineInc));
#endif
int liLine = 0;
// Use lineInc to determines bytes per line
int liBytesPerLine = (lineInc < 0 ? -lineInc : lineInc);
// If we want to search in reverse order
if (fromBottom)
{
// Move to the START of the line
lbLine += lineInc * (maxLines - 1);
// Negate the line increment
lineInc = -lineInc;
}
while (maxLines > 0)
{
// Setup the line scan
#if useUnsafe
byte* lbData = lbLine;
#else
int lbData = lbLine;
#endif
int liByte = liBytesPerLine;
// For each byte in the line
while (liByte > 0)
{
#if !useUnsafe
byte lbByte = Marshal.ReadByte((IntPtr)lbData);
#endif
// If we want set bits, and a bit is set
#if useUnsafe
if (set && *lbData != 0)
#else
if (set && lbByte != 0)
#endif
// Return the line number
return liLine;
else
// If we want unset bits and any bits arent set
#if useUnsafe
if (!set && *lbData != byte.MaxValue)
#else
if (!set && lbByte != byte.MaxValue)
#endif
// Return the line number
return liLine;
// Next byte for scan line
liByte--;
lbData++;
}
// Next scan line
liLine++;
maxLines--;
lbLine += lineInc;
}
// If all lines were scanned, return -1
if (maxLines == 0)
return -1;
else
// Return number of lines scanned
return liLine;
}
#endregion
#region Public Static Methods
/// <summary>
/// Gets the number of pixels between the Y hotspot
/// and the last physical line of a cursor
/// </summary>
/// <param name="cursor">The cursor to scan</param>
/// <returns>
/// The number of lines between the Y hotspot
/// and the last physical line of the cursor
/// </returns>
public static int GetBaseLineHeight(this Cursor cursor)
{
return GetBaseLine(cursor) - cursor.HotSpot.Y;
}
/// <summary>
/// Gets the physical base line of the cursor, that is,
/// the distance between the top of the virtual cursor
/// and the physical base line of the cursor
/// </summary>
/// <param name="cursor">The cursor to scan</param>
/// <returns>The number of lines between the top of the virtual cursor
/// and the physical base line of the curosr</returns>
public static int GetBaseLine(this Cursor cursor)
{
IconInfo liiInfo = new IconInfo();
if (!GetIconInfo(cursor.Handle, ref liiInfo))
return cursor.Size.Height;
Bitmap lbmpBitmap = Bitmap.FromHbitmap(liiInfo.hbmMask);
try
{
BitmapData lbdData = lbmpBitmap.LockBits(
new Rectangle(0, 0, lbmpBitmap.Width, lbmpBitmap.Height),
ImageLockMode.ReadOnly, PixelFormat.Format1bppIndexed);
try
{
// Calculate number of lines in AND scan before any found
int liLine = ScanBits(lbdData.Scan0, 0, lbdData.Stride, cursor.Size.Height, false, true);
// If no AND scan bits found then scan for XOR bits
if (liLine == -1 && lbdData.Height == cursor.Size.Height * 2)
liLine = ScanBits(lbdData.Scan0, cursor.Size.Height, lbdData.Stride, cursor.Size.Height, true, true);
return cursor.Size.Height-liLine;
}
finally
{
lbmpBitmap.UnlockBits(lbdData);
}
}
finally
{
DeleteObject(liiInfo.hbmMask);
DeleteObject(liiInfo.hbmColor);
lbmpBitmap.Dispose();
}
}
#endregion
}
}
您可以取消定义顶部的条件定义“useUnsafe”,这样您就不必根据需要启用不安全代码,但请注意它在此模式下运行速度会变慢。
所以这使用了扩展方法,所以你所要做的就是将 Cursor.Current.GetBaseLineHeight() 添加到你的 Cursor.Position.Y 中,这将是光标下的第一个空白行。
IE。
Point lptBlankLineUnderCursor = new Point(Cursor.Position.X, Cursor.Position.Y + Cursor.Current.GetBaseLineHeight())
我认为资源管理器将工具提示放在光标的热点下方,因此您不必更正 X 位置。这看起来不错:
private void panel1_MouseClick(object sender, MouseEventArgs e) {
int x = e.X;
int y = e.Y + Cursor.Current.Size.Height - Cursor.Current.HotSpot.Y;
toolTip1.Show("test", panel1, x, y);
}