Windows核心编程_窗口透明组件不透明

经过前几篇对界面编程的学习,已经对Windows窗口消息有了更加深刻的理解,今天就教大家写一个窗口透明而组件却不透明的小示列!

这个demo并不难,而且还非常的简单,如果你看过我的前几篇针对界面编程写的文件,相信你可以很轻松的写出来,主要实现思路就是,两个窗口,一个窗口用于做父窗口,全局透明化,第二个窗口用于做分层窗口,显示组件,只针对特定的窗口颜色透明化!

好话不多说,立马进入编码:

一. 创建Win32窗口

#include "stdafx.h"
#include <windows.h>

HWND hWnd;	//progman

//消息函数
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	//判断消息ID
	switch (uMsg){

	case WM_DESTROY:    // 窗口销毁消息
		PostQuitMessage(0);   //  发送退出消息
		return 0;
	}
	// 其他的消息调用缺省的消息处理程序
	return DefWindowProc(hwnd, uMsg, wParam, lParam);

}
// 3、注册窗口类型
BOOL RegisterWindow(LPCSTR lpcWndName, HINSTANCE hInstance)
{
	ATOM nAtom = 0;
	// 构造创建窗口参数
	WNDCLASS wndClass = { 0 };
	wndClass.style = CS_HREDRAW | CS_VREDRAW;
	wndClass.lpfnWndProc = WindowProc;      // 指向窗口过程函数
	wndClass.cbClsExtra = 0;
	wndClass.cbWndExtra = 0;
	wndClass.hInstance = hInstance;
	wndClass.hIcon = NULL;
	wndClass.hCursor = NULL;
	wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wndClass.lpszMenuName = NULL;
	wndClass.lpszClassName = lpcWndName;    // 注册的窗口名称,并非标题,以后创建窗口根据此注册的名称创建
	nAtom = RegisterClass(&wndClass);
	return TRUE;
}
//创建窗口(lpClassName 一定是已经注册过的窗口类型)
HWND CreateMyWindow(LPCTSTR lpClassName, HINSTANCE hInstance)
{
	HWND hWnd = NULL;
	// 创建窗口
	hWnd = CreateWindow(lpClassName, "test", WS_OVERLAPPEDWINDOW^WS_THICKFRAME, 0, 0, 1000, 800, NULL, NULL, hInstance, NULL);
	return hWnd;
}
//显示窗口
void DisplayMyWnd(HWND hWnd)
{
	//获得屏幕尺寸

	int scrWidth = GetSystemMetrics(SM_CXSCREEN);
	int scrHeight = GetSystemMetrics(SM_CYSCREEN);
	RECT rect;
	GetWindowRect(hWnd, &rect);
	ShowWindow(hWnd, SW_SHOW);
	//重新设置rect里的值
	rect.left = (scrWidth - rect.right) / 2;
	rect.top = (scrHeight - rect.bottom) / 2;
	//移动窗口到指定的位置
	SetWindowPos(hWnd, HWND_TOP, rect.left, rect.top, rect.right, rect.bottom, SWP_SHOWWINDOW);
	UpdateWindow(hWnd);
}

void doMessage()        // 消息循环处理函数
{
	MSG msg = { 0 };
	// 获取消息
	while (GetMessage(&msg, NULL, 0, 0)) // 当接收到WM_QIUT消息时,GetMessage函数返回0,结束循环
	{
		DispatchMessage(&msg); // 派发消息,到WindowPro函数处理
	}
}

// 入口函数
int WINAPI WinMain(HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPSTR lpCmdLine,
	int nShowCmd)
{
	LPCTSTR lpClassName = "MyWnd";  // 注册窗口的名称
	RegisterWindow(lpClassName, hInstance);
	hWnd = CreateMyWindow(lpClassName, hInstance);
	DisplayMyWnd(hWnd);
	doMessage();
	return 0;
}

运行结果:

那么我们要设置窗口的透明风格,只需要在创建的时候,使用CreateWindowEx来创建具有LAYERED扩展风格的窗口即可:

// 创建窗口

hWnd = CreateWindowEx(WS_EX_LAYERED,lpClassName, "test", WS_OVERLAPPEDWINDOW^WS_THICKFRAME, 0, 0, 1000, 800, NULL, NULL, hInstance, NULL);

//WS_EX_LAYERED风格会继承WM_PAINT,所以我们要用SetLayeredWindowAttributes来取消继承否则会造成窗口无法显示

SetLayeredWindowAttributes(hWnd, RGB(0, 0, 0), 155, LWA_ALPHA);//全局透明

运行结果:

那么我们给这个窗口绘制一张背景图,并且设置无边框样式:

hWnd = CreateWindowEx(WS_EX_LAYERED,lpClassName, NULL, WS_POPUP, 0, 0, 1000, 800, NULL, NULL, hInstance, NULL);
	SetLayeredWindowAttributes(hWnd, RGB(0, 0, 0), 155, LWA_ALPHA);

消息循环函数:

case WM_PAINT:
		PAINTSTRUCT ps;
		HDC hdc;
		HBITMAP hbmp;// 位图绘制对象句柄,模糊图像
		HDC mdc;
		hdc = BeginPaint(hwnd, &ps);
		mdc = CreateCompatibleDC(hdc); // 创建兼容的缓存DC对象
		//加载位图
		hbmp = (HBITMAP)LoadImage(
			NULL, // 模块实例句柄
			"C:\\Users\\ZZH\\Desktop\\D.bmp", // 位图路径。注意双斜杠,单斜杠表示转义,此时文件会加载不成功!!!
			IMAGE_BITMAP, // 图片类型
			1000,
			800,
			LR_LOADFROMFILE // 从路径处加载图片
			);
		// 缓存DC(mdc)选择位图绘制对象(可以理解为将图片保存到mdc中)
		SelectObject(mdc, hbmp);
		BitBlt(
			hdc, // 目的DC
			0,0,
			1000, // 目的DC的 x,y 坐标
			800,
			mdc, // 缓存DC
			0, 0, // 缓存DC的x,y坐标
			SRCCOPY // 粘贴方式
			);
		DeleteObject(hbmp);
		DeleteDC(mdc);
		EndPaint(hwnd, &ps);
		break;

运行结果:

图片UI:

那么父窗口基本已经写完了,我们还需要一个子窗口,也就是分层窗口,分层窗口的主要思路是:在窗体DC上绘制黑色背景,然后在将其去除掉,注意别忘记将分层窗口的大小位置与父窗口一致!

//创建分层窗口
	HWND c_hWnd = NULL;
	c_hWnd = CreateWindowEx(WS_EX_LAYERED, lpClassName, hWnd, WS_POPUP, 0, 0, 1000, 800, NULL, NULL, hInstance, NULL);
	SetLayeredWindowAttributes(c_hWnd, RGB(0, 0, 0), 155, LWA_COLORKEY);	//局部透明

这里窗口类是同一个,所以消息循环函数也是同一个,这里我们要用子类化的方式来new一个新的消息循环函数,不然会导致子窗口PAINT和父窗口一样!

子类化函数:

long OldWindowProc;	//旧的处理函数地址
WNDPROC lpfnOldProc = 0;	//设置后的32位风格值
LRESULT NewWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

{

	if (message == WM_PAINT)

	{

		
		return 0;	//不要交给父类PAINT



	}

	return CallWindowProc(lpfnOldProc, hWnd, message, wParam, lParam);//交给旧的消息处理函数

}

子类化:

OldWindowProc = GetWindowLong(c_hWnd, GWL_WNDPROC/* -4 */);	//保存原窗口消息处理函数地址
	lpfnOldProc = (WNDPROC)SetWindowLong(c_hWnd, GWL_WNDPROC, (DWORD)NewWndProc);	//设置新的消息处理函数
在子类化里绘制黑色背景:
if (message == WM_PAINT)

	{

		//绘制矩形填充窗口背景
		HDC hdc;
		hdc = GetDC(hWnd);
		HBRUSH hb;
		RECT rect;
		GetClientRect(hWnd, &rect);	//对于客户区左上角
		hb = CreateSolidBrush(RGB(0, 0, 0));//黑色画刷
		FillRect(hdc, &rect, hb);
		DeleteDC(hdc);
		DeleteObject(hb);
		UpdateWindow(hWnd);
		return 0;	//不要交给父类PAINT



	}
最后在分层窗口上创建一个BUTTON按钮试试:
//创建按钮
	CreateWindow("Button", "遮罩窗口", WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON
		, 50, 100, 230, 30, c_hWnd, NULL, NULL, NULL);

运行结果:

会发现按钮并没有显示出来,很奇怪,原因在于子类化的WM_PAINT消息里:

return 0;

这里不应该是return 0,应该是:

return DefWindowProc(hWnd, message, wParam, lParam);

DefWindowProc函数是系统的默认缺省处理函数,用于绘制窗口的,我们的控件,以及其它视窗,都是此函数来调用的,当窗口产生绘图消息的时候,那么窗口上的界面都需要重绘,而每个控件都有自己的消息队列,以及对应的处理函数包括绘图,如果这些控件没有提供相应接口,我们无法对其进行操作,默认Windows提供的控件,是不提供接口的,我们可以使用SendMessage来发送消息让其主动调用绘图函数,但是有优先级的原因,所以我们可以使用DefWindowProc,来显示的调用控件的消息处理函数,而不是发送到消息队列里!

if (message == WM_PAINT)

	{

		//绘制矩形填充窗口背景
		HDC hdc;
		hdc = GetDC(hWnd);
		HBRUSH hb;
		RECT rect;
		GetClientRect(hWnd, &rect);	//对于客户区左上角
		hb = CreateSolidBrush(RGB(0, 0, 0));//黑色画刷
		FillRect(hdc, &rect, hb);
		DeleteDC(hdc);
		DeleteObject(hb);
		UpdateWindow(hWnd);
		return DefWindowProc(hWnd, message, wParam, lParam);	//调用控件重绘



	}

运行结果:

会发现按钮的字也被透明化了,这是为什么?明明透明化的是窗口和控件有什么关系?

答:

控件的字是黑色的,但是控件是存活在窗口上的,所以控件的DC可以属于窗口的子DC,就相当于窗口属于屏幕的子DC一样,当控件绘制在窗口的某块区域的时候,那么窗口的这块区域DC=这个控件的DC,Windows只是把子控件绘制在了这块区域,但是窗口这块区域的原始DC数据还是不变的!只是起到了一个遮挡效果,而SetLayeredWindowAttributes是将窗口指定颜色去除,而控件又属于此窗口,所以SetLayeredWindowAttributes连同窗口上的所有子DC全部去除了!

我们只需要更改一下去除颜色以及背景色即可:

RGB(155, 123, 144)

运行结果:

这里在说一下:

SetLayeredWindowAttributes在使用LWA_COLORKEY来去除指定窗口屏幕色时,它的第三个参数也就是透明度是无效的,只有在LWA_ALPHA全局透明时有效!

完整代码:

#include "stdafx.h"
#include <windows.h>

HWND hWnd;	//progman

//消息函数
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	//判断消息ID
	switch (uMsg){
	case WM_PAINT:
		PAINTSTRUCT ps;
		HDC hdc;
		HBITMAP hbmp;// 位图绘制对象句柄,模糊图像
		HDC mdc;
		hdc = BeginPaint(hwnd, &ps);
		mdc = CreateCompatibleDC(hdc); // 创建兼容的缓存DC对象
		//加载位图
		hbmp = (HBITMAP)LoadImage(
			NULL, // 模块实例句柄
			"C:\\Users\\ZZH\\Desktop\\D.bmp", // 位图路径。注意双斜杠,单斜杠表示转义,此时文件会加载不成功!!!
			IMAGE_BITMAP, // 图片类型
			1000,
			800,
			LR_LOADFROMFILE // 从路径处加载图片
			);
		// 缓存DC(mdc)选择位图绘制对象(可以理解为将图片保存到mdc中)
		SelectObject(mdc, hbmp);
		BitBlt(
			hdc, // 目的DC
			0,0,
			1000, // 目的DC的 x,y 坐标
			800,
			mdc, // 缓存DC
			0, 0, // 缓存DC的x,y坐标
			SRCCOPY // 粘贴方式
			);
		DeleteObject(hbmp);
		DeleteDC(mdc);
		EndPaint(hwnd, &ps);
		break;
	case WM_DESTROY:    // 窗口销毁消息
		PostQuitMessage(0);   //  发送退出消息
		return 0;
	}
	// 其他的消息调用缺省的消息处理程序
	return DefWindowProc(hwnd, uMsg, wParam, lParam);

}
// 3、注册窗口类型
BOOL RegisterWindow(LPCSTR lpcWndName, HINSTANCE hInstance)
{
	ATOM nAtom = 0;
	// 构造创建窗口参数
	WNDCLASS wndClass = { 0 };
	wndClass.style = CS_HREDRAW | CS_VREDRAW;
	wndClass.lpfnWndProc = WindowProc;      // 指向窗口过程函数
	wndClass.cbClsExtra = 0;
	wndClass.cbWndExtra = 0;
	wndClass.hInstance = hInstance;
	wndClass.hIcon = NULL;
	wndClass.hCursor = NULL;
	wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wndClass.lpszMenuName = NULL;
	wndClass.lpszClassName = lpcWndName;    // 注册的窗口名称,并非标题,以后创建窗口根据此注册的名称创建
	nAtom = RegisterClass(&wndClass);
	return TRUE;
}
//创建窗口(lpClassName 一定是已经注册过的窗口类型)
HWND CreateMyWindow(LPCTSTR lpClassName, HINSTANCE hInstance)
{
	HWND hWnd = NULL;
	// 创建窗口
	hWnd = CreateWindowEx(WS_EX_LAYERED,lpClassName, NULL, WS_POPUP, 0, 0, 1000, 800, NULL, NULL, hInstance, NULL);
	//WS_EX_LAYERED风格会继承WM_PAINT,所以我们要用SetLayeredWindowAttributes来取消继承否则会造成窗口无法显示
	SetLayeredWindowAttributes(hWnd, RGB(0, 0, 0), 155, LWA_ALPHA);//全局透明
	return hWnd;
}
//显示窗口
void DisplayMyWnd(HWND hWnd)
{
	//获得屏幕尺寸

	int scrWidth = GetSystemMetrics(SM_CXSCREEN);
	int scrHeight = GetSystemMetrics(SM_CYSCREEN);
	RECT rect;
	GetWindowRect(hWnd, &rect);
	ShowWindow(hWnd, SW_SHOW);
	//重新设置rect里的值
	rect.left = (scrWidth - rect.right) / 2;
	rect.top = (scrHeight - rect.bottom) / 2;
	//移动窗口到指定的位置
	SetWindowPos(hWnd, HWND_TOP, rect.left, rect.top, rect.right, rect.bottom, SWP_SHOWWINDOW);
	UpdateWindow(hWnd);
}

void doMessage()        // 消息循环处理函数
{
	MSG msg = { 0 };
	// 获取消息
	while (GetMessage(&msg, NULL, 0, 0)) // 当接收到WM_QIUT消息时,GetMessage函数返回0,结束循环
	{
		DispatchMessage(&msg); // 派发消息,到WindowPro函数处理
	}
}
long OldWindowProc;	//旧的处理函数地址
WNDPROC lpfnOldProc = 0;	//设置后的32位风格值
LRESULT NewWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

{

	if (message == WM_PAINT)

	{

		//绘制矩形填充窗口背景
		HDC hdc;
		hdc = GetDC(hWnd);
		HBRUSH hb;
		RECT rect;
		GetClientRect(hWnd, &rect);	//对于客户区左上角
		hb = CreateSolidBrush(RGB(155, 123, 144));
		FillRect(hdc, &rect, hb);
		DeleteDC(hdc);
		DeleteObject(hb);
		UpdateWindow(hWnd);
		return DefWindowProc(hWnd, message, wParam, lParam);//调用控件重绘




	}

	return CallWindowProc(lpfnOldProc, hWnd, message, wParam, lParam);//交给旧的消息处理函数

}

// 入口函数
int WINAPI WinMain(HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPSTR lpCmdLine,
	int nShowCmd)
{
	LPCTSTR lpClassName = "MyWnd";  // 注册窗口的名称
	RegisterWindow(lpClassName, hInstance);
	hWnd = CreateMyWindow(lpClassName, hInstance);
	//创建分层窗口
	HWND c_hWnd = NULL;
	c_hWnd = CreateWindowEx(WS_EX_LAYERED, lpClassName, NULL, WS_POPUP, 0, 0, 1000, 800, hWnd, NULL, hInstance, NULL);
	//创建按钮
	CreateWindow("Button", "遮罩窗口", WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON
		, 50, 100, 230, 30, c_hWnd, NULL, NULL, NULL);
	SetLayeredWindowAttributes(c_hWnd, RGB(155, 123, 144), 155, LWA_COLORKEY);	//局部透明
	//子类化
	OldWindowProc = GetWindowLong(c_hWnd, GWL_WNDPROC/* -4 */);	//保存原窗口消息处理函数地址
	lpfnOldProc = (WNDPROC)SetWindowLong(c_hWnd, GWL_WNDPROC, (DWORD)NewWndProc);	//设置新的消息处理函数
	DisplayMyWnd(hWnd);
	DisplayMyWnd(c_hWnd);
	doMessage();
	return 0;
}

猜你喜欢

转载自blog.csdn.net/bjbz_cxy/article/details/81119772