File: winforms\Managed\System\WinForms\PropertyGridInternal\GridToolTip.cs
Project: ndp\fx\src\System.Windows.Forms.csproj (System.Windows.Forms)
//------------------------------------------------------------------------------
// <copyright file="GridToolTip.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------
 
/*
 */
namespace System.Windows.Forms.PropertyGridInternal {
    using System.Runtime.Serialization.Formatters;
    using System.Runtime.InteropServices;
    using System.Runtime.Remoting;
    using System.Diagnostics;
 
    using System;
    using System.Collections;
    
    using System.Windows.Forms;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.IO;
    using System.Drawing;
    using Microsoft.Win32;
    using Message = System.Windows.Forms.Message;
 
    internal class GridToolTip : Control {
 
        Control[]    controls;
        string       toolTipText;
        NativeMethods.TOOLINFO_T[]   toolInfos;
        bool         dontShow;
        Point        lastMouseMove = Point.Empty;
        private int maximumToolTipLength = 1000;
        private bool _positioned = false;
 
        internal GridToolTip(Control[] controls) {
            this.controls = controls;
            SetStyle(ControlStyles.UserPaint, false);
            this.Font = controls[0].Font;
            this.toolInfos = new NativeMethods.TOOLINFO_T[controls.Length];
            
            for (int i = 0; i < controls.Length; i++) {
                  controls[i].HandleCreated += new EventHandler(this.OnControlCreateHandle);
                  controls[i].HandleDestroyed += new EventHandler(this.OnControlDestroyHandle);
                  
                  if (controls[i].IsHandleCreated) {
                     SetupToolTip(controls[i]);
                  }
            }
        }
 
        public string ToolTip{
            get {
               return toolTipText;
            }
            set {
                  if (this.IsHandleCreated || !String.IsNullOrEmpty(value)) {
                      this.Reset();
                  }
 
                  if (value != null && value.Length > maximumToolTipLength)
                  {
					  //Let the user know the text was truncated by throwing on an ellipsis
                      value = value.Substring(0, maximumToolTipLength) + "...";
                  }
                  this.toolTipText = value;
 
                  if (this.IsHandleCreated) {
 
                     bool visible = this.Visible;
 
                     if (visible){
                        this.Visible = false;
                     }
 
                     // here's a hack.  if we give
                     // the tooltip an empty string, it won't come back
                     // so we just force it hidden instead
                     //
                     if (value == null || value.Length == 0){
                        dontShow = true;
                        value = "";
                     }
                     else{
                        dontShow = false;
                     }
                     
                     for (int i = 0; i < controls.Length; i++) {
                        UnsafeNativeMethods.SendMessage(new HandleRef(this, Handle), NativeMethods.TTM_UPDATETIPTEXT, 0, GetTOOLINFO(controls[i]));
                     }
 
                     if (visible && !dontShow){
                        this.Visible = true;
                    }
                  
               }
            }
        }
 
 
        /// <include file='doc\GridToolTip.uex' path='docs/doc[@for="GridToolTip.CreateParams"]/*' />
        /// <devdoc>
        ///     The createParams to create the window.
        /// </devdoc>
        /// <internalonly/>
        protected override  CreateParams CreateParams {
            get {
                NativeMethods.INITCOMMONCONTROLSEX icc = new NativeMethods.INITCOMMONCONTROLSEX();
                icc.dwICC = NativeMethods.ICC_TAB_CLASSES;
                SafeNativeMethods.InitCommonControlsEx(icc);
                CreateParams cp = new CreateParams();
                cp.Parent = IntPtr.Zero;
                cp.ClassName = NativeMethods.TOOLTIPS_CLASS;
                cp.Style |= (NativeMethods.TTS_ALWAYSTIP | NativeMethods.TTS_NOPREFIX);
                cp.ExStyle = 0;
                cp.Caption = this.ToolTip;
                return cp;
            }
        }
 
        private NativeMethods.TOOLINFO_T GetTOOLINFO(Control c) {
        
            int index = Array.IndexOf(controls, c);
            
            Debug.Assert(index != -1, "Failed to find control in tooltip array");
            
            if (toolInfos[index] == null){
               toolInfos[index] = new NativeMethods.TOOLINFO_T();
               toolInfos[index].cbSize = Marshal.SizeOf(typeof(NativeMethods.TOOLINFO_T));
               toolInfos[index].uFlags |= NativeMethods.TTF_IDISHWND | NativeMethods.TTF_TRANSPARENT | NativeMethods.TTF_SUBCLASS;
            }
            toolInfos[index].lpszText = this.toolTipText;
            toolInfos[index].hwnd = c.Handle;
            toolInfos[index].uId = c.Handle;
            return toolInfos[index];
        }
 
        /*
        private bool MouseMoved(Message msg){
            bool moved = true;
 
            Point newMove = new Point(NativeMethods.Util.LOWORD(msg.LParam), NativeMethods.Util.HIWORD(msg.LParam));
 
            // check if the mouse has actually moved...
            if (lastMouseMove == newMove){
                  moved = false;
            }
 
            lastMouseMove = newMove;
            return moved;
        }
        */
 
        private void OnControlCreateHandle(object sender, EventArgs e){
            SetupToolTip((Control)sender);
        }
 
        private void OnControlDestroyHandle(object sender, EventArgs e){
            if (IsHandleCreated) {
                UnsafeNativeMethods.SendMessage(new HandleRef(this, Handle), NativeMethods.TTM_DELTOOL, 0, GetTOOLINFO((Control)sender));
            }
        }
        
        protected override void OnHandleCreated(EventArgs e) {
            base.OnHandleCreated(e);
            for (int i = 0; i < controls.Length; i++) {
                  if (controls[i].IsHandleCreated) {
                     SetupToolTip(controls[i]);
                  }
            }
        }
 
        internal void PositionToolTip(Control parent, Rectangle itemRect) {
            if (_positioned && DpiHelper.EnableDpiChangedHighDpiImprovements) {
                return;
            }
 
            Visible = false;
            
            NativeMethods.RECT rect = NativeMethods.RECT.FromXYWH(itemRect.X, itemRect.Y, itemRect.Width, itemRect.Height);
 
            SendMessage(NativeMethods.TTM_ADJUSTRECT, 1, ref rect);
 
            // now offset it back to screen coords
            Point locPoint = parent.PointToScreen(new Point(rect.left, rect.top));
 
            Location = locPoint;   // set the position once so it updates it's size with it's real width.
 
            int overHang = (Location.X + Size.Width) - SystemInformation.VirtualScreen.Width;
            if (overHang > 0) {
                locPoint.X -= overHang;
                Location = locPoint;
            }
 
            // tell the control we've repositioned it.
            Visible = true;
 
            // _positioned flag is reset to false on mouse move before showing ToolTip with new text.
            _positioned = true;
        }
 
        private void SetupToolTip(Control c) {
            if (this.IsHandleCreated) {
               SafeNativeMethods.SetWindowPos(new HandleRef(this, Handle), NativeMethods.HWND_TOPMOST,
                                 0, 0, 0, 0,
                                 NativeMethods.SWP_NOMOVE | NativeMethods.SWP_NOSIZE |
                                 NativeMethods.SWP_NOACTIVATE);
 
               if (0 == (int)UnsafeNativeMethods.SendMessage(new HandleRef(this, Handle), NativeMethods.TTM_ADDTOOL, 0, GetTOOLINFO(c))) {
                     Debug.Fail("TTM_ADDTOOL failed for " + c.GetType().Name);
               }
 
               // Setting the max width has the added benefit of enabling multiline
               // tool tips! subhag 66503)
               //
               UnsafeNativeMethods.SendMessage(new HandleRef(this, Handle), NativeMethods.TTM_SETMAXTIPWIDTH, 0, SystemInformation.MaxWindowTrackSize.Width);
            }
        }
 
        public void Reset(){
            // okay, this resets the tooltip state,
            // which can get broken when we leave the window
            // then reenter.  So we set the tooltip to null,
            // update the text, then it back to what it was, so the tooltip
            // thinks it's back in the regular state again
            //
            string oldText = this.ToolTip;
            this.toolTipText = "";
            for (int i = 0; i < controls.Length; i++) {
               if (0 == (int)UnsafeNativeMethods.SendMessage(new HandleRef(this, Handle), NativeMethods.TTM_UPDATETIPTEXT, 0, GetTOOLINFO(controls[i]))) {
                    //Debug.Fail("TTM_UPDATETIPTEXT failed for " + controls[i].GetType().Name);
               }
            }
            this.toolTipText = oldText;
            this.SendMessage(NativeMethods.TTM_UPDATE, 0, 0);
            this._positioned = false;
        }
 
        protected override void WndProc(ref Message msg) {
            switch (msg.Msg) {
               case NativeMethods.WM_SHOWWINDOW:
                  if (unchecked( (int) (long)msg.WParam) != 0 && dontShow){
                     msg.WParam = IntPtr.Zero;
                  }
                  break;
               case NativeMethods.WM_NCHITTEST:
                  // Fix for VSWhidbey#93985 and VSWhidbey#172903. When using v6 common controls, the native
                  // tooltip does not end up returning HTTRANSPARENT all the time, so its TTF_TRANSPARENT
                  // behavior does not work, ie. mouse events do not fall thru to controls underneath. This
                  // is due to a combination of old app-specific hacks in comctl32, functional changes between
                  // v5 and v6, and the specfic way the property grid drives its tooltip. Workaround is to just
                  // force HTTRANSPARENT all the time.
                  msg.Result = (IntPtr) NativeMethods.HTTRANSPARENT;
                  return;
            }
            base.WndProc(ref msg);
        }
    }
}