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
相关产品推荐
相关产品推荐

