There are two possible solutions to the problem which both have advantages and disadvantages.
Solution 1 fixes the original problem stated by the OP by simply adding an application manifest file to the resources. There is no coding required for this solution. The transparency achieved with that solution is not perfect, but is supported on all Windows versions since Windows XP.
Solution 2 is more advanced as it creates a layered child window that provides true transparency of the image over the dialog background aswell as any overlapping child controls. The disadvantages are that at least Windows 8 is required and a decent amount of non-trivial code must be written (but you are lucky as I already did this for you ;-)).
Solution 1 - add an application manifest
The native static control supports bitmaps with alpha transparency only if you add an application manifest that specifies common controls version 6.0.0.0. From the "old-school" look of the controls in your screenshot we can see that you don't have such a manifest yet.
Save the following snippet into a file called "manifest.xml" and put it into your application resource folder. In Visual Studio, right-click your project, go to "manifest tool" > "input and output" > "additional manifest files" > enter relative path of "manifest.xml" without quotation marks.
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
<!-- Windows Vista -->
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
</application>
</compatibility>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
</assembly>
No further coding is required, just select the bitmap resource for the picture control (which actually is a static control) in the resource editor.
The bitmap to use should be a 32 bpp bitmap, bottom-up row order, non-premultiplied alpha. If you use PixelFormer to convert from PNG, use format A8:R8:G8:B8 (32 bpp) without selecting the other check boxes in the export dialog. If you use XnView to convert, simply save as BMP, it will use this format by default.
Result:
As we can see, we only get "fake" transparency. Any other controls underneath the image will be clipped at the boundaries of the static control.
Solution 2 - using a layered child window
True transparency is possible using a layered child window (WS_EX_LAYERED extended style). This is supported since Windows 8. It requires some coding though.
I wrapped the required code into a function SetLayeredWindowFromBitmapResource()
which might be called from the WM_INITDIALOG handler of the dialog. The function throws any error as std::system_error
exception, so you must add a try/catch block to handle the errors (this is shown further below in the "usage" example).
#include <system_error>
/// Turn given window into a layered window and load a bitmap from given resource ID
/// into it.
/// The window will be resized to fit the bitmap.
/// Bitmap must be 32bpp, top-down row order, premultiplied alpha.
///
///
ote For child windows, this requires Win 8 or newer OS
/// (and "supportedOS" element for Win 8 in application manifest)
///
/// exception Throws std::system_error in case of any error.
void SetLayeredWindowFromBitmapResource(
HWND hwnd, UINT bitmapResourceId, HINSTANCE hInstance = nullptr )
{
// Enable "layered" mode for the child window. This enables full alpha channel
// transparency.
// GetWindowLong() won't reset the last error in case of success.
// As we can't judge from the return value of GetWindowLong() alone if
// the function was successful (0 may be returned even in case of
// success), we must reset the last error to reliably detect errors.
::SetLastError( 0 );
DWORD exStyle = ::GetWindowLong( hwnd, GWL_EXSTYLE );
if( !exStyle )
{
// NOTE: Call GetLastError() IMMEDIATELY when a function's return value
// indicates failure and it is documented that said function supports
// GetLastError().
// ANY other code (be it your own or library code) before the next line
// must be avoided as it may invalidate the last error value.
if( DWORD err = ::GetLastError() )
throw std::system_error( static_cast<int>(err),
std::system_category(),
"SetLayeredWindowFromBitmapResource: Could not get extended window style" );
}
// SetWindowLong() won't reset the last error in case of success.
// As we can't judge from the return value of GetWindowLong() alone if
// the function was successful (0 may be returned even in case of
// success), we must reset the last error to reliably detect errors.
::SetLastError( 0 );
if( !::SetWindowLong( hwnd, GWL_EXSTYLE, exStyle | WS_EX_LAYERED ) )
{
if( DWORD err = ::GetLastError() )
throw std::system_error( static_cast<int>(err),
std::system_category(),
"SetLayeredWindowFromBitmapResource: Could not set extended window style" );
}
// Use RAII ( https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization )
// to cleanup resources even in case of exceptions.
// This greatly simplifies the code because now we don't have to manually cleanup the
// resources at every location in the code where we throw an exception.
struct Resources {
HBITMAP hImage = nullptr;
HGDIOBJ hOldImage = nullptr;
HDC hMemDC = nullptr;
// This destructor will be automatically called before the function
// SetLayeredWindowFromBitmapResource() returns aswell as any locations
// in the code where the "throw" keyword is used to throw an exception.
~Resources()
{
if( hMemDC )
{
if( hOldImage )
::SelectObject( hMemDC, hOldImage );
::DeleteDC( hMemDC );
}
if( hImage )
::DeleteObject( hImage );
}
} res;
// Make it possible to use nullptr as an argument for the hInstance parameter of
// this function. This means we will load the resources from the current executable
// (instead of another DLL).
if( ! hInstance )
hInstance = ::GetModuleHandle( nullptr );
// Load bitmap with alpha channel from resource.
// Flag LR_CREATEDIBSECTION is required to create a device-independent bitmap that
// preserves the alpha channel.
res.hImage = reinterpret_cast<HBITMAP>(::LoadImage(
hInstance, MAKEINTRESOURCE( bitmapResourceId ), IMAGE_BITMAP,
0, 0, LR_CREATEDIBSECTION ));
if( !res.hImage )
{
DWORD err = ::GetLastError();
throw std::system_error( static_cast<int>(err),
std::system_category(),
"SetLayeredWindowFromBitmapResource: Could not load bitmap resource" );
}
// Get bitmap information (width, height, etc.)
BITMAP imgInfo{ 0 };
if( !::GetObject( res.hImage, sizeof( imgInfo ), &imgInfo ) )
{
DWORD err = ::GetLastError();
throw std::system_error( static_cast<int>(err),
std::system_category(),
"SetLayeredWindowFromBitmapResource: Could not get bitmap information" );
}
if( imgInfo.bmBitsPixel != 32 || imgInfo.bmPlanes != 1 )
{
// Use a constant error value here because this is our own error condition.
// Of course GetLastError() wouldn't return anything useful in this case.
DWORD err = ERROR_INVALID_DATA;
throw std::system_error( err, std::system_category(),
"SetLayeredWindowFromBitmapResource: bitmap must be 32 bpp, single plane" );
}
// Create a memory DC that will be associated with the image.
// UpdateLayeredWindow() can't use image directly, it must be in a memory DC.
res.hMemDC = ::CreateCompatibleDC( nullptr );
if( !res.hMemDC )
{
DWORD err = ::GetLastError();
throw std::system_error( static_cast<int>(err),
std::system_category(),
"SetLayeredWindowFromBitmapResource: Could not create memory DC" );
}
res.hOldImage = ::SelectObject( res.hMemDC, res.hImage );
if( !res.hOldImage )
{
DWORD err = ::GetLastError();
throw std::system_error( static_cast<int>(err),
std::system_category(),
"SetLayeredWindowFromBitmapResource: Could not select bitmap into memory DC" );
}
// Assign the image to the child window, making it transparent.
SIZE size{ imgInfo.bmWidth, imgInfo.bmHeight };
POINT ptSrc{ 0, 0 };
BLENDFUNCTION blend{ AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
if( !::UpdateLayeredWindow( hwnd, nullptr, nullptr, &size, res.hMemDC, &ptSrc,
0, &blend, ULW_ALPHA ) )
{
DWORD err = ::GetLastError();
throw std::system_error( static_cast<int>(err),
std::system_category(),
"SetLayeredWindowFromBitmapResource: Could not update layered window" );
}
// Destructor of res object will cleanup resources here!
}
Usage:
The function may be called in WM_INITDIALOG handler of your dialog box procedure, see example below. The example also shows how to handle errors.
NOTE: I'm calling MessageBoxA() here because std::exception::what()
returns a const char*
which is apparently a multibyte (ANSI) encoded string that contains a localized error message from the OS (with VS2015 or newer).
#include <sstream>
/// Dialog box procedure.
INT_PTR CALLBACK TestDialogProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam ) {
UNREFERENCED_PARAMETER( lParam );
switch( message ) {
case WM_INITDIALOG: {
// This is the child window where we want to show the image (e. g. a static).
if( HWND hwndImage = ::GetDlgItem( hDlg, IDC_IMAGE ) ){
try{
SetLayeredWindowFromBitmapResource( hwndImage, IDB_BITM