可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I am using a ToolTip
control on my form, but have discovered that even though my cursor is on one control, the tooltip is showing somewhere else. I would like to show this within the control my cursor is on.
As shown in the image above, when my cursor is over Textbox3
, the tooltip is showing on Textbox4
. I would like for it to be displayed pointing at Textbox3
.
I'm currently using the following code to display the tooltip in 3 different events:
private void txtImmediateddest_Enter(object sender, EventArgs e)
{
ttpDetail.Show("Ex:111000025", txtImmediateddest);
}
private void txtImmediateddest_MouseHover(object sender, EventArgs e)
{
ttpDetail.Show("Ex:111000025", txtImmediateddest);
}
private void txtImmediateddest_MouseUp(object sender, MouseEventArgs e)
{
ttpDetail.Show("Ex:111000025", txtImmediateddest, e.Location);
//toolTipimmeddest.Show("Required & Must be 9 Digits", txtImmediateddest);
}
Edit
private void textBox1_MouseHover(object sender, EventArgs e)
{
ttpDetail.AutoPopDelay = 2000;
ttpDetail.InitialDelay = 1000;
ttpDetail.ReshowDelay = 500;
ttpDetail.IsBalloon = true;
//ttpDetail.SetToolTip(textBox1, "Ex:01(Should be Numeric)");
ttpDetail.Show("Ex : 01(Should Be Numeric)", textBox1,textBox1.Width, textBox1.Height/10,5000);
}
This works fine but when the mouse initially on to the control it is displaying the normal if i had for second time it is displaying correctly
Look at the following images
回答1:
The problem you're seeing is because your ToolTip
control's IsBalloon
property is set to "True". With this property set, the ToolTip
doesn't change its relative location, causing the balloon's arrow to point to the wrong control.
Here's a side-by-side comparison demonstrating this phenomenon:
The simple fix, obviously, is to disable the IsBalloon
property by setting it to "False". The control will revert to displaying a standard, rectangular tooltip window, which will look correctly aligned.
If that's not acceptable to you, then you will have to specify the exact location where you want the tooltip balloon to appear. Unfortunately, it looks like there's a bug in the ToolTip
control that causes it not to appear properly the first time it is attached to a control. This can generally be fixed by calling the Show
method with an empty string once. For example, using the following code:
private void txtImmediateddest_Enter(object sender, EventArgs e)
{
ttpDetail.Show(string.Empty, textBox3, 0);
ttpDetail.Show("Ex:111000025", textBox3, textBox3.Width / 2, textBox3.Height, 5000);
}
produces this result:
Of course, your luck may vary going this route, as well. I generally don't use the built-in ToolTip
control for edit controls (such as textboxes and comboboxes). I find it's much more reliable to P/Invoke SendMessage
, specifying EM_SHOWBALLOONTIP
and an EDITBALLOONTIP
structure containing information about the tooltip that I want to show. I'll leave looking up the appropriate definitions and writing the wrapper code as an exercise for the reader, as this answer is much too long already.
回答2:
After a lot of troubleshooting I found the code bellow to be net superior to the build-in balloon ToolTip. Make sure Visual Styles are enabled by uncommenting the dependency in the manifest file.
Create a BalloonTip over a TextBox like this:
new BalloonTip("Title", "Message", textBox1, BalloonTip.ICON.INFO, 5000);
and implement BalloonTip
like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace Lib.Windows
{
class BalloonTip
{
private System.Timers.Timer timer = new System.Timers.Timer();
private SemaphoreSlim semaphore = new SemaphoreSlim(1);
private IntPtr hWnd;
public BalloonTip(string text, Control control)
{
Show("", text, control);
}
public BalloonTip(string title, string text, Control control, ICON icon = 0, double timeOut = 0, bool focus = false)
{
Show(title, text, control, icon, timeOut, focus);
}
void Show(string title, string text, Control control, ICON icon = 0, double timeOut = 0, bool focus = false, short x = 0, short y = 0)
{
if (x == 0 && y == 0)
{
x = (short)(control.RectangleToScreen(control.ClientRectangle).Left + control.Width / 2);
y = (short)(control.RectangleToScreen(control.ClientRectangle).Top + control.Height / 2);
}
TOOLINFO toolInfo = new TOOLINFO();
toolInfo.cbSize = (uint)Marshal.SizeOf(toolInfo);
toolInfo.uFlags = 0x20; // TTF_TRACK
toolInfo.lpszText = text;
IntPtr pToolInfo = Marshal.AllocCoTaskMem(Marshal.SizeOf(toolInfo));
Marshal.StructureToPtr(toolInfo, pToolInfo, false);
byte[] buffer = Encoding.UTF8.GetBytes(title);
buffer = buffer.Concat(new byte[] { 0 }).ToArray();
IntPtr pszTitle = Marshal.AllocCoTaskMem(buffer.Length);
Marshal.Copy(buffer, 0, pszTitle, buffer.Length);
hWnd = User32.CreateWindowEx(0x8, "tooltips_class32", "", 0xC3, 0, 0, 0, 0, control.Parent.Handle, (IntPtr)0, (IntPtr)0, (IntPtr)0);
User32.SendMessage(hWnd, 1028, (IntPtr)0, pToolInfo); // TTM_ADDTOOL
User32.SendMessage(hWnd, 1042, (IntPtr)0, (IntPtr)((ushort)x | ((ushort)y << 16))); // TTM_TRACKPOSITION
//User32.SendMessage(hWnd, 1043, (IntPtr)0, (IntPtr)0); // TTM_SETTIPBKCOLOR
//User32.SendMessage(hWnd, 1044, (IntPtr)0xffff, (IntPtr)0); // TTM_SETTIPTEXTCOLOR
User32.SendMessage(hWnd, 1056, (IntPtr)icon, pszTitle); // TTM_SETTITLE 0:None, 1:Info, 2:Warning, 3:Error, >3:assumed to be an hIcon. ; 1057 for Unicode
User32.SendMessage(hWnd, 1048, (IntPtr)0, (IntPtr)500); // TTM_SETMAXTIPWIDTH
User32.SendMessage(hWnd, 0x40c, (IntPtr)0, pToolInfo); // TTM_UPDATETIPTEXT; 0x439 for Unicode
User32.SendMessage(hWnd, 1041, (IntPtr)1, pToolInfo); // TTM_TRACKACTIVATE
Marshal.FreeCoTaskMem(pszTitle);
Marshal.DestroyStructure(pToolInfo, typeof(TOOLINFO));
Marshal.FreeCoTaskMem(pToolInfo);
if (focus)
control.Focus();
// uncomment bellow to make balloon close when user changes focus,
// starts typing, resizes/moves parent window, minimizes parent window, etc
// adjust which control events to subscribe to depending on the control over which the balloon tip is shown
/*control.Click += control_Event;
control.Leave += control_Event;
control.TextChanged += control_Event;
control.LocationChanged += control_Event;
control.SizeChanged += control_Event;
control.VisibleChanged += control_Event;
Control parent = control.Parent;
while(parent != null)
{
parent.VisibleChanged += control_Event;
parent = parent.Parent;
}
control.TopLevelControl.LocationChanged += control_Event;
((Form)control.TopLevelControl).Deactivate += control_Event;*/
timer.AutoReset = false;
timer.Elapsed += timer_Elapsed;
if (timeOut > 0)
{
timer.Interval = timeOut;
timer.Start();
}
}
void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
Close();
}
void control_Event(object sender, EventArgs e)
{
Close();
}
void Close()
{
if (!semaphore.Wait(0)) // ensures one time only execution
return;
timer.Elapsed -= timer_Elapsed;
timer.Close();
User32.SendMessage(hWnd, 0x0010, (IntPtr)0, (IntPtr)0); // WM_CLOSE
//User32.SendMessage(hWnd, 0x0002, (IntPtr)0, (IntPtr)0); // WM_DESTROY
//User32.SendMessage(hWnd, 0x0082, (IntPtr)0, (IntPtr)0); // WM_NCDESTROY
}
[StructLayout(LayoutKind.Sequential)]
struct TOOLINFO
{
public uint cbSize;
public uint uFlags;
public IntPtr hwnd;
public IntPtr uId;
public RECT rect;
public IntPtr hinst;
[MarshalAs(UnmanagedType.LPStr)]
public string lpszText;
public IntPtr lParam;
}
[StructLayout(LayoutKind.Sequential)]
struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
public enum ICON
{
NONE,
INFO,
WARNING,
ERROR
}
}
static class User32
{
[DllImportAttribute("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
[DllImportAttribute("user32.dll")]
public static extern IntPtr CreateWindowEx(uint dwExStyle, string lpClassName, string lpWindowName, uint dwStyle, int x, int y, int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, IntPtr LPVOIDlpParam);
}
}
This is how it looks:
回答3:
Have you tried to use only the SetToolTip method (with out calling the show method) in the MouseOver event
ttpTemp.SetToolTip(txtTemp, "Ex:01(Should be Numeric)");
This works fine for me (I use Managed C++ but I think it is the same).
回答4:
With credits to Chris' answer, I post VB.NET port here:
Imports System.Collections.Generic
Imports System
Imports System.Linq
Imports System.Text
Imports System.Windows.Forms
Imports System.Runtime.InteropServices
Namespace [Lib].Windows
Class BalloonTip
Private timer As New System.Timers.Timer()
Private semaphore As New System.Threading.SemaphoreSlim(1)
Private hWnd As IntPtr
Public Sub New(text As String, control As Control)
Show("", text, control)
End Sub
Public Sub New(title As String, text As String, control As Control, Optional icon As ICON = 0, Optional timeOut As Double = 0, Optional focus As Boolean = False)
Show(title, text, control, icon, timeOut, focus)
End Sub
Private Sub Show(title As String, text As String, control As Control, Optional icon As ICON = 0, Optional timeout As Double = 0, Optional focus As Boolean = False)
Dim x As UShort = CType(control.RectangleToScreen(control.ClientRectangle).Left + control.Width / 2, UShort)
Dim y As UShort = CType(control.RectangleToScreen(control.ClientRectangle).Top + control.Height / 2, UShort)
Dim toolInfo As New TOOLINFO()
toolInfo.cbSize = CType(Marshal.SizeOf(toolInfo), UInteger)
toolInfo.uFlags = &H20
' TTF_TRACK
toolInfo.lpszText = text
Dim pToolInfo As IntPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(toolInfo))
Marshal.StructureToPtr(toolInfo, pToolInfo, False)
Dim buffer As Byte() = Encoding.UTF8.GetBytes(title)
buffer = buffer.Concat(New Byte() {0}).ToArray()
Dim pszTitle As IntPtr = Marshal.AllocCoTaskMem(buffer.Length)
Marshal.Copy(buffer, 0, pszTitle, buffer.Length)
hWnd = User32.CreateWindowEx(&H8, "tooltips_class32", "", &HC3, 0, 0, _
0, 0, control.Parent.Handle, CType(0, IntPtr), CType(0, IntPtr), CType(0, IntPtr))
User32.SendMessage(hWnd, 1028, CType(0, IntPtr), pToolInfo)
' TTM_ADDTOOL
'User32.SendMessage(hWnd, 1043, CType(0, IntPtr), CType(0, IntPtr); ' TTM_SETTIPBKCOLOR
'User32.SendMessage(hWnd, 1044, CType(&HFFFF, IntPtr), CType(0, IntPtr); ' TTM_SETTIPTEXTCOLOR
User32.SendMessage(hWnd, 1056, CType(icon, IntPtr), pszTitle)
' TTM_SETTITLE 0:None, 1:Info, 2:Warning, 3:Error, >3:assumed to be an hIcon. ; 1057 for Unicode
User32.SendMessage(hWnd, 1048, CType(0, IntPtr), CType(500, IntPtr))
' TTM_SETMAXTIPWIDTH
User32.SendMessage(hWnd, &H40C, CType(0, IntPtr), pToolInfo)
' TTM_UPDATETIPTEXT; 0x439 for Unicode
User32.SendMessage(hWnd, 1042, CType(0, IntPtr), CType(x Or (CUInt(y) << 16), IntPtr))
' TTM_TRACKPOSITION
User32.SendMessage(hWnd, 1041, CType(1, IntPtr), pToolInfo)
' TTM_TRACKACTIVATE
Marshal.FreeCoTaskMem(pszTitle)
Marshal.DestroyStructure(pToolInfo, GetType(TOOLINFO))
Marshal.FreeCoTaskMem(pToolInfo)
If focus Then
control.Focus()
End If
' uncomment below to make balloon close when user changes focus,
' starts typing, resizes/moves parent window, minimizes parent window, etc
' adjust which control events to subscribe to depending on the control over which the balloon tip is shown
'AddHandler control.Click, AddressOf control_Event
'AddHandler control.Leave, AddressOf control_Event
'AddHandler control.TextChanged, AddressOf control_Event
'AddHandler control.LocationChanged, AddressOf control_Event
'AddHandler control.SizeChanged, AddressOf control_Event
'AddHandler control.VisibleChanged, AddressOf control_Event
'Dim parent As Control = control.Parent
'While Not (parent Is Nothing)
' AddHandler parent.VisibleChanged, AddressOf control_Event
' parent = parent.Parent
'End While
'AddHandler control.TopLevelControl.LocationChanged, AddressOf control_Event
'AddHandler DirectCast(control.TopLevelControl, Form).Deactivate, AddressOf control_Event
timer.AutoReset = False
RemoveHandler timer.Elapsed, AddressOf timer_Elapsed
If timeout > 0 Then
timer.Interval = timeout
timer.Start()
End If
End Sub
Private Sub timer_Elapsed(sender As Object, e As System.Timers.ElapsedEventArgs)
Close()
End Sub
Private Sub control_Event(sender As Object, e As EventArgs)
Close()
End Sub
Sub Close()
If Not semaphore.Wait(0) Then
' ensures one time only execution
Return
End If
RemoveHandler timer.Elapsed, AddressOf timer_Elapsed
timer.Close()
User32.SendMessage(hWnd, &H10, CType(0, IntPtr), CType(0, IntPtr))
' WM_CLOSE
'User32.SendMessage(hWnd, &H0002, CType(0, IntPtr), CType(0, IntPtr)); ' WM_DESTROY
'User32.SendMessage(hWnd, &H0082, CType(0, IntPtr), CType(0, IntPtr)); ' WM_NCDESTROY
End Sub
<StructLayout(LayoutKind.Sequential)> _
Private Structure TOOLINFO
Public cbSize As UInteger
Public uFlags As UInteger
Public hwnd As IntPtr
Public uId As IntPtr
Public rect As RECT
Public hinst As IntPtr
<MarshalAs(UnmanagedType.LPStr)> _
Public lpszText As String
Public lParam As IntPtr
End Structure
<StructLayout(LayoutKind.Sequential)> _
Private Structure RECT
Public Left As Integer
Public Top As Integer
Public Right As Integer
Public Bottom As Integer
End Structure
Public Enum ICON
NONE
INFO
WARNING
[ERROR]
End Enum
End Class
NotInheritable Class User32
Private Sub New()
End Sub
<DllImportAttribute("user32.dll")> _
Public Shared Function SendMessage(hWnd As IntPtr, Msg As UInt32, wParam As IntPtr, lParam As IntPtr) As Integer
End Function
<DllImportAttribute("user32.dll")> _
Public Shared Function CreateWindowEx(dwExStyle As UInteger, lpClassName As String, lpWindowName As String, dwStyle As UInteger, x As Integer, y As Integer, _
nWidth As Integer, nHeight As Integer, hWndParent As IntPtr, hMenu As IntPtr, hInstance As IntPtr, LPVOIDlpParam As IntPtr) As IntPtr
End Function
End Class
End Namespace
回答5:
Hey i got at last by this code
When MouseLeave
public class MouseLeave
{
public void mouseLeave(TextBox txtTemp, ToolTip ttpTemp)
{
ttpTemp.Hide(txtTemp);
}
}
When Mouse Over
public class MouseOver
{
public void mouseOver(TextBox txtTemp, ToolTip ttpTemp)
{
switch (txtTemp.Name)
{
case "textBox1":
{
ttpTemp.AutoPopDelay = 2000;
ttpTemp.InitialDelay = 1000;
ttpTemp.ReshowDelay = 500;
ttpTemp.IsBalloon = true;
ttpTemp.SetToolTip(txtTemp, "Ex:01(Should be Numeric)");
ttpTemp.Show("Ex : 01(Should Be Numeric)", txtTemp, txtTemp.Width, txtTemp.Height / 10, 5000);
}
break;
case "txtDetail":
{
ttpTemp.AutoPopDelay = 2000;
ttpTemp.InitialDelay = 1000;
ttpTemp.ReshowDelay = 500;
ttpTemp.IsBalloon = true;
ttpTemp.SetToolTip(txtTemp, "Ex:01(Should be Numeric)");
ttpTemp.Show("Ex : 01(Should Be Numeric)", txtTemp, txtTemp.Width, txtTemp.Height / 10, 5000);
}
break;
}
}
}