C#: Set position of the arrow in a ToolTip Balloon?

后端 未结 2 1450
温柔的废话
温柔的废话 2021-01-14 05:28

Is it possible to change the location of the Arrow / Stem in a Balloon Tooltip? Reason to change is because of a Button, located in the top of the screen, should have a too

2条回答
  •  难免孤独
    2021-01-14 06:09

    I used InteropServices to call several User32.dll methods (CreateWindowEx, DestroyWindow, SetWindowPos and SendMessage) to use native Win32 tooltips instead of the WinForms-provided wrapper.

    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;
    
    /// 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.
    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.
    
        /// Send TTM_SETTITLE message to the tooltip.
        /// TODO [very low]: implement the custom icon, if needed. 
        /// HWND of the tooltip
        /// Standard icon
        /// The title string
        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;
                }
            }
        }
    
        /// Send a message that wants LPTOOLINFO as the lParam
        /// HWND of the tooltip.
        /// window message to send
        /// wParam value
        /// TOOLINFO structure that goes to the lParam field. The cbSize field must be set.
        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;
                }
            }
        }
    
        /// Registers a tool with a ToolTip control
        /// HWND of the tooltip
        /// TOOLINFO structure containing information that the ToolTip control needs to display text for the tool.
        /// Returns true if successful.
        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 );
        }
    
        /// Registers a tool with a ToolTip control
        /// HWND of the tooltip
        /// TOOLINFO structure containing information that the ToolTip control needs to display text for the tool.
        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 );
        }
    
        /// Activates or deactivates a tracking ToolTip
        /// HWND of the tooltip
        /// Value specifying whether tracking is being activated or deactivated
        /// Pointer to a TOOLINFO structure that identifies the tool to which this message applies
        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 );
        }
    
        /// returns (LPARAM) MAKELONG( pt.X, pt.Y )
        internal static IntPtr makeLParam( Point pt )
        {
            int res = ( pt.X & 0xFFFF );
            res |= ( ( pt.Y & 0xFFFF ) << 16 );
            return new IntPtr( res );
        }
    
        /// Sets the position of a tracking ToolTip
        /// HWND of the tooltip.
        /// The point at which the tracking ToolTip will be displayed, in screen coordinates.
        internal static void TrackPosition( IntPtr _wndTooltip, Point pt )
        {
            const int TTM_TRACKPOSITION = WM_USER + 18;
            SendMessage( _wndTooltip, TTM_TRACKPOSITION, 0, makeLParam( pt ) );
        }
    
        /// Sets the maximum width for a ToolTip window
        /// HWND of the tooltip.
        /// Maximum ToolTip window width, or -1 to allow any width
        /// the previous maximum ToolTip width
        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
    
        /// Sets the information that a ToolTip control maintains for a tool (not currently used).
        /// 
        internal void AlterToolInfo( Action 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;
    
        /// The maximum width for a ToolTip window.
        /// If a ToolTip string exceeds the maximum width, the control breaks the text into multiple lines.
        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;
        }
    
        /// Throws an exception if there's no alive WIN32 window.
        void VerifyControlIsAlive()
        {
            if( IntPtr.Zero == m_wndToolTip )
                throw new ApplicationException( "The control is not created, or is already destroyed." );
        }
    
        /// Create the balloon window.
        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;
    
        /// return true if the balloon is currently visible.
        public bool bVisible
        {
            get
            {
                if( IntPtr.Zero == m_wndToolTip )
                    return false;
                return m_bVisible;
            }
        }
    
        /// Balloon title.
        /// The balloon will only use the new value on the next Show() operation.
        public string strTitle;
    
        /// Balloon title icon.
        /// The balloon will only use the new value on the next Show() operation.
        public ToolTipIcon icon;
    
        /// Balloon title icon.
        /// The new value is updated immediately.
        public string strText
        {
            get { return m_toolinfo.text; }
            set
            {
                m_toolinfo.text = value;
                if( bVisible )
                    UpdateTipText( m_wndToolTip, m_toolinfo );
            }
        }
    
        /// Show the balloon.
        /// The balloon stem position, in the owner's client coordinates.
        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;
        }
    
        /// Hide the balloon if it's visible.
        /// If the balloon was previously hidden, this method does nothing.
        public void Hide()
        {
            VerifyControlIsAlive();
    
            if( !m_bVisible ) return;
    
            TrackActivate( m_wndToolTip, false, m_toolinfo );
    
            DelTool( m_wndToolTip, m_toolinfo );
    
            m_bVisible = false;
        }
    
        /// Destroy the balloon.
        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.

提交回复
热议问题