2010-01-08 6 views

답변

1

MSDN에 따라 placement is automatic과 같이 특별한 작업을 수행 할 필요는 없습니다. 풍선 툴팁에 문제가 있습니까?

+0

내 편집을 참조하십시오. – MysticEarth

+0

솔루션은 내가 내 대답에 넣은 링크에 있습니다. MSDN에서는 TOOLINFO 구조체를 수정하여 기본 위치 지정을 재정의 할 수있는 방법을 설명합니다. 물론, 이것은 약간의 P/Invoke가 필요할 것입니다. –

+0

그래, 나는 그것을 보았다. 그러나 솔직히, 나는 어디에서 시작 해야할지 모르겠다. – MysticEarth

2

InteropServices를 사용하여 WinForms 제공 래퍼 대신 기본 Win32 도구 설명을 사용하도록 여러 User32.dll 메서드 (CreateWindowEx, DestroyWindow, SetWindowPos 및 SendMessage)를 호출했습니다.

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()'를 호출하는 것을 잊어 버리면 윈도우 핸들러가 해제되지 않습니다. 파이널 라이저가 구현되지 않은 이유가 있습니까? –

+0

예. MSDN은 "스레드는 DestroyWindow를 사용하여 다른 스레드에 의해 생성 된 창을 파괴 할 수 없습니다"라고 말합니다. 그리고 파이널 라이저는 GC 스레드에서 호출됩니다. – Soonts

+0

충분합니다. 사용자가 객체를 처리 할 것인지의 여부에 의존하지 않는 것이 좋지만 GC 기반의 페일 오버를 간단하게 구현하는 것이 아닙니다. 나는이 사람이 같은 문제를 안고 있음을 발견했다 : http://bytes.com/topic/c-sharp/answers/446213-threading-problem-garbage-collector –