Follow-up: Toast Windows and AnimateWindow, a user found issue.

You can check out the previous entry here Watch out for "Power Toys", sometimes you get more than you bargained for. However, I'm more interested in examining why the failure occurs when adding a label. This is an issue that one of the users found when playing with the Toaster. Since I had to do some examination to find the underlying issue I can walk you through the process. What are we given?

  • Adding a Button doesn't cause the failure adding a Label does. That means I can look at differences in the code.
  • He gives me a stack trace, which I'm also able to reproduce so I can use that as well.
  • The top of the stack is in the DibGraphicsBufferManager, one of the most infamous pieces of work to date.

I added that last one there, the infamous part, because many users have trouble with double buffering under Windows Forms when doing animations.

Well, it does appear Windows Forms isn't AnimateWindow friendly. A rogue series of WM_PAINT messages are being processed by the label and then the allocation of a graphics buffer for double buffering is failing. Why? Well, I didn't go far enough to find out exactly why, but there are two options. Either the window has already changed size by the time the message is processed which is the most likely, or AnimateWindow doesn't like the creation of buffers while it is running. I think the issue would lie in the first option because it doesn't appear any work is done to prevent a double buffered control from allocating a buffer based on an old bounding region. There isn't any error control around any of the buffer allocations failing at all.

There are some more interesting notes that I'll point out:

  • It doesn't fail unless the label is large ;-) This is because the code-path is exercised for temporary buffers.
  • Docking the Label generally builds up the appropriate size for it to fail.
  • It only happens on double buffered controls. Any other controls will not fail in this manner.

Could the buffer manager be fixed to be resilient to this failure? Yes, it wouldn't be that hard. It would be nice if you didn't have to animate a window using a timer. The problem with a timer and animating a window is that it isn't nearly as fast as using AnimateWindow and doesn't have any of the built in drawing enhancements. The easiest fix is to add a ThreadExceptionEventHandler around your animation block when toasting. This is the easiest fix for the problem, so I'll throw it out and hopefully unblock some people that might have wanted to use the toasting code but couldn't.

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Threading;

public class AnimateWindowShow : Form {
    [DllImport("user32.dll")]
    static extern bool AnimateWindow(IntPtr hwnd, uint dwTime, uint dwFlags);
   
    public AnimateWindowShow() {
        Label lbl = new Label();
        lbl.Dock = DockStyle.Fill;
        this.Controls.Add(lbl);
   
        ShowInTaskbar = false;
        lbl.Click += new EventHandler(Form_Click);
        Click += new EventHandler(Form_Click);
    }
   
    private void Form_Click(object sender, EventArgs e) {
        GraphicsPath gp = new GraphicsPath(); gp.AddEllipse(ClientRectangle);
        this.Region = new Region(gp);

        Application.ThreadException += new ThreadExceptionEventHandler(this.Handle_Exceptions);       
        AnimateWindow(this.Handle, (uint) 1000, (uint) 0x90000);
       
        DateTime end = DateTime.Now.AddSeconds(3);
        while(end > DateTime.Now) { System.Threading.Thread.Sleep(0); }

        AnimateWindow(this.Handle, (uint) 1000, (uint) 0x80000);
        Application.ThreadException -= new ThreadExceptionEventHandler(this.Handle_Exceptions);       
    }
   
    private void Handle_Exceptions(object sender, ThreadExceptionEventArgs e) {
    }
   
    [STAThread]
    private static void Main(string[] args) {
        Application.Run(new AnimateWindowShow());
    }
}

Enjoy, and play with the values for AnimateWindow styles. You can achieve a number of effects. Remember that the event happens asynchronously so if you are hoping to animate the hiding of a window then you have to wait until the animation is done before you dispose of it. I'll show you how to do this another time.

Published Wednesday, July 28, 2004 2:57 PM by Justin Rogers

Comments

No Comments