1.背景介绍
需要实现的功能如下:
- 采用windows api创建一个透明窗口
- 在窗口中显示透明背景的gif图片
2.遇到的问题
透明背景的gif已经能够正常显示了,但是在开始显示gif图片之前,有时会闪现窗口背景,或者闪现窗口边框,这是一个概率事件,有时显示正常,有时会闪现窗口边框或者背景色:
根据调试过程,从开始创建窗口到出现这个白色边框的过程:
- 创建一个带边框不透明的窗口;
- 去掉窗口的边框;
- 实现窗口透明;
- 出现这种边框不透明,客户区透明的情形;
- 开始显示第一帧gif,此时这个边框仍然存在;
- 大约在显示第二帧gif时这个边框就消失了,恢复正常;
3.尝试的思路
3.1 是不是注册窗口时的参数不对
WNDCLASSEX wcex = { 0 };
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = (WNDPROC)WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = NULL;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = CreateSolidBrush(RGB(251, 255, 242));
wcex.lpszMenuName = NULL;
// If forget to set this attribute, the program will throw an exception.
wcex.lpszClassName = kWindowClassName;
wcex.hIconSm = NULL;
// Register the window
::RegisterClassEx(&wcex);
非常仔细的比对了网上给出的注册窗口的方法,发现没有什么不一样,即使有一些不一样,按照网上的方法改动后显示效果没有任何改变。
3.2是不是创建的窗口风格不合适
创建窗口时选择的风格是 WS_OVERLAPPEDWINDOW ,这个值是多种风格的组合,尝试了只取其中部分值,显示效果没有任何改变。
3.3是不是窗口接收到的消息序列不一样
我想是不是正常情形和异常情形窗口接收的消息序列不一样,如果真出现这种情况,可能会考虑直接构造消息序列发给窗口过程。
在命令行窗口打印出正常显示时窗口过程函数接收到的消息序列,以及异常显示时窗口过程函数接收到的消息序列,试验了很多次发现在这两种情况下窗口过程接收到的消息序列一模一样。
3.4尝试改变代码组织结构
我的代码其中的各个小块是参考网上的,从来没有怀疑过这样组织顺序有问题,因为我看了网上相当多的windows api创建窗口的代码,基本雷同,既然大家都这么写,想必是没错了。
但是遇到前面提到的问题,真的想不到解决办法了,只能将窗口过程函数中的部分代码拿到窗口创建的函数中执行,首先拿出来的是:
case WM_SIZE:
{
LONG_PTR Style = ::GetWindowLongPtr(hWnd, GWL_STYLE);
Style = Style & ~WS_CAPTION &~WS_SYSMENU &~WS_SIZEBOX;
::SetWindowLongPtr(hWnd, GWL_STYLE, Style);
break;
}
这个代码块是实现去除窗口边框功能的,也是参考网上的代码写的。
改写办法是将其中三行代码放在创建窗口CreateWindow之后,去掉对WM_SIZE消息的响应。
这样改过之后,显示效果正常了,不会再出现白色背景或者白色边框了。尝试了那么多方法,终于有一个可行的了。
下面给出完整的代码:
int InstallMainWindow::Execute(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wcex = { 0 };
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = (WNDPROC)WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = NULL;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = CreateSolidBrush(RGB(251, 255, 242));
wcex.lpszMenuName = NULL;
// If forget to set this attribute, the program will throw an exception.
wcex.lpszClassName = kWindowClassName;
wcex.hIconSm = NULL;
// Register the window
::RegisterClassEx(&wcex);
int xLoc = ((GetSystemMetrics(SM_CXSCREEN) / 2) - kInstallInterfaceWidth / 2);
int yLoc = ((GetSystemMetrics(SM_CYSCREEN) / 2) - kInstallInterfaceHeight / 2);
DWORD main_window_style = WS_OVERLAPPEDWINDOW;
main_window_ = ::CreateWindow(kWindowClassName, L"", main_window_style, xLoc, yLoc,
kInstallInterfaceWidth, kInstallInterfaceHeight, NULL, NULL, hInstance, NULL);
LONG_PTR Style = ::GetWindowLongPtr(main_window_, GWL_STYLE);
Style = Style & ~WS_CAPTION &~WS_SYSMENU &~WS_SIZEBOX &~WS_OVERLAPPED &~WS_MINIMIZEBOX &~WS_MAXIMIZEBOX;
::SetWindowLongPtr(main_window_, GWL_STYLE, Style);
::ShowWindow(main_window_, nCmdShow);
::UpdateWindow(main_window_);
LONG ret = ::GetWindowLongPtr(main_window_, GWL_EXSTYLE);
ret = ret | WS_EX_LAYERED;
::SetWindowLongPtr(main_window_, GWL_EXSTYLE, ret);
MSG msg;
while (::GetMessage(&msg, 0, 0, 0))
{
::TranslateMessage(&msg); // Translate keyboard message
::DispatchMessage(&msg); // Dispatch message to the corresponding window
}
return (int)msg.wParam;
}
LRESULT CALLBACK InstallMainWindow::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HBRUSH hBrush;
switch (message)
{
case WM_PAINT:
{
::SetLayeredWindowAttributes(hWnd, RGB(251, 255, 242), 0, LWA_COLORKEY);
break;
}
case WM_DESTROY:
{
::PostQuitMessage(0);
break;
}
}
return ::DefWindowProc(hWnd, message, wParam, lParam);
}
4.小结
一开始想到的是将WM_SIZE消息响应代码糅合到CreateWindow的样式中,因为都是控制窗口样式的,这样貌似更加统一。遗憾的是这样窗口边框去不掉了,就以为这几行代码只能放在WM_SIZE消息的响应代码中。实在无计可施了,才被迫将这段代码移到窗口创建之后,没想到竟然可以了,高兴了好一会儿,连续运行了十几次,发现仍然概率性的闪现背景,只能继续寻找解决办法。