2010-01-08 156 views
3

是否可以更改氣球工具提示中箭頭/符號的位置? 更改原因是因爲位於屏幕頂部的按鈕應該在其下方有一個工具提示。C#:設置ToolTip氣球中箭頭的位置?

破碎圖像鏈接刪除

以上的情況是,現在,我只需要箭頭是在氣球的左上方。

回答

1

根據MSDN,您不需要做任何特別的事情,如placement is automatic。氣球工具提示有問題嗎?

+0

請參閱我的編輯:) – MysticEarth 2010-01-08 15:38:48

+0

解決方案在於我把我的答案放在鏈接; MSDN解釋瞭如何通過修改TOOLINFO結構來覆蓋默認定位。當然,這將需要一些P/Invoke ... – 2010-01-08 15:48:10

+0

是的,我看到它。但坦率地說,我不知道從哪裏開始。 – MysticEarth 2010-01-08 15:54:07

2

我使用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(); 
    } 
} 

Here's my article with the complete source code

+0

如果調用者忘記調用Dispose()'/ Destroy()',窗口句柄將不會被釋放。是否有任何理由終結者沒有實施? – 2013-07-01 15:24:33

+0

是的。 MSDN說「一個線程不能使用DestroyWindow來銷燬由另一個線程創建的窗口」。從GC線程調用終結器。 – Soonts 2013-07-01 17:57:02

+0

夠公平的。雖然我不想依賴用戶是否要處理對象,但似乎並不是那種直接執行基於GC的處置故障轉移。我發現這個人有同樣的問題:http://bytes.com/topic/c-sharp/answers/446213-threading-problem-garbage-collector – 2013-07-02 08:20:40