How do I create a simple transparent window in Win32?

thebluetropics 1,046 Reputation points
2023-03-17T23:02:43.1566667+00:00

Hello. I am currently trying to build a semi-transparent window. Here is the expected window that I am trying to build (the water and the boat is my desktop wallpaper, not the window itself):

help1

I have read some other posts about this (post1, post2). However, I am confused, whether I should draw on the WM_PAINT as usual or just use UpdateLayeredWindow() and left WM_PAINT unhandled. Because both post that I read doesn't handle WM_PAINT at all. Someone also told me that I can't ignore WM_PAINT completely, which makes me more confused.

The above image is what I want to achieve. But here is the simpler example:

// Inside WM_PAINT
PAINTSTRUCT ps;
HDC hdc = BeginPaint(_phwnd, &ps);

Gdiplus::Graphics graphics(hdc);

Gdiplus::Rect rect(0, 0, 100, 100);
Gdiplus::SolidBrush brush(Gdiplus::Color(128, 255, 255, 255));
    
graphics.FillRectangle(&brush, rect);

EndPaint(_phwnd, &ps);

That is, I wanted to draw a semi-transparent white rectangle.

Also, I see that both SetLayeredWindowAttributes and UpdateLayeredWindow accepts COLORREF, that this color will be transparent. Is it possible for me to use all possible RGB color? Because I don't want the white color to became invisible in my window.

Best regards,

Windows API - Win32
Windows API - Win32
A core set of Windows application programming interfaces (APIs) for desktop and server applications. Previously known as Win32 API.
2,423 questions
0 comments No comments
{count} vote

Accepted answer
  1. Castorix31 81,726 Reputation points
    2023-03-18T08:12:25.17+00:00

    You can use DirectComposition

    For the test, I left the caption and the drawing is done on Right-Click (I set opacity 0.5 for black and white) :

    User's image

    #include <windows.h>
    #include <tchar.h>
    
    #include <d3d11.h>
    #pragma comment (lib, "D3D11")
    #include <d2d1.h>
    #pragma comment (lib, "D2d1")
    #include <d2d1_1.h>
    #include <d2d1_3.h>
    #include <dxgi1_2.h>
    
    #include <dcomp.h>
    #pragma comment(lib, "dcomp")
    
    #pragma comment(linker,"\"/manifestdependency:type='win32' \
    name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
    processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
    
    HINSTANCE hInst;
    LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
    int nWidth = 800, nHeight = 400;
    #define IDC_STATIC 10
    #define IDC_BUTTON 11
    
    ID2D1Factory1* m_pD2DFactory1 = NULL;
    ID3D11Device* m_pD3D11Device = NULL;
    ID3D11DeviceContext* m_pD3D11DeviceContext = NULL;
    IDXGIDevice1* m_pDXGIDevice = NULL;
    ID2D1Device* m_pD2DDevice = NULL;
    ID2D1DeviceContext3* m_pD2DDeviceContext3 = NULL;
    IDXGISwapChain1* m_pDXGISwapChain1 = NULL;
    ID2D1Bitmap1* m_pD2DTargetBitmap = NULL;
    
    ID2D1SolidColorBrush* m_pD2DBrushBlack = NULL;
    ID2D1SolidColorBrush* m_pD2DBrushWhite = NULL;
    ID2D1SolidColorBrush* m_pD2DBrushBlue = NULL;
    ID2D1SolidColorBrush* m_pD2DBrushGreen = NULL;
    
    IDCompositionDevice* m_pDCompositionDevice = NULL;
    IDCompositionTarget* m_pDCompositionTarget = NULL;
    
    template <class T> void SafeRelease(T** ppT)
    {
    	if (*ppT)
    	{
    		(*ppT)->Release();
    		*ppT = NULL;
    	}
    }
    
    HRESULT CreateD2D1Factory();
    HRESULT CreateD3D11Device();
    HRESULT CreateDeviceResources();
    HRESULT CreateSwapChain(HWND hWnd);
    HRESULT ConfigureSwapChain(HWND hWnd);
    HRESULT CreateDirectComposition(HWND hWnd);
    void OnResize(HWND hWnd, UINT nWidth, UINT nHeight);
    void Clean();
    
    int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
    {
    	hInst = hInstance;
    	WNDCLASSEX wcex =
    	{
    		sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW, WndProc, 0, 0, hInst, LoadIcon(NULL, IDI_APPLICATION),
    		LoadCursor(NULL, IDC_ARROW), (HBRUSH)(COLOR_WINDOW + 1), NULL, TEXT("WindowClass"), NULL,
    	};
    	if (!RegisterClassEx(&wcex))
    		return MessageBox(NULL, TEXT("Cannot register class !"), TEXT("Error"), MB_ICONERROR | MB_OK);
    	int nX = (GetSystemMetrics(SM_CXSCREEN) - nWidth) / 2, nY = (GetSystemMetrics(SM_CYSCREEN) - nHeight) / 2;
    	HWND hWnd = CreateWindowEx(WS_EX_NOREDIRECTIONBITMAP, wcex.lpszClassName, TEXT("Test"), WS_OVERLAPPEDWINDOW, nX, nY, nWidth, nHeight, NULL, NULL, hInst, NULL);
    	if (!hWnd)
    		return MessageBox(NULL, TEXT("Cannot create window !"), TEXT("Error"), MB_ICONERROR | MB_OK);
    	ShowWindow(hWnd, SW_SHOWNORMAL);
    	UpdateWindow(hWnd);
    	MSG msg;
    	while (GetMessage(&msg, NULL, 0, 0))
    	{
    		TranslateMessage(&msg);
    		DispatchMessage(&msg);
    	}
    	return (int)msg.wParam;
    }
    
    LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
    	int wmId, wmEvent;
    	switch (message)
    	{
    	case WM_CREATE:
    	{
    		HRESULT hr = CoInitialize(NULL);
    		if (SUCCEEDED(hr))
    		{
    			hr = CreateD2D1Factory();
    			if (SUCCEEDED(hr))
    			{
    				hr = CreateD3D11Device();
    				hr = CreateDeviceResources();
    				hr = CreateSwapChain(NULL);
    				if (SUCCEEDED(hr))
    				{
    					hr = ConfigureSwapChain(hWnd);
    					hr = CreateDirectComposition(hWnd);
    				}
    			}
    		}
    		return 0;
    	}
    	break;
    	case WM_RBUTTONDOWN:
    	{
    		HRESULT hr = S_OK;
    		if (m_pD2DDeviceContext3 && m_pDXGISwapChain1)
    		{
    			m_pD2DDeviceContext3->BeginDraw();
    			D2D1_SIZE_F size = m_pD2DDeviceContext3->GetSize();
    			//m_pD2DDeviceContext3->Clear(D2D1::ColorF(D2D1::ColorF::Red, 0.5f));
    			m_pD2DDeviceContext3->FillRectangle(D2D1::RectF(0.f, 0.f, size.width/2, size.height), m_pD2DBrushBlack);
    			m_pD2DDeviceContext3->FillRectangle(D2D1::RectF(size.width / 2, 0.f, size.width, size.height), m_pD2DBrushWhite);
    			m_pD2DDeviceContext3->FillRectangle(D2D1::RectF(0.f, 0.f, size.width / 6, size.height/3), m_pD2DBrushBlue);
    			m_pD2DDeviceContext3->FillRectangle(D2D1::RectF(size.width - size.width / 6, size.height - size.height / 3, size.width, size.height), m_pD2DBrushGreen);
    			hr = m_pD2DDeviceContext3->EndDraw();
    			hr = m_pDXGISwapChain1->Present(1, 0);
    		}
    	}
    	break;	
    	/*case WM_PAINT:
    	{
    		PAINTSTRUCT ps;
    		HDC hDC = BeginPaint(hWnd, &ps);
    
    		EndPaint(hWnd, &ps);
    	}
    	break;*/
    	case WM_SIZE:
    	{
    		UINT nWidth = LOWORD(lParam);
    		UINT nHeight = HIWORD(lParam);
    		OnResize(hWnd, nWidth, nHeight);
    		return 0;
    	}	
    	break;
    	case WM_DESTROY:
    	{
    		Clean();
    		CoUninitialize();
    		PostQuitMessage(0);
    		return 0;
    	}
    	break;
    	default:
    		return DefWindowProc(hWnd, message, wParam, lParam);
    	}
    	return 0;
    }
    
    HRESULT CreateD2D1Factory()
    {
    	HRESULT hr = S_OK;
    	D2D1_FACTORY_OPTIONS options;
    	ZeroMemory(&options, sizeof(D2D1_FACTORY_OPTIONS));
    	options.debugLevel = D2D1_DEBUG_LEVEL::D2D1_DEBUG_LEVEL_INFORMATION;
    	D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof(ID2D1Factory1), &options, (void**)&m_pD2DFactory1);
    	return hr;
    }
    
    HRESULT CreateSwapChain(HWND hWnd)
    {
    	HRESULT hr = S_OK;
    	DXGI_SWAP_CHAIN_DESC1 swapChainDesc = { 0 };
    	swapChainDesc.Width = 1;
    	swapChainDesc.Height = 1;
    	swapChainDesc.Format = DXGI_FORMAT::DXGI_FORMAT_B8G8R8A8_UNORM;
    	swapChainDesc.Stereo = false;
    	swapChainDesc.SampleDesc.Count = 1; // don't use multi-sampling
    	swapChainDesc.SampleDesc.Quality = 0;
    	swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    	swapChainDesc.BufferCount = 2; // use double buffering to enable flip
    	swapChainDesc.Scaling = (hWnd != NULL) ? DXGI_SCALING::DXGI_SCALING_NONE : DXGI_SCALING::DXGI_SCALING_STRETCH;
    	swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT::DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
    	swapChainDesc.Flags = 0;
    	swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
    	IDXGIAdapter* pDXGIAdapter = nullptr;
    	hr = m_pDXGIDevice->GetAdapter(&pDXGIAdapter);
    	if (SUCCEEDED(hr))
    	{
    		IDXGIFactory2* pDXGIFactory2 = nullptr;
    		hr = pDXGIAdapter->GetParent(IID_PPV_ARGS(&pDXGIFactory2));
    		if (SUCCEEDED(hr))
    		{
    			if (hWnd != NULL)
    			{
    				hr = pDXGIFactory2->CreateSwapChainForHwnd(m_pD3D11Device, hWnd, &swapChainDesc, nullptr, nullptr, &m_pDXGISwapChain1);
    			}
    			else
    			{
    				hr = pDXGIFactory2->CreateSwapChainForComposition(m_pD3D11Device, &swapChainDesc, nullptr, &m_pDXGISwapChain1);
    			}
    			if (SUCCEEDED(hr))
    				hr = m_pDXGIDevice->SetMaximumFrameLatency(1);
    			SafeRelease(&pDXGIFactory2);
    		}
    		SafeRelease(&pDXGIAdapter);
    	}
    	return hr;
    }
    
    HRESULT ConfigureSwapChain(HWND hWnd)
    {
    	HRESULT hr = S_OK;
    
    	D2D1_BITMAP_PROPERTIES1 bitmapProperties = D2D1::BitmapProperties1(
    		D2D1_BITMAP_OPTIONS::D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS::D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
    		D2D1::PixelFormat(DXGI_FORMAT::DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE::D2D1_ALPHA_MODE_PREMULTIPLIED),
    		0,
    		0,
    		NULL
    	);
    	unsigned int nDPI = GetDpiForWindow(hWnd);
    	bitmapProperties.dpiX = nDPI;
    	bitmapProperties.dpiY = nDPI;
    
    	IDXGISurface* pDXGISurface;
    	if (m_pDXGISwapChain1)
    	{
    		hr = m_pDXGISwapChain1->GetBuffer(0, IID_PPV_ARGS(&pDXGISurface));
    		if (SUCCEEDED(hr))
    		{
    			hr = m_pD2DDeviceContext3->CreateBitmapFromDxgiSurface(pDXGISurface, bitmapProperties, &m_pD2DTargetBitmap);
    			if (SUCCEEDED(hr))
    			{
    				m_pD2DDeviceContext3->SetTarget(m_pD2DTargetBitmap);
    			}
    			SafeRelease(&pDXGISurface);
    		}
    	}
    	return hr;
    }
    
    // https://docs.microsoft.com/en-us/windows/win32/direct2d/devices-and-device-contexts
    HRESULT CreateD3D11Device()
    {
    	HRESULT hr = S_OK;
    	UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
    	creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
    
    	// This array defines the set of DirectX hardware feature levels this app  supports.
    	// The ordering is important and you should  preserve it.
    	// Don't forget to declare your app's minimum required feature level in its
    	// description.  All apps are assumed to support 9.1 unless otherwise stated.
    	D3D_FEATURE_LEVEL featureLevels[] =
    	{
    		D3D_FEATURE_LEVEL_11_1,
    		D3D_FEATURE_LEVEL_11_0,
    		D3D_FEATURE_LEVEL_10_1,
    		D3D_FEATURE_LEVEL_10_0,
    		D3D_FEATURE_LEVEL_9_3,
    		D3D_FEATURE_LEVEL_9_2,
    		D3D_FEATURE_LEVEL_9_1
    	};
    	D3D_FEATURE_LEVEL featureLevel;
    	hr = D3D11CreateDevice(
    		nullptr,                    // specify null to use the default adapter
    		D3D_DRIVER_TYPE_HARDWARE,
    		0,
    		creationFlags,              // optionally set debug and Direct2D compatibility flags
    		featureLevels,              // list of feature levels this app can support
    		ARRAYSIZE(featureLevels),   // number of possible feature levels
    		D3D11_SDK_VERSION,
    		&m_pD3D11Device,                    // returns the Direct3D device created
    		&featureLevel,            // returns feature level of device created
    		&m_pD3D11DeviceContext                    // returns the device immediate context
    	);
    	if (SUCCEEDED(hr))
    	{
    		// Obtain the underlying DXGI device of the Direct3D11 device.
    		hr = m_pD3D11Device->QueryInterface((IDXGIDevice1**)&m_pDXGIDevice);
    		if (SUCCEEDED(hr))
    		{
    			// Obtain the Direct2D device for 2-D rendering.
    			hr = m_pD2DFactory1->CreateDevice(m_pDXGIDevice, &m_pD2DDevice);
    			if (SUCCEEDED(hr))
    			{
    				// Get Direct2D device's corresponding device context object.
    				ID2D1DeviceContext* pD2DDeviceContext = NULL;
    				hr = m_pD2DDevice->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, &pD2DDeviceContext);
    				if (SUCCEEDED(hr))
    					hr = pD2DDeviceContext->QueryInterface((ID2D1DeviceContext3**)&m_pD2DDeviceContext3);
    				SafeRelease(&pD2DDeviceContext);
    			}
    		}
    	}
    	return hr;
    }
    
    HRESULT CreateDeviceResources()
    {
    	HRESULT hr = S_OK;
    	if (m_pD2DDeviceContext3)
    	{
    		hr = m_pD2DDeviceContext3->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black, 0.5f), &m_pD2DBrushBlack);	
    		hr = m_pD2DDeviceContext3->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White, 0.5f), &m_pD2DBrushWhite);
    		hr = m_pD2DDeviceContext3->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Blue, 1.0f), &m_pD2DBrushBlue);
    		hr = m_pD2DDeviceContext3->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Green, 1.0f), &m_pD2DBrushGreen);
    	}
    	return hr;
    }
    
    HRESULT CreateDirectComposition(HWND hWnd)
    {
    	HRESULT hr = S_OK;	
    	hr = DCompositionCreateDevice(m_pDXGIDevice, __uuidof(m_pDCompositionDevice),	(void**)(&m_pDCompositionDevice));
    	if (SUCCEEDED(hr))
    	{	
    		hr = m_pDCompositionDevice->CreateTargetForHwnd(hWnd, true, &m_pDCompositionTarget);
    		if (SUCCEEDED(hr))
    		{
    			IDCompositionVisual* pDCompositionVisual = NULL;
    			hr = m_pDCompositionDevice->CreateVisual(&pDCompositionVisual);
    			if (SUCCEEDED(hr))
    			{
    				hr = pDCompositionVisual->SetContent(m_pDXGISwapChain1);
    				hr = m_pDCompositionTarget->SetRoot(pDCompositionVisual);
    				hr = m_pDCompositionDevice->Commit();
    				SafeRelease(&pDCompositionVisual);
    			}
    		}
    	}
    	return hr;
    }
    
    void OnResize(HWND hWnd, UINT nWidth, UINT nHeight)
    {
    	if (m_pDXGISwapChain1)
    	{
    		HRESULT hr = S_OK;		
    		if (nWidth != 0 && nHeight != 0)
    		{
    			m_pD2DDeviceContext3->SetTarget(nullptr);
    			SafeRelease(&m_pD2DTargetBitmap);
    			hr = m_pDXGISwapChain1->ResizeBuffers(
    				2, // Double-buffered swap chain.
    				nWidth,
    				nHeight,
    				DXGI_FORMAT_B8G8R8A8_UNORM,
    				0
    			);
    			if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
    			{
    				CreateD3D11Device();
    				CreateSwapChain(NULL);
    				return;
    			}
    			else
    			{
    				//DX::ThrowIfFailed(hr);
    			}
    			ConfigureSwapChain(hWnd);
    		}
    	}
    }
    
    void CleanDeviceResources()
    {
    	SafeRelease(&m_pD2DBrushBlack);
    	SafeRelease(&m_pD2DBrushWhite);	
    	SafeRelease(&m_pD2DBrushBlue);
    	SafeRelease(&m_pD2DBrushGreen);	
    }
    
    void Clean()
    {
    	SafeRelease(&m_pD2DDevice);
    	SafeRelease(&m_pD2DDeviceContext3);
    	SafeRelease(&m_pD2DTargetBitmap);
    	CleanDeviceResources();
    	SafeRelease(&m_pDXGISwapChain1);
    	SafeRelease(&m_pDXGIDevice);
    	SafeRelease(&m_pD3D11Device);
    	SafeRelease(&m_pD3D11DeviceContext);
    	SafeRelease(&m_pD2DFactory1);
    
    	SafeRelease(&m_pDCompositionDevice);
    	SafeRelease(&m_pDCompositionTarget);	
    }
    
    1 person found this answer helpful.

2 additional answers

Sort by: Most helpful
  1. Pedro Gil 5 Reputation points
    2023-03-19T07:57:58.5633333+00:00

    Yes, that's how I create the window and I can fade in and out with SetLareyedWindowAttributes.

    My questions was if there is another way to do it. I wonder if using SetlayeredWindowAttributes within a for... next is the best way to do it... and I'm not sure if some friends have been able to achieve that effect with Direct2D or GDI+.

    Thank you very much for your help and patience.

    0 comments No comments

  2. Pedro Gil 5 Reputation points
    2023-04-08T14:50:08.8533333+00:00

    Hi @Castorix31 Do you have any comment / suggestion about how to make the same effect as using SetLayeredWindowAttributes within a for next, but with Direct2D? Is it possible? Thanks once again.