Sometimes you need an "irregular" (non-rectangular) shape window, be it a splash screen, or a desktop widget.
There are several functions in the Windows API that can be used to create "wrong" windows, such as : CreateEllipticRgn, CreateRectRgn, CreatePolygonRgn , CreateRoundRectRgn , CombineRgn etc., but when you work with these functions, you find a number of disadvantages, the windows are angular, oblique edges have unpleasant teeth, you can not make a good shadow, and writing code to create a window of complex shape sometimes requires a lot of effort.
Since Windows 2000, windows have had an extended style WS_EX_LAYERED which makes the window multi-layer, and also added some API functions for working with such windows, one of them UpdateLayeredWindow which is responsible for updating the position, size, shape, content, and transparency of a layered window.This function allows you to create a window based on an image, including PNG , taking into account the alpha channel. Creating a window shape based on a pre-prepared image is much more convenient and easier than working with regions, but this method also has its own drawback. On a layered window it is not possible, in a simple way, to display any components, such as buttons, text boxes, etc., this is a consequence of the fact that the operating system takes over the entire process of redrawing the window, and the standard message WM_PAINT to the window is no longer sent. In the majority of all sorts of splash screens, widgets and other decorations do not require any additional components, or require them in a minimal amount, so you can ignore the drawback.
Next I’d like to give a little visual example of using layered windows. Since it all comes down to API function calls, the programming language can be anything, but I have Visual Studio installed at work, so I will write in VB.NET I won’t describe all API functions which are used here; I’ll add more links to MSDN pages below, because the main goal of this article is to show their practical use.
- First we need to draw our future window in your favorite graphics editor, I drew in Photoshop, necessarily irregularly shaped and necessarily with transparency, to feel all the charms of layered windows, and save it in the format PNG I got a sticker like this :
- Next, create a new project in Visual Studio and add our image to the resource under the name "Sticker", remove all unnecessary headers and borders from the only form in the project.
- You need to define the API functions and structures that will be needed in the process. I usually do this in a separate class.
Namespace SystemPublic Class Win32APIPublic Const WS_EX_LAYERED = H80000Public Const ULW_ALPHA As Int32 = H2Public Const AC_SRC_OVER As Byte = H0Public Const AC_SRC_ALPHA As Byte = H1'Point (coordinate)'<StructLayout(LayoutKind.Sequential)> _Public Structure PointPublic x As Int32Public y As Int32Public Sub New(ByVal x As Int32, ByVal y As Int32)Me.x = xMe.y = yEnd SubEnd Structure'Size'<StructLayout(LayoutKind.Sequential)> _Public Structure SizePublic cx As Int32Public cy As Int32Public Sub New(ByVal cx As Int32, ByVal cy As Int32)Me.cx = cxMe.cy = cyEnd SubEnd Structure' Defines the output mode of semi-transparent images'<StructLayout(LayoutKind.Sequential, Pack:=1)> _Public Structure BLENDFUNCTIONPublic BlendOp As BytePublic BlendFlags As BytePublic SourceConstantAlpha As BytePublic AlphaFormat As BytePublic Sub New(ByVal BledOp As Byte, ByVal BlendFlags As Byte, ByVal SourceContrastAlpha As Byte, ByVal AlphaFormat As Byte)Me.BlendOp = BledOpMe.BlendFlags = BlendFlagsMe.SourceConstantAlpha = SourceContrastAlphaMe.AlphaFormat = AlphaFormatEnd SubEnd Structure' Gets the display context descriptor for the client area of the specified window'<DllImport("user32.dll")> _Public Shared Function GetDC(ByVal hWnd As IntPtr) As IntPtrEnd Function'Creates a compatible context with the given device'<DllImport("gdi32.dll")> _Public Shared Function CreateCompatibleDC(ByVal hDC As IntPtr) As IntPtrEnd Function'Frees the context.<DllImport("user32.dll", ExactSpelling:=True)> _Public Shared Function ReleaseDC(ByVal hWnd As IntPtr, ByVal hDC As IntPtr) As IntegerEnd Function'Removes context'.<DllImport("gdi32.dll")> _Public Shared Function DeleteDC(ByVal hdc As IntPtr) As BooleanEnd Function'Selects an object in the given context'<DllImport("gdi32.dll", ExactSpelling:=True)> _Public Shared Function SelectObject(ByVal hDC As IntPtr, ByVal hObject As IntPtr) As IntPtrEnd Function'Deletes an object'<DllImport("gdi32.dll")> _Public Shared Function DeleteObject(ByVal hObject As IntPtr) As BooleanEnd Function'Updates a multi-layer window'<DllImport("user32.dll")> _Public Shared Function UpdateLayeredWindow(ByVal hwnd As IntPtr, ByVal hdcDst As IntPtr, ByRef pptDst As Win32API.Point, ByRef psize As Win32API.Size, ByVal hdcSrc As IntPtr, ByRef pprSrc As Win32API.Point, ByVal crKey As Int32, ByRef pblend As Win32API.BLENDFUNCTION, ByVal dwFlags As Int32) As BooleanEnd FunctionEnd ClassEnd Namespace
- This and all following code is written into the class of our only form in the project. First we need to describe some local variables, we will need them in the process.
Private _ScreenDC As IntPtr = IntPtr.ZeroPrivate _MemDC As IntPtr = IntPtr.ZeroPrivate _BitmapHandle As IntPtr = IntPtr.ZeroPrivate _OldBitmapHandle As IntPtr = IntPtr.ZeroPrivate _SizeAs Win32API.Size = NothingPrivate _PoinSource As Win32API.Point= NothingPrivate _TopPos As Win32API.Point = NothingPrivate _Blend As Win32API.BLENDFUNCTION = NothingPrivate _Opacity As Byte = 255Private bmpDest As Bitmap = NothingPrivate bmpSrc As Bitmap = Nothing
- Making a window multi-layer. In .NET you can assign some properties to the window just before it is created (API function call CreateWindowEx function ) by overriding the class property CreateParams
Protected Overrides ReadOnly Property CreateParams() As System.Windows.Forms.CreateParamsGetDim CP As CreateParams = MyBase.CreateParamsCP.ExStyle = CP.ExStyle Or Win32API.WS_EX_LAYEREDReturn CPEnd GetEnd Property
- Creates a multilayer window update function based on the image and the degree of overall transparency (0 to 255).
Public Sub SetImage(ByRef Bitmap As Bitmap, ByVal Opacity As Byte)'Get the descriptor for the display context'_ScreenDC = Win32API.GetDC(0)'Create a display compatible context'._MemDC = Win32API.CreateCompatibleDC(_ScreenDC)'Picture handle'._BitmapHandle = IntPtr.Zero'Handle of the old image'_OldBitmapHandle = IntPtr.ZeroTry'Get the handle of the image'_BitmapHandle = Bitmap.GetHbitmap(Color.FromArgb(0))'Save the image handle in case of an error'_OldBitmapHandle = Win32API.SelectObject(_MemDC, _BitmapHandle)'Set the size of the window, in our case it's the size of the image'_Size = New Win32API.Size(Bitmap.Width, Bitmap.Height)_PoinSource = New Win32API.Point(0, 0)_TopPos = New Win32API.Point(Me.Left, Me.Top)'Fill in the BLENDFUNCTION structure'._Blend = New Win32API.BLENDFUNCTION(Win32API.AC_SRC_OVER, 0, Opacity, Win32API.AC_SRC_ALPHA)'Update the layered window'.Win32API.UpdateLayeredWindow(Me.Handle, _ScreenDC, _TopPos, _Size, _MemDC, _PoinSource, 0, _Blend, Win32API.ULW_ALPHA)FinallyWin32API.ReleaseDC(IntPtr.Zero, _ScreenDC)If _BitmapHandle <> IntPtr.Zero ThenWin32API.SelectObject(_MemDC, _OldBitmapHandle)Win32API.DeleteObject(_BitmapHandle)End IfWin32API.DeleteDC(_MemDC)_Size = Nothing_PoinSource = Nothing_TopPos = Nothing_Blend = NothingEnd TryEnd Sub
- When a form instance is loaded, we form an image (write text on a sticker, just static) and then pass it to the update function.
Private Sub FormSticker_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.LoadbmpSrc = My.Resources.StickerbmpDest = New Bitmap(bmpSrc.Width, bmpSrc.Height)Using g As Graphics = Graphics.FromImage(bmpDest)With g.InterpolationMode = Drawing2D.InterpolationMode.NearestNeighbor.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias.TextRenderingHint = Drawing.Text.TextRenderingHint.AntiAlias'Drawing a sticker'.drawImage(bmpSrc, 0, 0, bmpSrc.Width, bmpSrc.Height)'Writing text'Dim sf As New StringFormat(StringFormatFlags.LineLimit)sf.Alignment = StringAlignment.Centersf.LineAlignment = StringAlignment.Center.DrawString("Go for milk and pick up the cat from the vet", Me.Font, New SolidBrush(Me.ForeColor), New Rectangle(10, 10, bmpDest.Width - 20, bmpDest.Height - 20), sf)End WithEnd UsingMe.SetImage(bmpDest, Me._Opacity)bmpDest.Dispose()End Sub
Well, that’s pretty much it, run it and see what we have (the result is in the first image).
- Descriptions of structures and functions used
Point , Size , GetDC , ReleaseDC , DeleteDC , SelectObject , DeleteObject , UpdateLayeredWindow
- Original example