Эмулируйте функциональность всплывающей подсказки с помощью ToolStripDropDown

Я создал пользовательский элемент управления UserControl, который отображается в ToolStripDropDown, чтобы эмулировать функциональность всплывающей подсказки. В основном это отлично работает для любого элемента управления, когда я подписываюсь на их события MouseEnter и MouseLeave.

Я также хочу использовать его для пользовательских объектов (не элементов управления). Я создал интерфейс, который определяет события MouseEnter и MouseLeave, так что я могу подписать любой объект (например, нарисованные пользователем примитивы) на эту всплывающую подсказку. Эти классы выполняют свою собственную работу, чтобы определить, когда запускать MouseEnter и MouseLeave.

Моя проблема заключается в том, что когда отображается всплывающая подсказка, мои пользовательские элементы управления, которые содержат пользовательские объекты, не получают события MouseMove, даже если всплывающая подсказка отображается сбоку, а не под мышью. Я генерирую свое собственное событие MouseLeave на основе проверки MouseMove, если мышь больше не находится над рассматриваемым объектом. Но очевидно, что без событий MouseMove MouseLeave никогда не срабатывает.

Когда я показываю всплывающую подсказку в элементе управления, происходит то же самое (без событий MouseMove), за исключением того, что MouseLeave все еще срабатывает.

1) Как я могу эмулировать эту функциональность MouseLeave? Должен ли я использовать p/invoke для движения мыши SetCapture, или кто-нибудь знает более простой способ?

2) Когда отображается всплывающая подсказка, даже несмотря на то, что ни ToolStripDropDown, ни мой UserControl внутри него не запускают событие «GotFocus», я все равно теряю фокус клавиатуры, пока отображается всплывающая подсказка, что также нежелательно. поведение всплывающей подсказки. Могу ли я этого избежать?

В основном я хочу, чтобы это была полностью не фокусируемая, не мешающая всплывающая подсказка. Я просмотрел пример проекта под названием SuperTooltip, но он имел ту же несовершенную функциональность. Я попытался установить для ControlStyles.Selectable значение false и не заметил никаких изменений.

Вот код, в котором я создаю свою всплывающую подсказку UserControl:

public CustomTooltip()
{
    this.SetStyle(ControlStyles.Selectable, false);

    dropDown = new ToolStripDropDown();
    dropDown.AutoSize = false;
    dropDown.Margin = Padding.Empty;
    dropDown.Padding = Padding.Empty;

    host = new ToolStripControlHost(this);
    host.AutoSize = false;
    host.Margin = Padding.Empty;
    host.Padding = Padding.Empty;

    this.Location = new Point(0, 0);
    dropDown.Items.Add(host);
}

И я показываю это с помощью:

dropDown.Show(
    new Point(Cursor.Position.X, Cursor.Position.Y + Cursor.Current.Size.Height),
    ToolStripDropDownDirection.BelowRight
);

person Trevor Elliott    schedule 02.03.2012    source источник


Ответы (1)


Я обнаружил, что смог добиться этого, наследуя от Form и вообще не используя ToolStripDropDown. Этот класс эмулирует функциональность всплывающей подсказки и позволяет настраивать параметры постепенного появления/исчезновения. Вы можете подписаться на элемент управления или любой класс, который определяет и реализует ITooltipTarget для MouseEnter и MouseLeave.

public abstract class CustomTooltip : Form
{
    #region Static
    protected static readonly int FadeInterval = 25;
    protected static readonly IntPtr HWND_TOPMOST = (IntPtr)(-1);
    private const int SWP_NOSIZE = 0x0001;
    private const int SWP_NOMOVE = 0x0002;
    private const int SWP_NOACTIVATE = 0x0010;
    private const int WS_POPUP = unchecked((int)0x80000000);
    private const int WS_EX_TOPMOST = 0x00000008;
    private const int WS_EX_NOACTIVATE = 0x08000000;

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
    #endregion

    protected Dictionary<object, object> subscriptions;
    protected Timer popupTimer;
    protected Timer fadeTimer;
    protected bool isFading = false;
    protected int fadeDirection = 1;

    [DefaultValue(500)]
    /// <summary>
    /// Delay in milliseconds before the tooltip is shown.  0 means no delay.
    /// </summary>
    public int PopupDelay
    {
        get
        {
            return _popupDelay;
        }
        set
        {
            _popupDelay = value;

            if (value > 0)
                popupTimer.Interval = value;
            else
                popupTimer.Interval = 1;
        }
    }
    private int _popupDelay = 500;

    [DefaultValue(0)]
    /// <summary>
    /// How long to spend fading in and out in milliseconds.  0 means no fade.
    /// </summary>
    public int FadeTime
    {
        get
        {
            return _fadeTime;
        }
        set
        {
            _fadeTime = value;
        }
    }
    private int _fadeTime = 0;

    public virtual new object Tag
    {
        get
        {
            return base.Tag;
        }
        set
        {
            base.Tag = value;

            OnTagChanged(EventArgs.Empty);
        }
    }

    public CustomTooltip()
    {
        this.SetStyle(ControlStyles.Selectable, false);

        subscriptions = new Dictionary<object, object>();

        popupTimer = new Timer();
        popupTimer.Interval = PopupDelay;
        popupTimer.Tick += new EventHandler(popupTimer_Tick);

        fadeTimer = new Timer();
        fadeTimer.Interval = FadeInterval;
        fadeTimer.Tick += new EventHandler(fadeTimer_Tick);

        this.Visible = false;
        this.ShowInTaskbar = false;
        this.FormBorderStyle = FormBorderStyle.None;
        this.ControlBox = false;
        this.StartPosition = FormStartPosition.Manual;
    }

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams cp = base.CreateParams;

            cp.Style |= WS_POPUP;
            cp.ExStyle |= WS_EX_TOPMOST | WS_EX_NOACTIVATE;

            return cp;
        }
    }

    protected override bool ShowWithoutActivation
    {
        get
        {
            return true;
        }
    }

    protected virtual void Subscribe(Control control, object tag)
    {
        subscriptions.Add(control, tag);
        control.MouseEnter += new EventHandler(Item_MouseEnter);
        control.MouseLeave += new EventHandler(Item_MouseLeave);
    }

    protected virtual void Subscribe(ITooltipTarget item, object tag)
    {
        subscriptions.Add(item, tag);
        item.MouseEnter += new EventHandler(Item_MouseEnter);
        item.MouseLeave += new EventHandler(Item_MouseLeave);
    }

    public virtual void Unsubscribe(Control control)
    {
        control.MouseEnter -= new EventHandler(Item_MouseEnter);
        control.MouseLeave -= new EventHandler(Item_MouseLeave);
        subscriptions.Remove(control);
    }

    public virtual void Unsubcribe(ITooltipTarget item)
    {
        item.MouseEnter -= new EventHandler(Item_MouseEnter);
        item.MouseLeave -= new EventHandler(Item_MouseLeave);
        subscriptions.Remove(item);
    }

    public void ClearSubscriptions()
    {
        foreach (object o in subscriptions.Keys)
        {
            if (o is Control)
                Unsubscribe((Control)o);
            else if (o is ITooltipTarget)
                Unsubscribe((ITooltipTarget)o);
        }
    }

    protected virtual void OnTagChanged(EventArgs e)
    {
    }

    protected override void OnSizeChanged(EventArgs e)
    {
        base.OnSizeChanged(e);
    }

    protected override void OnMouseEnter(EventArgs e)
    {
        base.OnMouseEnter(e);

        Item_MouseLeave(null, EventArgs.Empty);
    }

    private void Item_MouseEnter(object sender, EventArgs e)
    {
        Tag = subscriptions[sender];
        popupTimer.Start();
    }

    private void Item_MouseLeave(object sender, EventArgs e)
    {
        if (FadeTime > 0)
            FadeOut();
        else
            this.Hide();

        popupTimer.Stop();
    }

    protected virtual void FadeIn()
    {
        isFading = true;
        Opacity = 0;
        fadeDirection = 1;
        fadeTimer.Start();
    }

    protected virtual void FadeOut()
    {
        isFading = true;
        Opacity = 1;
        fadeDirection = -1;
        fadeTimer.Start();
    }

    private void popupTimer_Tick(object sender, EventArgs e)
    {
        if (isFading)
            this.Hide();

        if (FadeTime > 0)
            FadeIn();

        Location = new Point(Cursor.Position.X, Cursor.Position.Y + Cursor.Size.Height);
        SetWindowPos(Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
        Show();

        popupTimer.Stop();
    }

    private void fadeTimer_Tick(object sender, EventArgs e)
    {
        if (Opacity == 0 && fadeDirection == -1)
        {
            isFading = false;
            fadeTimer.Stop();
            this.Hide();
        }
        else if (Opacity == 1 && fadeDirection == 1)
        {
            fadeTimer.Stop();
            isFading = false;
        }
        else
        {
            double change = ((double)fadeTimer.Interval / (double)FadeTime) * (double)fadeDirection;
            Opacity += change;
        }
    }
}

public interface ITooltipTarget
{
    event EventHandler MouseEnter;
    event EventHandler MouseLeave;
}

Чтобы использовать вышеуказанные классы, вам просто нужно наследовать от CustomTooltip, чтобы создать собственную нарисованную всплывающую подсказку. Производный класс будет использовать свойство Tag для определения отображаемого содержимого. Например, если мне нужна всплывающая подсказка, которая связывает изображение с объектом и рисует это изображение, я бы сделал что-то вроде:

public class CustomImageTooltip : CustomTooltip
{
     public Image Image
     {
        get
        {
            if (Tag is Image)
                return Tag as Image;
            else
                return null;
        }
     }

    public CustomImageTooltip()
    {
        InitializeComponent();

        this.SetStyle(ControlStyles.DoubleBuffer |
                      ControlStyles.AllPaintingInWmPaint |
                      ControlStyles.UserPaint, true);
    }

    public void Subscribe(Control control, Image image)
    {
        base.Subscribe(control, image);
    }

    public void Subscribe(ITooltipTarget item, Image image)
    {
        base.Subscribe(item, image);
    }

    protected override void OnTagChanged(EventArgs e)
    {
        base.OnTagChanged(e);
        if (Image != null)
            this.Size = Image.Size;
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        Graphics g = e.Graphics;

        g.Clear(Color.White);

        if (Image != null)
            g.DrawImage(
                Image,
                new RectangleF(0, 0, ClientSize.Width, ClientSize.Height),
                new RectangleF(0, 0, Image.Size.Width, Image.Size.Height),
                GraphicsUnit.Pixel
            );

        g.DrawRectangle(Pens.Black, 0, 0, ClientRectangle.Width - 1, ClientRectangle.Height - 1);
    }
}

И чтобы использовать этот класс CustomImageTooltip в своем приложении, вам нужно просто подписаться и отказаться от подписки на один экземпляр класса:

// Constructor
customImageTooltip = new CustomImageTooltip();

foreach (CustomObject o in myCustomObjects)
{
    customImageTooltip.Subscribe(o, o.Image);
}

// Destructor
foreach (CustomObject o in myCustomObjects)
{
    customImageTooltip.Unsubscribe(o);
}
person Trevor Elliott    schedule 02.03.2012