Introduction
Everyone has seen the Opacity property in the Visual Studio property inspector when a form is selected. But few people realize exactly how that works behind the scenes, or even how poorly it is implemented. Windows 2K Microsoft has used the Layered Window API to snazzy up the mouse cursor with a little transparent drop shadow. This is done in the same fashion as the opacity setting in .NET Windows Forms. When changing the opacity of a form via its opacity property, the form makes a Windows API call behind the scenes, yep that's right, its just a simple winAPI wrapper smashed into a single property. Obviously you lose a little control. Let's get some back.
Background
Before the .NET Framework, transparent windows were done the same way as they are now, It just took quite a few more lines of code. When setting the opacity to a value besides 100%, the .NET Framework makes a call to SetWindowLong
and throws a flag to toggle on the WS_EX_LAYERED API
. Layered windows are a very powerful tool, but we will be talking about just the simplest use of it, setting a forms's Opacity, afterward SetLayeredWindowAttributes
is called, passing to it a byte value indicating the level of opacity. 0 for transparent and 255 for opaque.
Sounds simple enough, but, Windows doesn't like that change, when you make the transition between not layered and layered windows, your form flickers black. And that's not attractive. and every time you alter the opacity property between 100% and any other valid value, the switch is made and you run the almost inevitable chance of seeing the infamous black flicker.
The trick here is to just keep the form a layered window all the time, your form will use a little more system resources, but not enough to make a real difference. Your computer is already making hundreds of calls to the layered window API, just a few more won't make any significant differences.
Using the Code
Included in the source download is my class called DDFormFader
. It's a Layered Window wrapper class that handles all the trouble for you! You simply construct it passing the Handle to the form you want to control the opacity of and they use its pubic
functions to manipulate the transparency effects.
DDFormsExtentions.DDFormFader FF;
public Form1()
{
InitializeComponent();
FF = new DDFormsExtentions.DDFormFader(Handle);
FF.setTransparentLayeredWindow();
FF.seekSpeed = 5;
FF.StepsToFade = 8;
FF.updateOpacity((byte)0, false);
Load += new EventHandler(Form1_Load);
}
void Form1_Load(object sender, EventArgs e)
{
FF.seekTo((byte)255); }
You can easily change the form's opacity using the UpdateOpacity
method, and fade to any opacity using the seekTo
method.
But enough of that. Let's see the real code!
First we interlop the user32
methods responsible for setting the flags that make the magic happen:
[DllImport("user32.dll", SetLastError = true)]
static extern int GetWindowLong(IntPtr hWnd, int nIndex);
DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll")]
static extern bool SetLayeredWindowAttributes(IntPtr hwnd, uint crKey,
byte bAlpha, uint dwFlags);
And set up the flags you will need:
private const int GWL_EXSTYLE = (-20);
private const int WS_EX_LAYERED = 0x80000;
private const long LWA_ALPHA = 0x2L;
Now we toggle on the Layered window style with a simple call to the setWindowLong
function, followed by a call to SetLayeredWindowAtrributes
where the 3rd param is a byte representing the level of opacity, 0 for transparent, 255 for Opaque, and any number in between. Much finer control than .NET's percent system.
SetWindowLong(frmHandle, GWL_EXSTYLE,
GetWindowLong(frmHandle, GWL_EXSTYLE) ^ WS_EX_LAYERED);
SetLayeredWindowAttributes(frmHandle, 0, _currentTransparency, (uint)LWA_ALPHA);
To disable the Layered window and revert to a standard window, you simply call the SetWindowLong
method again with the same params. The DDFormFader
class included has more functionality and is completely commented. Thanks!
Points of Interest
One thing to note that threw me for a loop was immediately after calling setWindowLong
it is necessary to call SetLayeredWindowAttributes
. If it's not immediately after the form will stop painting correctly and crazy things will happen, just remember to call SetLayeredWindowAttributes
on the very next line. Or of course just use my class, it handles it all for you. :)
History
- Current Version 1.0 Original release