One common problem that comes up in the creation of stylized windows such as Splash screens is the desire to have parts of the form be “shadowed” or, in other words, blend with the forms behind them. You can see this effect in action with other application splash screens, such as photoshop:
What witchcraft is this? How does Photoshop do it? One of the first things you might try with .NET is something like the TransparencyKey of the form. Of course, this doesn’t work, instead you get this unsightly ring of the transparency key around it.
The solution, of course, is a bit more complicated.
Starting with Windows 2000, the Win32 API and Window Manager has supported the idea of “layered” Windows. Before this, Windows had concepts known as “regions”; this basically allowed you to define regions of your window where the background would show through. This didn’t allow for per-pixel blending, but that sort of thing was not widely supported (or even conceived of, really) by most graphics Adapters. The “Layered” Windowing API basically provided a way to allow Per-pixel Alpha. Which is what we want.
Windows Forms, however, does not expose this layered window API through it’s methods or properties, with the exception of the opacity property. I don’t recall if WPF does either, but I would be surprised if it’s capabilities exposed more of the Layered Windowing API. Basically- we’re on our own
So what exactly do we need to do? Well, one thing we need to do is override the CreateParams property of the desired form, so that it adds the WS_EX_LAYERED style to the window, letting the Windowing Manager know it has to do more work with this window. Since we would like a neatly decoupled implementation, we are going to create a class that derives from Form, but use that as the base class for another form. This let’s us override the CreateParams property within that base class, but this has a bit of a caveat as well, since I’ve found that makes the Designer a bit annoyed and it complains and won’t let you use the designer. So you will have to deal with that particular point yourself. (I imagine there is a way to make it work, but I don’t want to bloat the class with such functionality). If necessary, the appropriate functions and the CreateParams override can be put into the classes that need this functionality instead.
Here is the PerPixelAlphaForm class. By the way, I’m going with Visual Basic.NET because there are already a million hits for this functionality with C#:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
Imports System Imports System.Drawing Imports System.Drawing.Imaging Imports System.Windows.Forms Imports System.Runtime.InteropServices ' class that exposes needed windows GDI Functions. Public Class Win32 Public Enum Bool bFalse = 0 bTrue = 1 End Enum <StructLayout(LayoutKind.Sequential)> Public Structure Point Public x As Int32 Public y As Int32 Public Sub New(x As Int32, y As Int32) Me.x = x Me.y = y End Sub End Structure <StructLayout(LayoutKind.Sequential)> Public Structure Size Public cx As Int32 Public cy As Int32 Public Sub New(cx As Int32, cy As Int32) Me.cx = cx Me.cy = cy End Sub End Structure <StructLayout(LayoutKind.Sequential, Pack:=1)> Structure ARGB Public Blue As Byte Public Green As Byte Public Red As Byte Public Alpha As Byte End Structure <StructLayout(LayoutKind.Sequential, Pack:=1)> Public Structure BLENDFUNCTION Public BlendOp As Byte Public BlendFlags As Byte Public SourceConstantAlpha As Byte Public AlphaFormat As Byte End Structure Public Const ULW_COLORKEY As Int32 = &H1 Public Const ULW_ALPHA As Int32 = &H2 Public Const ULW_OPAQUE As Int32 = &H4 Public Const AC_SRC_OVER As Byte = &H0 Public Const AC_SRC_ALPHA As Byte = &H1 <DllImport("user32.dll", ExactSpelling:=True, SetLastError:=True)> Public Shared Function UpdateLayeredWindow(hwnd As IntPtr, hdcDst As IntPtr, ByRef pptDst As Point, ByRef psize As Size, hdcSrc As IntPtr, ByRef pprSrc As Point, crKey As Int32, ByRef pblend As BLENDFUNCTION, dwFlags As Int32) As Bool End Function <DllImport("user32.dll", ExactSpelling:=True, SetLastError:=True)> Public Shared Function GetDC(hWnd As IntPtr) As IntPtr End Function <DllImport("user32.dll", ExactSpelling:=True)> Public Shared Function ReleaseDC(hWnd As IntPtr, hDC As IntPtr) As Integer End Function <DllImport("gdi32.dll", ExactSpelling:=True, SetLastError:=True)> Public Shared Function CreateCompatibleDC(hDC As IntPtr) As IntPtr End Function <DllImport("gdi32.dll", ExactSpelling:=True, SetLastError:=True)> Public Shared Function DeleteDC(hdc As IntPtr) As Bool End Function <DllImport("gdi32.dll", ExactSpelling:=True)> Public Shared Function SelectObject(hDC As IntPtr, hObject As IntPtr) As IntPtr End Function <DllImport("gdi32.dll", ExactSpelling:=True, SetLastError:=True)> Public Shared Function DeleteObject(hObject As IntPtr) As Bool End Function End Class Public MustInherit Class PerPixelAlphaForm Inherits Form Public Sub New() FormBorderStyle = FormBorderStyle.None End Sub Public Overloads Sub SetBitmap(bmp As Bitmap) SetBitmap(bmp, 255) End Sub Protected Overrides ReadOnly Property CreateParams() As CreateParams Get Dim cp As CreateParams = MyBase.CreateParams cp.ExStyle = cp.ExStyle Or &H80000 Return cp End Get End Property Public Overloads Sub SetBitmap(bmp As Bitmap, Opacity As Byte) If bmp.PixelFormat <> PixelFormat.Format32bppArgb Then Throw New ApplicationException("The Bitmap must be a 32bpp with a Alpha Channel") End If 'Create Compatible DC with the screem 'Select bitmap with 32bpp with alpha into the compatible DC 'call UpdateLayeredWindow Dim ScreenDC As IntPtr = Win32.GetDC(IntPtr.Zero) Dim MemDC As IntPtr = Win32.CreateCompatibleDC(ScreenDC) Dim hBitmap As IntPtr = IntPtr.Zero Dim oldBitmap As IntPtr = IntPtr.Zero Try hBitmap = bmp.GetHbitmap(Color.FromArgb(0)) oldBitmap = Win32.SelectObject(MemDC, hBitmap) Dim thesize As Win32.Size = New Win32.Size(bmp.Width, bmp.Height) Dim pointSource As Win32.Point = New Win32.Point(0, 0) Dim topPos As Win32.Point = New Win32.Point(Left, Top) Dim blend As Win32.BLENDFUNCTION = New Win32.BLENDFUNCTION() blend.BlendOp = Win32.AC_SRC_OVER blend.BlendFlags = 0 blend.SourceConstantAlpha = Opacity blend.AlphaFormat = Win32.AC_SRC_ALPHA Win32.UpdateLayeredWindow(Handle, ScreenDC, topPos, New Win32.Size(Size.Width, Size.Height), MemDC, pointSource, 0, blend, Win32.ULW_ALPHA) Catch ex As Exception Finally Win32.ReleaseDC(IntPtr.Zero, ScreenDC) If Not hBitmap = IntPtr.Zero Then Win32.SelectObject(MemDC, oldBitmap) Win32.DeleteObject(hBitmap) End If Win32.DeleteDC(MemDC) End Try End Sub End Class |
This provides the core implementation. The “Win32” class here provides the needed API declarations.
In order to use this, we need to make our form derive from it, and then use the inherited SetBitmap function:
1 2 3 4 5 6 7 8 |
Public Class Form1 Inherits PerPixelAlphaForm Private useBackground As Bitmap Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load useBackground = New Bitmap("D:\pngtest.png") SetBitmap(useBackground) End Sub End Class |
(Here I just used an image on my second Hard disk that had a transparency channel, but the Bitmap constructor parameter can easily be changed.). The result:
The Project I used for this can be downloaded from here. This was created using Visual Studio Professional 2012, Update 1- other editions or earlier versions may have problems with the project or source files.
Have something to say about this post? Comment!
13 thoughts on “Per Pixel Alpha on VB.NET Forms”
Hello,
(Sorry, my english is poor…)
I have a lot of problems with the transparancy key and your solution seems really excellent!
But I have a problem : I work in a company using Visual Studio 2010 Ultimate Edition with the .NET Framework 4, and it doesn’t work with this configuration. Do you know a solution for this configuration?
Thanks a lot,
Emths
I was able to get this working in Visual Studio 2010 Ultimate With framework 4. Anything specific going wrong?
These are the two errors it appears :
Error 1 Base class ‘PerPixelAlphaForm’ specified for class ‘Form2’ cannot be different from the base class ‘System.Windows.Forms.Form’ of one of its other partial types. C:\Users\xavier.jacquet\AppData\Local\Temporary Projects\WindowsApplication1\Form2.vb 3 14 WindowsApplication1
Error 2 ‘SetBitmap’ is not declared. It may be inaccessible due to its protection level. C:\Users\xavier.jacquet\AppData\Local\Temporary Projects\WindowsApplication1\Form2.vb 18 9 WindowsApplication1
In fact, I worked for about 15 years in Visual Basic 6 and just begin to work with OOA and OOD. So I have maybe do something wrong…
Ahh OK, I got that error too. I think I fixed it with Resharper. You can fix it manually by clicking the “Show All Files” button on the Solution Explorer; The Form will now have a drop-down and you can edit the .Designer.vb file and change it to Inherit from the PerPixelAlphaForm.
Ok! Thanks a lot! I will test it as soon as possible (I’m not at work this afternoon…) I’ll tell you my results! 🙂
Hello,
I was very busy since I began to work on it, but I’ve tried and it works perfectly! Thanks a lot!
But I have a last question : could I add some controls (like a ProgressBar) on it? I’ve tried to do it creating the progress bar in code, but it doesn’t work… Do you know a solution?
Have a nice day!
Ideally you would design the form first (with the controls on the form as desired) and then swap the base class. I tried fiddling with it to get the Designer to work with the PerPixelAlphaForm class, but wasn’t able to get that to work. If it’s already swapped over you’d probably add code to the InitializeComponent() method and create appropriate fields for new controls, and assign them much in the manner that the Designer-Generated code does.
Yes, it’s exactly what I thought. I’m going to test it as soon as possible. Thanks a lot for your help! 😉
Hi,
first thank you for your code, it helped me very much. But i got a problem that all controls i add to my form are not visible.
Do you know why?
Thank you very much!
Hello,
Thanks alot for providing this code! Unfortunatelly I dont get it to run properly. I am aswell using vb.net 2010 (express) so I had the same issues Emths had at first.
when I open the .Designer.vb and replace
Inherits System.Windows.Forms.Form
with
Inherits PerPixelAlphaForm
vb throws the following error:
Der Designer kann keine Instanz des Typs WindowsApplication1.PerPixelAlphaForm erstellen, da dieser als abstrakt deklariert ist.
? Translation: The designer was not able to generate an instance of type WindowsApplication1.PerPixelAlphaForm, due of its abstract declaration.
I hope you can help me on this!
thanks in advance
Hello
thanks alot you for this code! unfortunatelly I was not able to execute it poperly.
I am using Visual Basic 2010 .net so experienced the same issues Emths did.
However I was not able to debug the code after replacing:
Inherits System.Windows.Forms.Form
with
Inherits PerPixelAlphaForm
in the .Design.vb
Instead VB throws the following warning:
Der Designer kann keine Instanz des Typs WindowsApplication1.PerPixelAlphaForm erstellen, da dieser als abstrakt deklariert ist.
Translated:
The designer was not able to create an instance of type WindowsApplication1.PerPixelAlphaForm, due of its abstract declaration
Please help me solve this issue
Regards
Omega
Hi, Thanks for reading! This is unfortunately a limitation of the implementation here which prevents the form from being used in the designer. That’s the issue I mentioned regarding designing the class. One approach would be to keep the form deriving from “Form” until you are finished using the designer, then swap the base class for the replaced one afterwards. A less-than-perfect solution. However thinking about it I’m not sure why the class I created needs to be set to “MustInherit” I’m not in a position to give it a try at this moment but you could try removing MustInherit from the PerPixelAlphaForm class declaration. and seeing if that allows you to use the designer.