是否可以更改氣球工具提示中箭頭/符號的位置? 更改原因是因爲位於屏幕頂部的按鈕應該在其下方有一個工具提示。C#:設置ToolTip氣球中箭頭的位置?
破碎圖像鏈接刪除
以上的情況是,現在,我只需要箭頭是在氣球的左上方。
是否可以更改氣球工具提示中箭頭/符號的位置? 更改原因是因爲位於屏幕頂部的按鈕應該在其下方有一個工具提示。C#:設置ToolTip氣球中箭頭的位置?
破碎圖像鏈接刪除
以上的情況是,現在,我只需要箭頭是在氣球的左上方。
根據MSDN,您不需要做任何特別的事情,如placement is automatic。氣球工具提示有問題嗎?
我使用InteropServices來調用多個User32.dll方法(CreateWindowEx,DestroyWindow,SetWindowPos和SendMessage)以使用本機Win32工具提示而不是WinForms提供的包裝。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Drawing;
using System.ComponentModel;
/// <remarks>This classes implements the balloon tooltip.
/// http://stackoverflow.com/questions/2028466
/// I hated Microsoft WinForms QA department after I had to develop my own version of the tooltip class,
/// just to workaround a bug, that would only add ~5 lines of code into the system.windows.forms.dll when fixed properly.</remarks>
class BalloonToolTip : IDisposable
{
#region Unmanaged shit
[DllImport("user32.dll")]
static extern IntPtr CreateWindowEx(int exstyle, string classname, string windowtitle,
int style, int x, int y, int width, int height, IntPtr parent,
int menu, int nullvalue, int nullptr);
[DllImport("user32.dll")]
static extern int DestroyWindow(IntPtr hwnd);
[DllImport("user32.dll")]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags);
[DllImport("user32.dll")]
static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, IntPtr lParam);
const int WS_POPUP = unchecked((int)0x80000000);
const int TTS_BALLOON = 0x40;
const int TTS_NOPREFIX = 0x02;
const int TTS_ALWAYSTIP = 0x01;
const int CW_USEDEFAULT = unchecked((int)0x80000000);
const int WM_USER = 0x0400;
IntPtr HWND_TOPMOST = new IntPtr(-1);
const int SWP_NOSIZE = 0x0001;
const int SWP_NOMOVE = 0x0002;
const int SWP_NOACTIVATE = 0x0010;
const int WS_EX_TOPMOST = 0x00000008;
[StructLayout(LayoutKind.Sequential)]
public struct TOOLINFO
{
public int cbSize;
public int uFlags;
public IntPtr hwnd;
public IntPtr id;
private RECT m_rect;
public IntPtr nullvalue;
[MarshalAs(UnmanagedType.LPTStr)]
public string text;
public uint param;
public Rectangle rect
{
get
{
return new Rectangle(m_rect.left, m_rect.top, m_rect.right - m_rect.left, m_rect.bottom - m_rect.top);
}
set
{
m_rect.left = value.Left;
m_rect.top = value.Top;
m_rect.right = value.Right;
m_rect.bottom = value.Bottom;
}
}
}
[StructLayout(LayoutKind.Sequential)]
struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}
const int TTF_TRANSPARENT = 0x0100;
const int TTF_TRACK = 0x0020;
const int TTF_ABSOLUTE = 0x0080;
#endregion
#region Managed wrapper over some tooltip messages.
// The most useful part of the wrappers in this section is their documentation.
/// <summary>Send TTM_SETTITLE message to the tooltip.
/// TODO [very low]: implement the custom icon, if needed. </summary>
/// <param name="_wndTooltip">HWND of the tooltip</param>
/// <param name="_icon">Standard icon</param>
/// <param name="_title">The title string</param>
internal static int SetTitle(IntPtr _wndTooltip, ToolTipIcon _icon, string _title)
{
const int TTM_SETTITLE = WM_USER + 33;
var tempptr = IntPtr.Zero;
try
{
tempptr = Marshal.StringToHGlobalUni(_title);
return SendMessage(_wndTooltip, TTM_SETTITLE, (int)_icon, tempptr);
}
finally
{
if(IntPtr.Zero != tempptr)
{
Marshal.FreeHGlobal(tempptr);
tempptr = IntPtr.Zero;
}
}
}
/// <summary>Send a message that wants LPTOOLINFO as the lParam</summary>
/// <param name="_wndTooltip">HWND of the tooltip.</param>
/// <param name="_msg">window message to send</param>
/// <param name="_wParam">wParam value</param>
/// <param name="_ti">TOOLINFO structure that goes to the lParam field. The cbSize field must be set.</param>
internal static int SendToolInfoMessage(IntPtr _wndTooltip, int _msg, int _wParam, TOOLINFO _ti)
{
var tempptr = IntPtr.Zero;
try
{
tempptr = Marshal.AllocHGlobal(_ti.cbSize);
Marshal.StructureToPtr(_ti, tempptr, false);
return SendMessage(_wndTooltip, _msg, _wParam, tempptr);
}
finally
{
if(IntPtr.Zero != tempptr)
{
Marshal.FreeHGlobal(tempptr);
tempptr = IntPtr.Zero;
}
}
}
/// <summary>Registers a tool with a ToolTip control</summary>
/// <param name="_wndTooltip">HWND of the tooltip</param>
/// <param name="_ti">TOOLINFO structure containing information that the ToolTip control needs to display text for the tool.</param>
/// <returns>Returns true if successful.</returns>
internal static bool AddTool(IntPtr _wndTooltip, TOOLINFO _ti)
{
const int TTM_ADDTOOL = WM_USER + 50;
int res = SendToolInfoMessage(_wndTooltip, TTM_ADDTOOL, 0, _ti);
return Convert.ToBoolean(res);
}
/// <summary>Registers a tool with a ToolTip control</summary>
/// <param name="_wndTooltip">HWND of the tooltip</param>
/// <param name="_ti">TOOLINFO structure containing information that the ToolTip control needs to display text for the tool.</param>
internal static void DelTool(IntPtr _wndTooltip, TOOLINFO _ti)
{
const int TTM_DELTOOL = WM_USER + 51;
SendToolInfoMessage(_wndTooltip, TTM_DELTOOL, 0, _ti);
}
internal static void UpdateTipText(IntPtr _wndTooltip, TOOLINFO _ti)
{
const int TTM_UPDATETIPTEXT = WM_USER + 57;
SendToolInfoMessage(_wndTooltip, TTM_UPDATETIPTEXT, 0, _ti);
}
/// <summary>Activates or deactivates a tracking ToolTip</summary>
/// <param name="_wndTooltip">HWND of the tooltip</param>
/// <param name="bActivate">Value specifying whether tracking is being activated or deactivated</param>
/// <param name="_ti">Pointer to a TOOLINFO structure that identifies the tool to which this message applies</param>
internal static void TrackActivate(IntPtr _wndTooltip, bool bActivate, TOOLINFO _ti)
{
const int TTM_TRACKACTIVATE = WM_USER + 17;
SendToolInfoMessage(_wndTooltip, TTM_TRACKACTIVATE, Convert.ToInt32(bActivate), _ti);
}
/// <summary>returns (LPARAM) MAKELONG(pt.X, pt.Y)</summary>
internal static IntPtr makeLParam(Point pt)
{
int res = (pt.X & 0xFFFF);
res |= ((pt.Y & 0xFFFF) << 16);
return new IntPtr(res);
}
/// <summary>Sets the position of a tracking ToolTip</summary>
/// <param name="_wndTooltip">HWND of the tooltip.</param>
/// <param name="pt">The point at which the tracking ToolTip will be displayed, in screen coordinates.</param>
internal static void TrackPosition(IntPtr _wndTooltip, Point pt)
{
const int TTM_TRACKPOSITION = WM_USER + 18;
SendMessage(_wndTooltip, TTM_TRACKPOSITION, 0, makeLParam(pt));
}
/// <summary>Sets the maximum width for a ToolTip window</summary>
/// <param name="_wndTooltip">HWND of the tooltip.</param>
/// <param name="pxWidth">Maximum ToolTip window width, or -1 to allow any width</param>
/// <returns>the previous maximum ToolTip width</returns>
internal static int SetMaxTipWidth(IntPtr _wndTooltip, int pxWidth)
{
const int TTM_SETMAXTIPWIDTH = WM_USER + 24;
return SendMessage(_wndTooltip, TTM_SETMAXTIPWIDTH, 0, new IntPtr(pxWidth));
}
#endregion
/// <summary>Sets the information that a ToolTip control maintains for a tool (not currently used).</summary>
/// <param name="act"></param>
internal void AlterToolInfo(Action<TOOLINFO> act)
{
const int TTM_GETTOOLINFO = WM_USER + 53;
const int TTM_SETTOOLINFO = WM_USER + 54;
var tempptr = IntPtr.Zero;
try
{
tempptr = Marshal.AllocHGlobal(m_toolinfo.cbSize);
Marshal.StructureToPtr(m_toolinfo, tempptr, false);
SendMessage(m_wndToolTip, TTM_GETTOOLINFO, 0, tempptr);
m_toolinfo = (TOOLINFO)Marshal.PtrToStructure(tempptr, typeof(TOOLINFO));
act(m_toolinfo);
Marshal.StructureToPtr(m_toolinfo, tempptr, false);
SendMessage(m_wndToolTip, TTM_SETTOOLINFO, 0, tempptr);
}
finally
{
Marshal.FreeHGlobal(tempptr);
}
}
readonly Control m_ownerControl;
// The ToolTip's HWND
IntPtr m_wndToolTip = IntPtr.Zero;
/// <summary>The maximum width for a ToolTip window.
/// If a ToolTip string exceeds the maximum width, the control breaks the text into multiple lines.</summary>
int m_pxMaxWidth = 200;
TOOLINFO m_toolinfo = new TOOLINFO();
public BalloonToolTip(Control owner)
{
m_ownerControl = owner;
// See http://msdn.microsoft.com/en-us/library/bb760252(VS.85).aspx#tooltip_sample_rect
m_toolinfo.cbSize = Marshal.SizeOf(typeof(TOOLINFO));
m_toolinfo.uFlags = TTF_TRANSPARENT | TTF_TRACK;
m_toolinfo.hwnd = m_ownerControl.Handle;
m_toolinfo.rect = m_ownerControl.Bounds;
}
/// <summary>Throws an exception if there's no alive WIN32 window.</summary>
void VerifyControlIsAlive()
{
if(IntPtr.Zero == m_wndToolTip)
throw new ApplicationException("The control is not created, or is already destroyed.");
}
/// <summary>Create the balloon window.</summary>
public void Create()
{
if(IntPtr.Zero != m_wndToolTip)
Destroy();
// Create the tooltip control.
m_wndToolTip = CreateWindowEx(WS_EX_TOPMOST, "tooltips_class32",
string.Empty,
WS_POPUP | TTS_BALLOON | TTS_NOPREFIX | TTS_ALWAYSTIP,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
m_ownerControl.Handle, 0, 0, 0);
if(IntPtr.Zero == m_wndToolTip)
throw new Win32Exception();
if(!SetWindowPos(m_wndToolTip, HWND_TOPMOST,
0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE))
throw new Win32Exception();
SetMaxTipWidth(m_wndToolTip, m_pxMaxWidth);
}
bool m_bVisible = false;
/// <summary>return true if the balloon is currently visible.</summary>
public bool bVisible
{
get
{
if(IntPtr.Zero == m_wndToolTip)
return false;
return m_bVisible;
}
}
/// <summary>Balloon title.
/// The balloon will only use the new value on the next Show() operation.</summary>
public string strTitle;
/// <summary>Balloon title icon.
/// The balloon will only use the new value on the next Show() operation.</summary>
public ToolTipIcon icon;
/// <summary>Balloon title icon.
/// The new value is updated immediately.</summary>
public string strText
{
get { return m_toolinfo.text; }
set
{
m_toolinfo.text = value;
if(bVisible)
UpdateTipText(m_wndToolTip, m_toolinfo);
}
}
/// <summary>Show the balloon.</summary>
/// <param name="pt">The balloon stem position, in the owner's client coordinates.</param>
public void Show(Point pt)
{
VerifyControlIsAlive();
if(m_bVisible) Hide();
// http://www.deez.info/sengelha/2008/06/12/balloon-tooltips/
if(!AddTool(m_wndToolTip, m_toolinfo))
throw new ApplicationException("Unable to register the tooltip");
SetTitle(m_wndToolTip, icon, strTitle);
TrackPosition(m_wndToolTip, m_ownerControl.PointToScreen(pt));
TrackActivate(m_wndToolTip, true, m_toolinfo);
m_bVisible = true;
}
/// <summary>Hide the balloon if it's visible.
/// If the balloon was previously hidden, this method does nothing.</summary>
public void Hide()
{
VerifyControlIsAlive();
if(!m_bVisible) return;
TrackActivate(m_wndToolTip, false, m_toolinfo);
DelTool(m_wndToolTip, m_toolinfo);
m_bVisible = false;
}
/// <summary>Destroy the balloon.</summary>
public void Destroy()
{
if(IntPtr.Zero == m_wndToolTip)
return;
if(m_bVisible) Hide();
DestroyWindow(m_wndToolTip);
m_wndToolTip = IntPtr.Zero;
}
void IDisposable.Dispose()
{
Destroy();
}
}
如果調用者忘記調用Dispose()'/ Destroy()',窗口句柄將不會被釋放。是否有任何理由終結者沒有實施? – 2013-07-01 15:24:33
是的。 MSDN說「一個線程不能使用DestroyWindow來銷燬由另一個線程創建的窗口」。從GC線程調用終結器。 – Soonts 2013-07-01 17:57:02
夠公平的。雖然我不想依賴用戶是否要處理對象,但似乎並不是那種直接執行基於GC的處置故障轉移。我發現這個人有同樣的問題:http://bytes.com/topic/c-sharp/answers/446213-threading-problem-garbage-collector – 2013-07-02 08:20:40
請參閱我的編輯:) – MysticEarth 2010-01-08 15:38:48
解決方案在於我把我的答案放在鏈接; MSDN解釋瞭如何通過修改TOOLINFO結構來覆蓋默認定位。當然,這將需要一些P/Invoke ... – 2010-01-08 15:48:10
是的,我看到它。但坦率地說,我不知道從哪裏開始。 – MysticEarth 2010-01-08 15:54:07