哈喽,好久不见!今天要分享的内容是qt编写的窗口在windows系统下如何完美实现窗口边框阴影。
qt虽然自带阴影类QGraphicsDropShadowEffect,但是它的原理是需要两层widget,上面一层显示窗口,下面一层作为阴影。但是,如果产品经理反复横跳,刚开始不要阴影,后来又提出需求了,怎么办?这时你的窗口布局什么的全都弄好了,又不好修改。如何做到想加就加,想不要就不要呢?请往下看。
我们这里是使用的windows api做的,所以做全平台的就不能帮助啦。
首先,我们使用windows的dwmapi创建一个阴影类。
//*******************************************************h*******************************************
#ifndef WINDWMAPI_H
#define WINDWMAPI_H
#include <windows.h>
typedef struct _MARGINS
{
int cxLeftWidth; // width of left border that retains its size
int cxRightWidth; // width of right border that retains its size
int cyTopHeight; // height of top border that retains its size
int cyBottomHeight; // height of bottom border that retains its size
} MARGINS, *PMARGINS;
class WinDwmapi
{
public:
WinDwmapi();
~WinDwmapi();
typedef HRESULT (WINAPI* DwmIsCompositionEnabledPtr)(BOOL* pfEnabled);
typedef HRESULT (WINAPI* DwmExtendFrameIntoClientAreaPtr)(HWND hWnd, const MARGINS *pMarInset);
HRESULT DwmIsCompositionEnabled(BOOL* pfEnabled) const;
HRESULT DwmExtendFrameIntoClientArea(HWND hWnd, const MARGINS *pMarInset) const;
static const WinDwmapi* instance();
private:
DwmIsCompositionEnabledPtr dwm_is_composition_enabled_;
DwmExtendFrameIntoClientAreaPtr dwm_extendframe_into_client_area_;
HMODULE dwmapi_dll_;
};
#endif // WINDWMAPI_H
//********************************************************cpp***************************************************
#include "windwmapi.h"
WinDwmapi::WinDwmapi()
: dwmapi_dll_(LoadLibraryW(L"dwmapi.dll"))
, dwm_is_composition_enabled_(NULL)
{
if (dwmapi_dll_)
{
dwm_is_composition_enabled_ = reinterpret_cast<DwmIsCompositionEnabledPtr>(GetProcAddress(dwmapi_dll_, "DwmIsCompositionEnabled"));
dwm_extendframe_into_client_area_ = reinterpret_cast<DwmExtendFrameIntoClientAreaPtr>(GetProcAddress(dwmapi_dll_, "DwmExtendFrameIntoClientArea"));
}
}
WinDwmapi::~WinDwmapi()
{
if (dwmapi_dll_)
{
FreeLibrary(dwmapi_dll_);
}
}
HRESULT WinDwmapi::DwmIsCompositionEnabled(BOOL *pfEnabled) const
{
if (dwm_is_composition_enabled_)
{
return dwm_is_composition_enabled_(pfEnabled);
}
return E_NOTIMPL;
}
HRESULT WinDwmapi::DwmExtendFrameIntoClientArea(HWND hWnd, const MARGINS *pMarInset) const
{
if (dwm_extendframe_into_client_area_)
{
return dwm_extendframe_into_client_area_(hWnd, pMarInset);
}
return E_NOTIMPL;
}
const WinDwmapi *WinDwmapi::instance()
{
static const WinDwmapi s_dwmapi;
return &s_dwmapi;
}
接下来使用的时候,只需要在窗口的构造函数里面调用一下就可以了
HWND hwnd = (HWND)this->winId();
DWORD style = ::GetWindowLong(hwnd, GWL_STYLE);
::SetWindowLong(hwnd, GWL_STYLE, style | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_CAPTION);
const MARGINS shadow = { 1, 1, 1, 1 }; //边框保留1px以绘制阴影
WinDwmapi::instance()->DwmExtendFrameIntoClientArea(HWND(winId()), &shadow);
这里就已经可以出现边框阴影了,但是会带来三个个问题:1、它会使窗口边高大,所以在这后面你需要resize一次。
2、它会带来系统边框,需要在nativeEvent里面解决。3、在全屏的时候会使窗口超出屏幕,这里也在nativeEvent里面解决。解决问题2的思路是在nativeEvent里面截取WM_NCCALCSIZE信息,然后直接返回,使系统无法设置边框。解决3的思路是截取窗口状态变化信息,当为全屏时,在四周设置一定外边框,当取消全屏时就取消四周外边框。
下面就来解决问题2和3(重载nativeEvent)
bool CMainWdg::nativeEvent(const QByteArray &eventType, void *message, long *result)
{
MSG *msg = (MSG*)message;
//去除阴影带来的边框问题
if (msg->message == WM_NCCALCSIZE)
{
*result = 0;
return true;
}
//窗口显示时的大小的信号
if(msg->message == WM_GETMINMAXINFO)
{
if (::IsZoomed(msg->hwnd))
{
// 最大化时会超出屏幕,所以填充边框间距
RECT frame = { 0, 0, 0, 0 };
AdjustWindowRectEx(&frame, WS_OVERLAPPEDWINDOW, FALSE, 0);
frame.left = abs(frame.left);
frame.top = abs(frame.bottom);
m_top = frame.top;
m_vblMain->setContentsMargins(frame.left, frame.top, frame.right, frame.bottom);
m_hblTitle->setContentsMargins(frame.left, 0, frame.right, 0);
}
else
{
m_top = 0;
m_vblMain->setContentsMargins(1, 0, 1, 1); //这里是恢复我的原来边框设置,如果你没有可以不要
m_hblTitle->setContentsMargins(0, 0, 0, 0); //这里也是恢复原来的边框设置,如果你没有可以不要
}
//*result = ::DefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam);
//return true;
}
return QWidget::nativeEvent(eventType, message, result);
}