views:

120

answers:

3

I'm using the AnimateWindow API to show or hide a Form with a slide animation. The problem is that if the Form contains a RichTextBox control, it doesn't display this control correctly. It's transparent and doesn't show any text.

After the animation is complete, double clicking somewhere in the control will reveal lines of text.

I've created a full sample that anyone can use to compile and test themselves. There's no better way to debug this, unless you already know the answer.

There's 2 buttons in the main form, one to show another form and the other to hide that same form. I've added both the RichTextBox as a simple TextBox. As you'll see, the problem only happens on the RichTextBox.

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

namespace WindowsFormsApplication1 {
    static class Program {
        public const int AW_ACTIVATE = 0x00020000;
        public const int AW_HIDE = 0x00010000;
        public const int AW_HOR_NEGATIVE = 0x00000002;
        public const int AW_HOR_POSITIVE = 0x00000001;
        public const int AW_SLIDE = 0x00040000;

        [DllImport("user32")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool AnimateWindow(IntPtr hWnd, int time, int awFlags);

        [STAThread]
        public static void Main() {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }

    public class Form1 : Form {
        public Form form;

        public Form1() {
            InitializeComponent();
            form = new Form2();
            form.Show();
            form.Hide();
        }

        private void button1_Click(object sender, EventArgs e) {
            form.Location = new Point(Location.X, Location.Y + form.Height + 100);
            Program.AnimateWindow(form.Handle, 1000, Program.AW_SLIDE | Program.AW_HOR_NEGATIVE | Program.AW_ACTIVATE);
            form.Show();
        }

        private void button2_Click(object sender, EventArgs e) {
            Program.AnimateWindow(form.Handle, 1000, Program.AW_HIDE | Program.AW_HOR_POSITIVE);
            form.Hide();
        }

        #region Windows Form Designer generated code

        private void InitializeComponent() {
            this.button1 = new System.Windows.Forms.Button();
            this.button2 = new System.Windows.Forms.Button();
            this.SuspendLayout();
            this.button1.Font = new System.Drawing.Font("Microsoft Sans Serif", 14.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.button1.Location = new System.Drawing.Point(11, 12);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(133, 114);
            this.button1.TabIndex = 0;
            this.button1.Text = "SHOW";
            this.button1.UseVisualStyleBackColor = true;
            this.button1.Click += new System.EventHandler(this.button1_Click);
            this.button2.Font = new System.Drawing.Font("Microsoft Sans Serif", 14.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.button2.Location = new System.Drawing.Point(150, 12);
            this.button2.Name = "button2";
            this.button2.Size = new System.Drawing.Size(133, 114);
            this.button2.TabIndex = 1;
            this.button2.Text = "HIDE";
            this.button2.UseVisualStyleBackColor = true;
            this.button2.Click += new System.EventHandler(this.button2_Click);
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(294, 138);
            this.Controls.Add(this.button2);
            this.Controls.Add(this.button1);
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
            this.MaximizeBox = false;
            this.MinimizeBox = false;
            this.Name = "Form1";
            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
            this.Text = "Form1";
            this.ResumeLayout(false);
        }

        #endregion

        private System.Windows.Forms.Button button1;
        private System.Windows.Forms.Button button2;
    }

    public class Form2 : Form {
        public Form2() {
            InitializeComponent();
        }

        #region Windows Form Designer generated code

        private void InitializeComponent() {
            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form2));
            this.richTextBox1 = new System.Windows.Forms.RichTextBox();
            this.textBox1 = new System.Windows.Forms.TextBox();
            this.SuspendLayout();
            this.richTextBox1.Dock = System.Windows.Forms.DockStyle.Top;
            this.richTextBox1.Location = new System.Drawing.Point(0, 0);
            this.richTextBox1.Name = "richTextBox1";
            this.richTextBox1.Size = new System.Drawing.Size(240, 50);
            this.richTextBox1.TabIndex = 0;
            this.richTextBox1.Text = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.";
            this.textBox1.Dock = System.Windows.Forms.DockStyle.Bottom;
            this.textBox1.Location = new System.Drawing.Point(0, 57);
            this.textBox1.Multiline = true;
            this.textBox1.Name = "textBox1";
            this.textBox1.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
            this.textBox1.Size = new System.Drawing.Size(240, 50);
            this.textBox1.TabIndex = 1;
            this.textBox1.Text = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.";
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(240, 107);
            this.ControlBox = false;
            this.Controls.Add(this.textBox1);
            this.Controls.Add(this.richTextBox1);
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
            this.Name = "Form2";
            this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
            this.Text = "Form2";
            this.ResumeLayout(false);
            this.PerformLayout();
        }

        #endregion

        private System.Windows.Forms.RichTextBox richTextBox1;
        private System.Windows.Forms.TextBox textBox1;
    }
}

NOTE: I'm starting a bounty on this so if you're going to answer, please answer with a solution that works and not something for me to try and see if it works. Why? Because if people upvote your answer and doesn't fix anything , it will still be marked as accepted which it didn't help. Thanks for understanding.

A: 

Does WPF not contain the slide animations you need?

APIs like AnimateWindow are ... not given a lot of attention at Microsoft - usually in favor of a 'better' .NET / WPF method. Using native / not managed APIs to animate windows quickly lands you in a world of hurt :- Microsoft do not support flicker control animations - or alpha effects - in native controls and windows.

The native AnimateWindow tends to fall over if the target window has aero glass. any non-client elements at all. Any child controls. any transparency (window regions, layered windows with alpha or masking).

It really just doesn't work that well at all.


The only way to work around it would be to do what AnimateWindow does. AnimateWindow is not so much an API call, but a convenience wrapper around functionality that can "easilly" be implemented by the user.

All that AnimateWindow does it, well, reposition the animated window on a timer. Depending on the animation it will use clip regions, or add the WS_EX_LAYERED style (to do the alpha blends) for the duration of the animation. This is why AnimateWindow fails so spectacularly when given windows already using those effects.

Performing the animation yourself, you can try to tweak the parameters to do it in a way more compatible with the rich edits painting requirements.

Chris Becke
WPF is out of the question, I don't have time nor the resources to rewrite my whole application at the moment. I can't say I haven't thought about it, but I just don't have the time for it right now. About the Aero problems you see in the sample, that problem doesn't exist on my real application because I'm not exactly using Aero. My application is skinned and the only issue is with `RichTextBox`, all other native controls work just fine...
Nazgulled
The problem seems to be that AnimateWindow snapshots the contents of the window - which it then animates in. But it seems to require that the window handle WM_ERASEBKGND. child controls that don't paint their background on WM_ERASEBKGND seem to not be painted correctly by AnimateWindow. I would hazard that RichEdit is one of those.
Chris Becke
Any way to solve it or workaround it then?
Nazgulled
A: 

Please see this code... this is the most bizarre bug I've ever seen... the only difference is that I had to trap the VisibleChanged Event for the RichTextBox, and to set the Capture property, update it, then switch off the Capture, my thinking there was to send a message to the control to force a double-click event but discovered that is not required... I also had to override the OnActivated event for the form2 itself in order to get the event trapped AFTER the animation is done and to force a refresh...strange bizarre bug....

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Text;

namespace WindowsFormsApplication1
{
    static class Program
    {
        public const int AW_ACTIVATE = 0x00020000;
        public const int AW_HIDE = 0x00010000;
        public const int AW_HOR_NEGATIVE = 0x00000002;
        public const int AW_HOR_POSITIVE = 0x00000001;
        public const int AW_SLIDE = 0x00040000;

        [DllImport("user32")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool AnimateWindow(IntPtr hWnd, int time, int awFlags);

        [STAThread]
        public static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }

    public class Form1 : Form
    {
        public Form form;

        public Form1()
        {
            InitializeComponent();
            form = new Form2();
            form.Show();
            form.Hide();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            form.Location = new Point(Location.X, Location.Y + form.Height + 100);
            Program.AnimateWindow(form.Handle, 1000, Program.AW_SLIDE | Program.AW_HOR_NEGATIVE | Program.AW_ACTIVATE);
            form.Show();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            Program.AnimateWindow(form.Handle, 1000, Program.AW_HIDE | Program.AW_HOR_POSITIVE);
            form.Hide();
        }

        #region Windows Form Designer generated code

        private void InitializeComponent()
        {
            this.button1 = new System.Windows.Forms.Button();
            this.button2 = new System.Windows.Forms.Button();
            this.SuspendLayout();
            this.button1.Font = new System.Drawing.Font("Microsoft Sans Serif", 14.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.button1.Location = new System.Drawing.Point(11, 12);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(133, 114);
            this.button1.TabIndex = 0;
            this.button1.Text = "SHOW";
            this.button1.UseVisualStyleBackColor = true;
            this.button1.Click += new System.EventHandler(this.button1_Click);
            this.button2.Font = new System.Drawing.Font("Microsoft Sans Serif", 14.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.button2.Location = new System.Drawing.Point(150, 12);
            this.button2.Name = "button2";
            this.button2.Size = new System.Drawing.Size(133, 114);
            this.button2.TabIndex = 1;
            this.button2.Text = "HIDE";
            this.button2.UseVisualStyleBackColor = true;
            this.button2.Click += new System.EventHandler(this.button2_Click);
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(294, 138);
            this.Controls.Add(this.button2);
            this.Controls.Add(this.button1);
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
            this.MaximizeBox = false;
            this.MinimizeBox = false;
            this.Name = "Form1";
            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
            this.Text = "Form1";
            this.ResumeLayout(false);
        }

        #endregion

        private System.Windows.Forms.Button button1;
        private System.Windows.Forms.Button button2;
    }

    public class Form2 : Form
    {
        public Form2()
        {
            InitializeComponent();
            this.richTextBox1.VisibleChanged += new EventHandler(richTextBox1_VisibleChanged);
        }

        void richTextBox1_VisibleChanged(object sender, EventArgs e)
        {
            if (this.richTextBox1.Visible)
            {
                System.Diagnostics.Debug.WriteLine("Visible!");
                this.richTextBox1.Capture = true;
                this.richTextBox1.Update();
                this.richTextBox1.Capture = false;
                this.richTextBox1.Refresh();
            }
            else System.Diagnostics.Debug.WriteLine("InVisible!");
        }

        #region Windows Form Designer generated code

        private void InitializeComponent()
        {
            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form2));
            this.richTextBox1 = new System.Windows.Forms.RichTextBox();
            this.textBox1 = new System.Windows.Forms.TextBox();
            this.SuspendLayout();
            this.richTextBox1.Dock = System.Windows.Forms.DockStyle.Top;
            this.richTextBox1.Location = new System.Drawing.Point(0, 0);
            this.richTextBox1.Name = "richTextBox1";
            this.richTextBox1.Size = new System.Drawing.Size(240, 50);
            this.richTextBox1.TabIndex = 0;
            this.richTextBox1.Text = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.";
            this.textBox1.Dock = System.Windows.Forms.DockStyle.Bottom;
            this.textBox1.Location = new System.Drawing.Point(0, 57);
            this.textBox1.Multiline = true;
            this.textBox1.Name = "textBox1";
            this.textBox1.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
            this.textBox1.Size = new System.Drawing.Size(240, 50);
            this.textBox1.TabIndex = 1;
            this.textBox1.Text = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.";
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(240, 107);
            this.ControlBox = false;
            this.Controls.Add(this.textBox1);
            this.Controls.Add(this.richTextBox1);
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
            this.Name = "Form2";
            this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
            this.Text = "Form2";
            this.ResumeLayout(false);
            this.PerformLayout();
        }

        #endregion

        protected override void OnActivated(EventArgs e)
        {
            base.OnActivated(e);
            System.Diagnostics.Debug.WriteLine("OnActivated");
            this.Refresh();
        }

        private System.Windows.Forms.RichTextBox richTextBox1;
        private System.Windows.Forms.TextBox textBox1;
    }
}

The code works nonetheless - a very obscure thing - the funny thing is within the VisibleChanged handler, I was expecting the debug output to say "InVisible" when clicking on the 'Hide' button but it did not. I did try using WM_PRINT and WM_PRINTCLIENT message which failed with the rich-text-box, just don't ask me why setting the capture on the rich-text-box itself and forcing a refresh solved it... Here's the quote from MSDN on this:

The window procedures for the window and its child windows should handle any WM_PRINT or WM_PRINTCLIENT messages. Dialog boxes, controls, and common controls already handle WM_PRINTCLIENT. The default window procedure already handles WM_PRINT. If a child window is displayed partially clipped, when it is animated it will have holes where it is clipped.

I do not know if it's worth the trouble in pointing this out to Microsoft and say "There's a bug either in the AnimateWindow or in the RichTextBox Control with .NET", either way, it is difficult to ascertain, since after the animation is done, the text-box appears ok, but not the rich-text-box, yet explicitly using the Capture and the Update when the capture is on, forced a refresh... it swings both ways on the bug itself - could be in both spots... Very unusual and bizarre anyway.. hope the code is of use to you.

tommieb75
Was the text visible during the animation for you with your code? It wasn't for me and that's what I want "fixed".I can just call refresh() after the animation is complete just like liggett78 said to display the RichTextBox properly, but that's not enough unfortunately.Also, your code in my machine, seems to do the exact same thing with or without the OnActivated override.
Nazgulled
@Nazgulled: Ahhhh Gotcha.... the text in the rtf box is blanking during the animation... understood now the problem... even with your original code, it showed up blank when showing it *after* the animation - BTW - this is on XP w SP3 Home Edition - mea culpa!!!
tommieb75
actually, on closer inspection, I subclassed the richtext control and discovered that two messages are being used WM_PRINT and WM_PRINTCLIENT (0x317/0x318 respectively) which is what I suspect is causing the blanking during the animation when clicking on 'Hide', furthermore, after reading this link http://weblogs.asp.net/justin_rogers/archive/2004/07/28/200213.aspx I am wondering is the double buffering being used on the rtf, and not on the plain text box? For 'Show' there's a load of message's that are not found anywhere... still hunting them down..
tommieb75
It would be great if you could find a fix for me cause it's way over my capabilities... :(
Nazgulled
A: 

The RichTextBox does not process WM_PRINT messages, which is I believe what AnimateWindow uses behind the scenes, so I don't see any way of doing the animation with the text visible in your rich text box (in case you want richtext box to display text during the animation).

When the animation is complete you can call richTextBox1.Refresh (in your button1_Click via a helper method in the Form2 class) for the box to repaint - the control with the text will be shown alright without any double-clicking.

liggett78
I do want the text to be visible during the animation, that's actually the point on this question. As you pointed out, it's easy to fix the RichTextBox when the animation is over. But that kinda sucks :(
Nazgulled
Why don't you use fading instead of sliding as animation method? With fading nobody will even notice that there is a painting problem with richtext box.
liggett78
Because first, that is not a solution to my problem lol. Second because I'm already using it but I have both implemented and the user selects the one it wants, slide animation has always been the default though and it's the one I prefer when the form is snapped to the side (which is all the time for me when I use the app).
Nazgulled