When people want to use a 32-bit image as the background for a form, they will likely discover the
WS_EX_LAYERED window style, which allows you to do just that. The problem that most people will have is that if they try to simply create their window with the layered style, they will find that they are very limited to what they can do. You cannot for example make it a child window, or place any controls on it.
This little library allows you to use that same 32-bit image as your background without having to make any compromises so you can do everything you were always able to do, plus have more professional looking UIs for custom windows and widgets.
I originally wrote this article almost three years before this update, and at the time I knew that it wasn't the best solution but it was one of the very few that would work. The idea was and still is to create an additional window with the
WS_EX_LAYERED style and use that window to show the alpha blended background image. We then just need to make sure that this window is positioned behind the main form.
How Do You Make Such A Thing
You might think that positioning two windows on top of each other would be a simple task, and both times I came to look at this problem I had the same thoughts. There are however a few problems that arise, and the solutions to these problems inevitably create more problems of their own. So that you have a better understanding of how this works, and why I've done it the way I have, I'll go through the problems that I encountered and their solutions. You never know you might find out something new!
Positioning the Window
If we are going to use two windows to achieve the effect of a blended background with user controls on top of it, then the first thing that needs to be done is to position the two windows in the same place. Your first thought would probably to register for the
LocationChanged event and set the background window to the same position. The problem is that one window will always be slightly ahead of the other, which may cause some flickering which won't make your hard work look particularly fantastic and it may put some users off. This difference if position between the two windows was one of the biggest problems I had in the first version of this library, which I solved by drawing all of the windows controls to the background image by calling
DrawToBitmap, hiding the main window and then only moving the background. It worked very well except for the large amount of time it takes to do all of that drawing which caused the movement of the window to be a little jumpy.
Fig 1. The two forms don't line up while the user is dragging them
The solution I have this time comes in two parts, the first is a better way to try and ensure that both windows move at the same time and the second is to again draw the window to the background form.
For both of the windows to move together as one (or as close to as possible) you need to listen out for the
WM_WINDOWPOSCHANGING message which is sent just before a form is actually moved or resized, we can then use the information that comes with this message to use
DeferWindowPos which will prevent drawing of one or more windows until the entire set of windows has been moved. Since we are sending out new messages to move the window, it is necessary to cancel the movement that would have been caused by the initial
WM_WINDOWPOSCHANGING message which can be done by appending the
WndProc(ref Message m)
Win32.WINDOWPOS posInfo = (Win32.WINDOWPOS)Marshal.PtrToStructure
Win32.WindowPosFlags move_size = Win32.WindowPosFlags.SWP_NOMOVE |
if ((posInfo.flags & move_size) != move_size)
if (posInfo.hwndInsertAfter != this.Handle)
IntPtr hwdp = Win32.BeginDeferWindowPos(2);
if (hwdp != IntPtr.Zero)
hwdp = Win32.DeferWindowPos(hwdp, m_layeredWnd.Handle,
this.Handle, posInfo.x, posInfo.y, 0, 0,
(uint)(posInfo.flags | Win32.WindowPosFlags.SWP_NOSIZE |
if (hwdp != IntPtr.Zero)
hwdp = Win32.DeferWindowPos(hwdp, this.Handle,
this.Handle, posInfo.x, posInfo.y, posInfo.cx,
(posInfo.flags | Win32.WindowPosFlags.SWP_NOZORDER));
if (hwdp != IntPtr.Zero)
posInfo.flags |= Win32.WindowPosFlags.SWP_NOMOVE;
Marshal.StructureToPtr(posInfo, m.LParam, true);
In most situations, this should be enough to provide clean movement of both the windows, but for some users it is still not enough which is why you have the option to draw the entire window to the background when the window is moved. To make this drawing as fast as possible, instead of calling
DrawToBitmap for each control, we can just
BitBlt the window to the background by getting hold of its Device Context which Windows uses to put your fully drawn window on the screen. To copy the window to the background, we also need to create a mask so that we only copy over the areas of the window that aren't transparent.
Creating the mask is simple, all we need do is draw the window onto a monochrome bitmap and any pixels on the window that are the same colour as the background colour will be drawn white, and everything else will be black. We can then use this mask with the
MaskBlt function to draw the window onto the background. Using only two
BitBlts, the whole operation only takes a fraction of the time that
DrawToBitmap would have, which means that unless you have a particularly large background image or window, there should be no visible lag when starting to move the window.
IntPtr windowDC = Win32.GetWindowDC(this.Handle);
IntPtr memDC = Win32.CreateCompatibleDC(windowDC);
IntPtr BmpMask = Win32.CreateBitmap(this.ClientSize.Width,
this.ClientSize.Height, 1, 1, IntPtr.Zero);
IntPtr BmpBack = backImage.GetHbitmap(Color.FromArgb(0));
uint oldCol = Win32.SetBkColor(windowDC, 0X00FF00FF);
Win32.BitBlt(memDC, 0, 0, this.ClientSize.Width,
this.ClientSize.Height, windowDC, 0, 0, SRCCOPY);
IntPtr brush = Win32.CreateSolidBrush(0x00FFFFFF);
Win32.MaskBlt(memDC, 0, 0, backImage.Width,
backImage.Height, windowDC, 0, 0, BmpMask, 0, 0, 0xCFAA0020);
Getting Mouse Input
Our main window will have a transparent background, and as such it will not be able to receive mouse input. The window will not receive any messages for a transparent area of the background, unless you call the Windows function
GetCapture(HWND hWnd) but that generates more problems than it solves as it causes problems with regular Windows messages to the desktop and other windows, because your application is capturing all of the mouse input. Your solution may then be to get the mouse input from the background window which should catch any mouse events, so you would register for the background window's mouse events instead.
There are of course, problems. When you click on the background window, it will become the active window and be drawn above your controls. You can try to
catch this and restore focus to the main window but it still causes flickering, which is exceedingly unpleasant. There is a window style that you can apply that will stop a window from gaining focus which works to some extent. The background window will not gain focus and be brought forwards but your main window will still lose its focus.
My solution then is to fully disable the background window, any action on this window is then completely ignored and focus remains on our main window. The downside is that no events will ever be fired so we need to look at the window's messages. The downside again is that a disabled window only ever receives one message
WM_SETCURSOR which Windows uses to allow applications to change the current cursor. Thankfully this message comes with some additional information which is the hit test code and the current action of the mouse. The mouse action is given in the form of the standard windows messages
WM_MOUSEMOVE, WM_LBUTTONDOWN, etc. To use these messages, I subclass the background window so that I can intercept its messages and check for
WM_SETCURSOR and then fire off the appropriate events.
m_customLayeredWindowProc = new Win32.Win32WndProc(this.LayeredWindowWndProc);
m_layeredWindowProc = Win32.SetWindowLong
(m_layeredWnd.Handle, GWL_WNDPROC, m_customLayeredWindowProc);
private int LayeredWindowWndProc(IntPtr hWnd, int Msg, int wParam, int lParam)
return Win32.CallWindowProc(m_layeredWindowProc, hWnd, Msg, wParam, lParam);
Handling Semi-Transparent Windows
Because we have two separate windows if the opacity is less than 1.0, our main windows controls would be blended with the background window and then to the desktop, rather than straight to the desktop. This one has a fairly simple solution, because we know that the windows will be moving together we can cut out sections of the background image where each of the main windows controls will be, and then draw these sections to the background of our main window. The caveat is that if you place a control over an area of the background image that is not fully opaque, then the colours will be blended with the background colour of the window (which is this case will likely be Fuchsia). This is an optional feature, and can be enabled as per your requirements.
Fig 2. Top: Form without Control backgrounds drawn. Bottom: Form with Control backgrounds.
See how in the top image, your Controls are blended with the background window, and also notice that the text has been drawn onto the windows Fuchsia back colour giving it that pink outline. In the bottom image, we draw the background behind each control so the text is drawn over the actual background image removing the outline, and the button blends straight through to the desktop because we've cut out that section from the background image itself.
So with the aforementioned solutions, the final setup looks something like this:
class AlphaForm : Form
private LayeredWindow m_layeredWnd;
private Bitmap m_backgroundImage;
override OnLoad(EventArgs e)
foreach (Control ctrl in this.Controls)
Rectangle rect = ctrl.ClientRectangle;
e.Graphics.DrawImage(m_backgroundImage, rect, rect, GraphicsUnit.Pixel);
private int LayeredWindowWndProc(IntPtr hWnd, int Msg, int wParam, int lParam)
if(message is WM_SETCURSOR)
Point mousePos = System.Windows.Forms.Cursor.Position;
MouseEvent = lParam >> 16;
MouseEventArgs e = new MouseEventArgs(...);
override WndProc(ref Message m)
DeferWindowPos( main window );
DeferWindowPos( background );
if(left mouse button is down)
(int)Win32.Message.WM_NCLBUTTONDOWN, (int)Win32.Message.HTCAPTION, 0);
class LayeredWindow : Form
void UpdateWindow(Bitmap image, byte opacity, int width = -1, int height = -1)
That's it in a nutshell, there's more code around to make it more usable but that's all you really need to make this work. The code is well commented so it might be worth your time to read through it if you want to see how everything works together in detail.
Using the Code
In the previous version, you had to pass your main form to my custom alpha form, and then show my alpha form. It was a strange thing to have to do, so in this version I've made sure to make it even simpler. You can setup your form to use background images with an alpha channel by typing out 5 characters.
public class Form1 : Form
public class Form1 : AlphaForm
AlphaForm will allow you to use 32bit images for your background. You will still be in charge of setting your border style to none and everything should remain exactly the same as with a standard Windows Form, including editing it in the designer (something that was missing in the previous version). Once you have chosen a background image, it will be rendered to the form in the designer so that you can position your controls, but when you run your application, the image will only be on the background window.
Methods and Properties
There are a few properties and methods that have been added to give a little more control over the background form and I'll list them here followed by an example of their usage.
|The image that is to be used as the forms background|
true when the form is dragged, the foreground window will be drawn to the background window and then hidden. This prevents any visual disparity between the two forms.
|If true, a portion of the background image will be drawn behind each control on the form.|
|Changes the way that the form will behave when it is resized, available options are:
None: The background image will always remain its original size
Stretch: The background will be resized to fit the client area of the main form
Clip: The background image will be clipped to within the client area of the main form
void DrawControlBackground(Control ctrl, bool drawBack)
|When the |
RenderControlBackground option is set, you can use this method to control which of your Controls will have a background drawn for it. By default, all Controls are set to
|Can be used to force an update of the forms background when you have added, removed or moved some of your Controls|
void SetOpacity(double opacity)
|Should be used to set the opacity of your form instead of the |
Opacity property (Otherwise you must call
The defaults for each of the properties is
false, with the
SizeMode being set to
All of the attributes are available to be set in the designer under the
AlphaForm category but here is a quick example of setting up a form with all of the features. Take note that because in the example
picBox is excluded from having a background drawn, it would not blend properly with the rest of the form, similar to the top image in Figure 2.
public partial class Form1 : AlphaForm
private void Form1_Load(object sender, EventArgs e)
this.BlendedBackground = new Bitmap(@"C:\myImage.png");
this.SizeMode = SizeModes.Clip;
this.DrawControlBackrounds = true;
this.EnhancedRendering = true;
So with this update, I hope to resolve any of the strange issues that people may have experienced, such as not being able to move the window or trying to remove the window from the taskbar, as well as improve usability by making it easier to use. Any forms using this should behave exactly the same as a standard form, and the integration between the layered background window and your own should be seamless. That's what I hope anyway.
- Demo and source update to fix a couple of bugs
- Article rewrite
- Code updated to new version
- Updated source code and demo
- Added VB startup code
- Added screenshots
- Edited article text
- Updated demo.zip to contain source code
- Added information about the settings