I was working on an application today trying to get a transparent form to work and for the life of me what I was trying to do was not working the way I expected.  Basically I was trying create a transparent form and draw a “moving” geometrical shape on the form.

 

My initial approach was to draw a bitmap using the graphics object and then to assign that bitmap to the Form.BackgroundImage property.  I set the transparency key of my form to black and  then created a new bitmap and assigned that to the form background.  I then used the graphics object to fill in the bitmap as black and then drew my shape on the form.  Needless to say as you can see by the title of this entry that things quite didn’t work out as expected.

 

Follow with me how I worked this out:

 

I started with a blank windows form.  In the constructor for the form I added:

 

          this.TransparencyKey = Color.Black;

          this.BackColor = Color.Black;

 

The behavior you get when running the form now should be that the main area of the form is transparent but the form border (including title bar) is still visible. 

 

Now the next step is to create a bitmap on the fly and assign it to the background of the form.  For my sample I’ve simplified my original code by drawing a circle.

  

          this.TransparencyKey = Color.Black;

          this.BackColor = Color.Black;

         

          this.BackgroundImage = new Bitmap(this.Width, this.Height);

          Graphics g = Graphics.FromImage(this.BackgroundImage);

          g.FillEllipse(new SolidBrush(Color.Black), 25, 25, 25, 25);

 

This code does not behave the way you expect (or at least not how I would expect with my limited knowledge of the concepts involved).  What you should see is that there’s a black circle drawn on the form.  I wasn’t too upset by seeing this circle on my form as I figured I could use the Color.Transparent color to draw sections of my bitmap. 

 

My main objective was to be able to change the location of the shape of the form of my background.  So, we’ll simulate this artificially by creating a button to the form and in the click event of the button we’ll create a loop that paints the several of the circles on our bitmap (you can leave the code in the constructor or take it out). 

 

         Graphics g= Graphics.FromImage(this.BackgroundImage);

          for (int i = 0; i < this.Width; i += 30)

                 for (int j = 0; j < this.Height; j += 30)

                 {

                        g.FillEllipse(new SolidBrush(Color.Blue), i, j, 50, 50);

                        this.Refresh();

                        System.Threading.Thread.Sleep(10);

          }

 

Now what you see is that the blue circles cover up most of the form which is what we expect since I never cleaned up previous circles painted on the bitmap.  Here’s where things start getting tricky…I expected if I created a new bitmap before drawing the new location of the circle that this would effectively give me the behavior I desired:  a single circle moving across the form…this did not work.  I subsequently tried painting the bitmap a solid Black (the transparency key for the form…we showed earlier that this wouldn’t work) and Color.Transparent which is the transparent color for the system palette.  As you see below even if I create a new bitmap and fill it the transparent color it still does not work.

 

         Graphics g;

          for (int i = 0; i < this.Width; i += 30)

                 for (int j = 0; j < this.Height; j += 30)

                 {

                        this.BackgroundImage = new Bitmap(this.Width, this.Height);

                        g = Graphics.FromImage(this.BackgroundImage);

                        g.FillRectangle(new SolidBrush(Color.Transparent), 0, 0, this.Width, this.Height);

                        g.FillEllipse(new SolidBrush(Color.Blue), i, j, 50, 50);

                        this.Refresh();

                        System.Threading.Thread.Sleep(10);

          }

 

      Confused as I was I had to verify that I wasn’t crazy and didn’t have some ridiculous bug in my code.  To do this I just printed out a copy of the bitmap for each iteration of the loop.  Add the following line prior to the call to this.Refresh() within the button click event handler:

 

this.BackgroundImage.Save("Background" + i.ToString() + "-" + j.ToString() + ".bmp");

 

To quickly view the images just open up windows explorer and use the thumbnail view for the folder…you should see the progression of the circle through the image. 

 

So, verifying that I wasn’t going crazy I enlisted some help from George Robertson who was kind enough to indulge me with some assistance, thanks George!  George recommended I used Layered Windows.  So, to cut this overlong short story short (what a nice mouthful) I used layered windows and did all of the painting to the form itself and not to the background image of the form.

 

To make the code you have now work, remove the button handler and use all of the following code:

 

 

          [System.Runtime.InteropServices.DllImport("user32.dll")]

          public static extern Int32 GetWindowLong(IntPtr hwnd, int nIndex);

 

          [System.Runtime.InteropServices.DllImport("user32.dll")]

          public static extern Int32 SetWindowLong(IntPtr hwnd, int nIndex, Int32 dwNewLong);

 

          [System.Runtime.InteropServices.DllImport("user32.dll")]

          public static extern bool SetLayeredWindowAttributes(IntPtr hwnd, Int32 crKey, int bAlpha, uint dwFlags);

 

          public const int GWL_EXSTYLE = -20;

          public const int LWA_COLORKEY = 0x00000001;

          public const int WS_EX_LAYERED = 0x00080000;

 

          private bool bPaintCircles;

          private int i;

          private int j;

 

          private void button1_Click(object sender, System.EventArgs e)

          {

                 bPaintCircles = true;

                

                 for (i = 0; i < this.Width; i += 30)

                        for (j = 0; j < this.Height; j += 30)

                        {

                               this.Refresh();

                               System.Threading.Thread.Sleep(10);

                        }     

 

                 IntPtr hWnd = this.Handle;

                 SetWindowLong(hWnd, GWL_EXSTYLE, GetWindowLong(hWnd, GWL_EXSTYLE) | WS_EX_LAYERED);

                 SetLayeredWindowAttributes(hWnd, 0x00000000, 0, LWA_COLORKEY);

                 this.BackColor = Color.Black;

          }

 

          private void Form1_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)

          {

          }

 

          private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)

          {

                 if (bPaintCircles)

                 {

                        // Black is our transparency key as we passed to SetLayeredWindowAttributes

                        e.Graphics.FillRectangle(new SolidBrush(Color.Black), 0, 0, this.Width, this.Height);

                        e.Graphics.FillEllipse(new SolidBrush(Color.Blue), i, j, 50, 50);

                 }

          }

 

 

Lessons Learned:

 

·         There’s this thing called layered windows that you can use to draw transparent forms or forms that support opacity.

·         It appears that the Background image is applied after the mask for the transparency and therefore the background image is not transparent.

·         I don’t know squat about how .NET handles transparency and how all of this is supposed to fit together, the documentation has left me confused.

·         I won’t make this mistake next time,..I’ll use this approach straight away.