What's the Quickest Way to Compute Log2 of an Integer in C#

C# Form.TransparencyKey working different for different colors, why?

I've heard of this problem before but never realized it was related to the TransparencyKey choice. Nice find. It is almost certainly caused by Aero. With it disabled, the effect is implemented by use of a hardware overlay in the video adapter. With it enabled, the desktop window compositing feature implements it. You can typically tell by a very brief flash of the transparency color before DWM catches up and replaces the area with the pixels from the windows in the background. Turning DWM off for your window might fix the problem but you'll also lose the glass effects.

I can see little rhyme or reason to the color value, looks pretty random to me. Hard to call this anything else than a bug. I never ran into this before myself, I always use the same transparency key. Color.Fuchsia, an excellent fuchsed-up color. Recommended.

Form's TransparencyKey leaves ghastly colored edging

This code is a translation (with minor interpretations) of the code found here:

Windows Form Transparent Background Image.

Originally from the Microsoft samples code-base (at least it was, before they killed it).


When a Form is rendered transparent, setting its TransparencyKey to the same Color used as the BackGroundColor and then draw a semi-transparent Bitmap on the transparent surface of the Form, the anti-aliased parts of the Bitmap are not blended with whatever there is behind the Form.

The Color used as TransparencyKey may affect the rendering result, but the semi-transparent pixels (especially the pixels near the edges of the Bitmap) will always be visible on the different backgrounds, since there's no blending.

To solve the problem, we can build a Layered Window:

The system automatically composes and repaints layered windows and the
windows of underlying applications. As a result, layered windows are
rendered smoothly, without the flickering typical of complex window
regions. In addition, layered windows can be partially translucent,
that is, alpha-blended.

To create a Layered Form, we can set the WS_EX_LAYERED extended style overriding the Form's CreateParams property:

Protected Overrides ReadOnly Property CreateParams As CreateParams
Get
Dim parms As CreateParams = MyBase.CreateParams
parms.ExStyle = parms.ExStyle Or WS_EX_LAYERED
Return parms
End Get
End Property

Windows 8+: The WS_EX_LAYERED style is supported for top-level windows and child windows. Previous Windows versions support this style only for top-level windows.

To draw a Bitmap that can blend with the background, we select a Bitmap into the Window Device Context, then call UpdateLayeredWindow, specifying the type of rendering using a BLENDFUNCTION structure.

This structure allows to define (BlendOp) how the source and destination Bitmaps are blended (actually, the only possible operation is Source Over, AC_SRC_OVER), the level of opacity applied to the source Bitmap (SourceConstantAlpha: 255 = opaque, 0 = fully transparent) and how the Colors of the source and destination bitmaps are interpreted (AlphaFormat).

Here, we want to blend a source Bitmap that has an Alpha Channel (per-pixel alpha), so it's semi-transparent: we specify AC_SRC_ALPHA as the AlphaFormat (see the Docs about how Color blending is interpreted based on the Color type of the source bItmap).

That's all.

To build a Layered Form, add a new Form to a Project, change the Constructor as shown here, add the CreateParams override, the WndProc override if the Form can be moved dragging it and the SelectBitmap() method call, which activates the alpha blending of the source Bitmap (passed in the constructor) and the Form's DC.

Also, add the NativeMethods support class to the Project:

► The Form can be created as usual, in this case passing a Bitmap object to its constructor:

(the Bitmap format must be a 32bit ARGB - a PNG with alpha channel will do)

Dim layeredForm As New PerPixelAlphaLayeredForm(bitmap)
layeredForm.Show()

Public Class PerPixelAlphaLayeredForm
Public Sub New()
Me.New(Nothing)
End Sub

Public Sub New(bitmap As Bitmap)
InitializeComponent()
Me.LayerBitmap = bitmap
End Sub

Private ReadOnly Property LayerBitmap As Bitmap

Protected Overrides Sub OnLoad(e As EventArgs)
MyBase.OnLoad(e)
If Me.LayerBitmap IsNot Nothing Then
Me.ClientSize = Me.LayerBitmap.Size
Dim screenSize = Screen.FromHandle(Me.Handle).Bounds.Size
Me.Location = New Point((screenSize.Width - Me.Width) \ 2, (screenSize.Height - Me.Height) \ 2)
SelectBitmap(Me.LayerBitmap)
' Or, call the SelectBitmapFadeOut() method
' Task.Run(Function() SelectBitmapFadeOut(Me.LayerBitmap))
End If
Me.TopMost = True
End Sub

Private Sub SelectBitmap(bitmap As Bitmap)
NativeMethods.SelectBitmapToLayeredWindow(Me, bitmap, 255)
End Sub

Private Async Function SelectBitmapFadeOut(bitmap As Bitmap) As Task
Dim fadeProgress As Integer = 255
For i = fadeProgress To 1 Step -1
BeginInvoke(New MethodInvoker(Sub() NativeMethods.SelectBitmapToLayeredWindow(Me, bitmap, fadeProgress)))
fadeProgress -= 1
Await Task.Delay(10)
Next
End Function

Protected Overrides ReadOnly Property CreateParams As CreateParams
Get
Dim parms As CreateParams = MyBase.CreateParams
If Not DesignMode Then parms.ExStyle = parms.ExStyle Or NativeMethods.WS_EX_LAYERED
Return parms
End Get
End Property

Protected Overrides Sub WndProc(ByRef m As Message)
If m.Msg = NativeMethods.WM_NCHITTEST Then
m.Result = New IntPtr(NativeMethods.HTCAPTION)
Else
MyBase.WndProc(m)
End If
End Sub
End Class

NativeMethods support class:

Imports System.Drawing.Imaging
Imports System.Runtime.InteropServices

Friend Class NativeMethods
Public Const HTCAPTION As Integer = &H2
Public Const WM_PAINT = &HF
Public Const WM_NCHITTEST As Integer = &H84
Public Const WS_EX_LAYERED As Integer = &H80000

Public Const AC_SRC_OVER As Byte = 0
Public Const AC_SRC_ALPHA As Byte = 1

<Flags>
Friend Enum ULWFlags
ULW_COLORKEY = &H1
ULW_ALPHA = &H2
ULW_OPAQUE = &H4
ULW_EX_NORESIZE = &H8
End Enum

<StructLayout(LayoutKind.Sequential)>
Friend Structure POINT
Public x As Integer
Public y As Integer
Public Sub New(X As Integer, Y As Integer)
Me.x = X
Me.y = Y
End Sub
End Structure

<StructLayout(LayoutKind.Sequential)>
Friend Structure SIZE
Public cx As Integer
Public cy As Integer
Public Sub New(cX As Integer, cY As Integer)
Me.cx = cX
Me.cy = cY
End Sub
End Structure

<StructLayout(LayoutKind.Sequential, Pack:=1)>
Friend 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)>
Friend Structure BLENDFUNCTION
Public BlendOp As Byte
Public BlendFlags As Byte
Public SourceConstantAlpha As Byte
Public AlphaFormat As Byte
End Structure

<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Friend 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 Integer,
ByRef pblend As BLENDFUNCTION, dwFlags As ULWFlags) As Boolean
End Function

<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Friend Shared Function SetLayeredWindowAttributes(hWnd As IntPtr, crKey As Integer,
bAlpha As Byte, dwFlags As ULWFlags) As Boolean
End Function

<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Friend Shared Function ReleaseDC(hWnd As IntPtr, hDC As IntPtr) As Integer
End Function

<DllImport("gdi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Friend Shared Function CreateCompatibleDC(hDC As IntPtr) As IntPtr
End Function

<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Friend Shared Function GetDC(hWnd As IntPtr) As IntPtr
End Function

<DllImport("gdi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Friend Shared Function DeleteDC(hdc As IntPtr) As Boolean
End Function

<DllImport("gdi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Friend Shared Function SelectObject(hDC As IntPtr, hObject As IntPtr) As IntPtr
End Function

<DllImport("gdi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Friend Shared Function DeleteObject(hObject As IntPtr) As Boolean
End Function

Public Shared Sub SelectBitmapToLayeredWindow(form As Form, bitmap As Bitmap, opacity As Integer)

If bitmap.PixelFormat <> PixelFormat.Format32bppArgb Then
Throw New ApplicationException("The bitmap must be 32bpp with alpha-channel.")
End If

Dim screenDc As IntPtr = GetDC(IntPtr.Zero)
Dim sourceDc As IntPtr = CreateCompatibleDC(screenDc)
Dim hBitmap As IntPtr = IntPtr.Zero
Dim hOldBitmap As IntPtr = IntPtr.Zero

Try
' Get handle to the New bitmap and select it into the current device context.
hBitmap = bitmap.GetHbitmap(Color.FromArgb(0))
hOldBitmap = SelectObject(sourceDc, hBitmap)

Dim windowLocation As New POINT(form.Left, form.Top)
Dim windowSize As New SIZE(bitmap.Width, bitmap.Height)
Dim sourceLocation As New POINT(0, 0)
Dim blend As New BLENDFUNCTION() With {
.BlendOp = AC_SRC_OVER,
.BlendFlags = 0,
.SourceConstantAlpha = CType(opacity, Byte),
.AlphaFormat = AC_SRC_ALPHA
}

' Update the window.
' Handle => Handle to the layered window
' screenDc => Handle to the screen DC
' windowLocation => Screen position of the layered window
' windowSize => SIZE of the layered window
' sourceDc => Handle to the layered window surface DC
' sourceLocation => Location of the layer in the DC
' 0 => Color key of the layered window
' blend => Transparency of the layered window
' ULW_ALPHA => Use blend as the blend function
UpdateLayeredWindow(form.Handle, screenDc, windowLocation, windowSize,
sourceDc, sourceLocation, 0, blend, ULWFlags.ULW_ALPHA)
Finally
' Release device context.
ReleaseDC(IntPtr.Zero, screenDc)
If hBitmap <> IntPtr.Zero Then
SelectObject(sourceDc, hOldBitmap)
DeleteObject(hBitmap)
End If
DeleteDC(sourceDc)
End Try
End Sub
End Class

You can download a Sample Project from Google Drive.

Built with .Net Framework 4.7.2 - any other Framework, 4.5.2+, will do

TransparencyKey Property on Forms

How about storing the previous values of BackColor and TransparencyKey in local variables, and restoring them when you want to revert to non-transparent? For instance:

private Color _oldBG;
private Color _oldTPKey;

private void MakeTransparent() {
_oldBG = BackColor;
_oldTPKey = TransparencyKey;
BackColor = Color.White;
TransparencyKey = Color.White;
}

private void MakeNonTransparent() {
BackColor = _oldBG;
TransparencyKey = _oldTPKey;
}

Unexpected transparency instead of white

To solve this issue, reset the TransparencyKey back to its default value, or remove the assignment:

this.TransparencyKey = Color.Empty;

instead of

this.TransparencyKey = Color.Transparent;

References: https://stackoverflow.com/a/13426429/1132334, and documentation:

When the TransparencyKey property is assigned a Color, the areas of the form that have the same BackColor will be displayed transparently

Titlebar not clickable with Form.TransparencyKey = White

Any part of the window that matches the TransparencyKey color will be transparent to the user's eye. And to the mouse, you'll click whatever window is underneath yours if you click on a part of the window that's transparent.

That makes using Color.White rather a bad choice for a color key, plenty of odds that the user's theme colors make parts of the title bar white as well. Like the X in the Close button.

Pretty important that you pick a color that does not appear anywhere else in the window so you don't get accidental transparency. The standard choice for such a color is Color.Fuchsia. Nice fuchsed-up colors that normally burn a pinhole in the user's retina and nobody would voluntarily use in their UI designs.


One more excruciating detail: this kind of transparency, created by using layered windows, is implemented differently on modern Windows versions that have Aero enabled. With Aero, it is no longer implemented by the video adapter but in software by the DWM. Which has a bug, it doesn't correctly handle the mouse transparency for certain kinds of color key values. This bug is not well characterized, but I know it doesn't work for Color.Red for example. So picking the right key color is important. Unless you want to take advantage of the bug, some programmers still want mouse events for transparent parts of the window. Risky, but the bug has been around for many years now so is probably not going to get fixed anymore.

Transparent Windows Form that can handle Click

If I understand your question correctly, you can Use TrancparencyKey

Set TrancparencyKey and BackColor properties both to same color like Color.Red.

Here is the screenshot of transparent form over visual studio:

enter image description here

Note:

  • When you use for example Color.Red every thing works fine and you can handle mouse Click. But the behavior is different for different colors, for example Color.Magenta the form can not capture the mouse Click.


Related Topics



Leave a reply



Submit