You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WinForms自定义ImageToolTip中GIF动画无法播放的解决求助

问题背景

  • 创建了自定义类ImageToolTip,可显示包含图片的WinForms工具提示(Tooltip)
  • 实现了该类在工具条按钮上的程序化调用,功能正常

遇到的问题

无法实现提示框内图片的动画效果。熟悉ImageAnimator组件的使用,但常规的「在帧变更事件中使控件无效并在OnPaint()方法中重绘」的方案无法应用于Tooltip控件;且ToolTip类未提供自身绘制位置的参考,无法临时拼凑解决方案。

已尝试的方案(已在OnDraw事件处理器中加入ImageAnimator.UpdateFrames()调用):

  • 将Active属性设为false后再设为true,重新激活Tooltip,无效
  • 调用Hide()方法后再调用Show()方法,触发跨线程异常:

System.InvalidOperationException: Cross-thread operation not valid: Control 'xyz' accessed from a thread other than the thread it was created on.

代码片段

自定义ImageToolTip类代码

public class ImageToolTip : ToolTip
{
    private const int IMAGE_SIZE = 32;
    private const int MARGIN = 4;
    private const TextFormatFlags TEXT_FORMAT_FLAGS = TextFormatFlags.VerticalCenter
    | TextFormatFlags.Left
    | TextFormatFlags.LeftAndRightPadding
    | TextFormatFlags.NoClipping
    | TextFormatFlags.WordBreak;

    public string ImageFileName { get; set; }

    /// <summary>
    /// NOTES:
    /// 1/. Will be blithely ignored if the owning control is the top tool strip
    /// 2/. We are using the filename, and not the actual image as this also confirms if we need to
    ///     animate it or not
    /// 3/. Code to find the image *will* search all images, not just button related ones; required
    ///     as the image will often be something not normally button related
    /// </summary>
    /// <remarks>
    /// HACK: Rather than shtuff about making our own, derived Popup and Draw handlers (with an
    /// ImageToolTipDetails param), we will stick to a holistic image.
    /// After all, the primary use is for tool strip buttons, which define their own values...
    /// </remarks>
    private Image CurrentImage = null;

    public event EventHandler FrameChanged;

    public ImageToolTip()
    {
        this.OwnerDraw = true;
        this.Popup += new PopupEventHandler(this.OnPopup);
        this.Draw += new DrawToolTipEventHandler(this.OnDraw);
    }

    private void OnPopup(object sender, PopupEventArgs e)
    {
        ImageToolTipDetails deets = DeriveToolTipDetails(e.AssociatedControl);
        if (deets == null)
        {
            // Bug out completely; do not try to show the tool tip.
            e.Cancel = true;
        }
        else
        {
            e.ToolTipSize = DeriveToolTipSize(deets);
        }
    }

    public Size DeriveToolTipSize(ImageToolTipDetails aDetails)
    {
        Size result = TextRenderer.MeasureText(aDetails.Text, SystemFonts.DefaultFont);
        // HACK: With the image/s currently used, we don't seem to need:
        //  1/. Margins above, to the left of the image
        //  2/. A margin between the image, and the text
        result.Height += IMAGE_SIZE + MARGIN;  // For the image above the text.
        // We seem to need to draw the text an extra MARGIN in, hence 3 times.
        result.Width += 3 * MARGIN;

        return result;
    }

    private void OnDraw(object sender, DrawToolTipEventArgs e)
    {
        ImageToolTipDetails deets = DeriveToolTipDetails(e.AssociatedControl);
        if (deets == null)
        {
            // Bug out completely; do not try to show the tool tip.
            // This event may never even get called, as the Popup event cancelled itself, but
            // better safe than sorry...
        }
        else
        {
            e.DrawBackground();
            e.DrawBorder();

            //e.DrawText();
            if (CurrentImage == null)
            {
                CommonFunctions cf = new CommonFunctions();
                CurrentImage = cf.FindButtonImageFilename(deets.ImageFileName, true);
                CurrentControl = e.AssociatedControl;
                if (ImageAnimator.CanAnimate(CurrentImage))
                {
                    ImageAnimator.Animate(CurrentImage, new EventHandler(this.OnFrameChanged));
                }
            }

            e.Graphics.DrawImage(CurrentImage, e.Bounds.Left, e.Bounds.Top, IMAGE_SIZE, IMAGE_SIZE);
            // The example I based this from used SystemBrushes.ActiveCaptionText, but I perceive that
            // using the ForeColor specified for this control is more accurate.
            e.Graphics.DrawString(deets.Text, SystemFonts.DefaultFont, SystemBrushes.FromSystemColor(this.ForeColor),
                e.Bounds.Left + MARGIN, e.Bounds.Top + IMAGE_SIZE);
        }
    }

    private void OnFrameChanged(object o, EventArgs e)
    {
        //Cite: https://learn.microsoft.com/en-us/dotnet/api/system.drawing.imageanimator.animate?view=netframework-4.8.1&viewFallbackFrom=windowsdesktop-10.0
        //Force a call to the Paint event handler.
        //this.Invalidate();
        ImageAnimator.UpdateFrames();
        //this.Active = false;
        //this.Active = true;
        //string text = this.GetToolTip(CurrentControl);
        //this.Hide(CurrentControl);
        //this.Show(text, CurrentControl);
        // Bubble the event up to the parent.
        FrameChanged?.Invoke(this, e);
    }

    /// <summary>
    /// Gets the image and text to use, for the tool tip.
    /// </summary>
    /// <param name="aAssociatedControl"></param>
    /// <returns>Returns null if nothing is to be displayed (as per its BL).</returns>
    private ImageToolTipDetails DeriveToolTipDetails(Control aAssociatedControl)
    {
        ImageToolTipDetails result = null;

        if (aAssociatedControl.GetType() == typeof(MenuStrip))
        {
            // The problem we have is usually the Y co-ord is just beyond the bounds of the tool strip,
            // and we get a null item.  This takes the approx. middle of the strip.
            // PRE: We only ever care about the top strip - hence we use MenuStrip, and not ToolStrip!
            // HACK: We may need to amend co-ords if the overall window is not maximised?
            ToolStripItem item = ((MenuStrip)aAssociatedControl).GetItemAt(Cursor.Position.X, 20);
            if (item != null)
            {
                if (item.Tag != null)
                {
                    result = (ImageToolTipDetails)item.Tag;
                }
                // else do nothing; do NOT show the tooltip at all (let the button's ToolTipText handle it).
            }
            // else do nothing; user is either hovering over blank space or it is not a button (menu item, etc).
        }
        else  // Something else; handle as per normal.
        {
            // The Draw caller has the text value, but the Popup one doesn't; easier to simply get it here, rather
            // than make the callers provide it, as another function param.
            result = new ImageToolTipDetails(ImageFileName, GetToolTip(aAssociatedControl));
        }

        return result;
    }
}

使用该类的窗体代码片段

public frmThing()
{
    InitializeComponent();
    imageToolTip1 = new ImageToolTip();
    imageTollTip1.ImageFileName = "animatedImage.gif";
    // Try direct shows/hides.
    //imageToolTip1.SetToolTip("blah!", btnFoo);
    imageToolTip1.FrameChanged += new System.EventHandler(ToolTipFrameChanged);
}

private void btnFoo_MouseEnter(object sender, EventArgs e)
{
    // I exist to try doing all Shows/Hides explicitly, rather than leverage ShowToolTip().
    imageToolTip1.Show("blah!", btnFoo);
}

private void btnFoo_MouseLeave(object sender, EventArgs e)
{
    // I exist to try doing all Shows/Hides explicitly, rather than leverage ShowToolTip().
    imageToolTip1.Hide();
}

protected void ToolTipFrameChanged(object sender, EventArgs e)
{
    //imageToolTip1.Active = false;
    //imageToolTip1.Active = true;
    imageToolTip1.Hide();
    imageToolTip1.Show("blah!", btnFoo);
}

现寻求可行的解决思路,以实现WinForms自定义Tooltip中GIF图片的动画播放效果。

内容的提问来源于stack exchange,提问作者Chaos Worrier

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.02 04:37:27