在QT中如果想要自绘标题和边框,一般步骤是:

  1) 在创建窗口前设置Qt::FramelessWindowHint标志,设置该标志后会创建一个无标题、无边框的窗口。

  2)在客户区域的顶部创建一个自绘标题栏。

  3)给窗口绘制一个背景作为边框。

  4)如果想要鼠标拖动效果,可以在WM_NCHITTEST消息中返回HTCAPTION,具体方法百度这里不再详述。

  但是这样做会导致一个问题:

    在win7系统上,将窗口移动到屏幕边缘会自动排列(在屏幕顶部,左边,右边都会自动排列)的功能失效。

  如果你的窗口没有这个功能,只有两种可能:

  1)你的窗口不支持"移动到屏幕边缘自动排列"功能。

  2)你从系统关闭了此项功能(控制面板\轻松访问\轻松访问中心\使任务更容易被关注\防止将窗口移动到屏幕边缘时自动排列窗口)。

怎么样才能够既能够自绘标题和边框,又能够使用屏幕自动排列功能:

  有一个windows消息能够帮助我们,响应WM_NCCALCSIZE消息,直接返回true,就可以使客户区域的大小和窗口大小完全一样,这样就没有了标题栏和边框,我们可以按照上面的一般步骤来自绘标题栏和边框,唯一不同的是不需要设置Qt::FramelessWindowHint标志。

  这样做也会有问题:

    窗口显示的不完整,特别是在最大化的时候,非常明显。

  为什么会显示不完整,这个问题困扰我一整天。我新建了一个win32项目,响应WM_NCCALCSIZE消息,窗口显示完整,应该是QT自己处理的问题,最后不断调试QT源码,终于明白问题所在:

  调用堆栈(从下往上看):

QWindowsWindow::frameMarginsDp() 行     C++
QWindowsWindow::frameMargins() 行 C++
QWidgetPrivate::updateFrameStrut() 行 C++
QWidget::create(unsigned int window, bool initializeWindow, bool destroyOldWindow) 行 C++

  关键函数:

QMargins QWindowsWindow::frameMarginsDp() const
{
// Frames are invalidated by style changes (window state, flags).
// As they are also required for geometry calculations in resize
// event sequences, introduce a dirty flag mechanism to be able
// to cache results.
if (testFlag(FrameDirty)) {
// Always skip calculating style-dependent margins for windows claimed to be frameless.
// This allows users to remove the margins by handling WM_NCCALCSIZE with WS_THICKFRAME set
// to ensure Areo snap still works (QTBUG-40578).
m_data.frame = window()->flags() & Qt::FramelessWindowHint
? QMargins(, , , )
: QWindowsGeometryHint::frame(style(), exStyle());
clearFlag(FrameDirty);
}
return m_data.frame + m_data.customMargins;
}

  注释里面清楚说明这是一个BUG(QTBUG-40578),我们虽然已经让客户区域大小和窗口大小完全一样,但是QT还是认为系统有边框,只有当设置了Qt::FramelessWindowHint标志,才会返回QMargins(0, 0, 0, 0)。

现在又回到了原点,且问题相互矛盾,想要自绘标题和边框必须设置Qt::FramelessWindowHint标志,但是设置Qt::FramelessWindowHint标志后"屏幕边缘自动排列"无效。

  首先要搞清楚Qt::FramelessWindowHint标志如何影响窗口,因为它直接导致"屏幕边缘自动排列"无效:

WindowCreationData::fromWindow(const QWindow * w, const QFlags<enum Qt::WindowType> flagsIn, unsigned int creationFlags) 行     C++
QWindowsWindowData::create(const QWindow * w, const QWindowsWindowData & parameters, const QString & title) 行 C++
QWindowsIntegration::createWindowData(QWindow * window) 行 C++
QWindowsIntegration::createPlatformWindow(QWindow * window) 行 C++
QWindowPrivate::create(bool recursive) 行 C++
QWindow::create() 行 C++
QWidgetPrivate::create_sys(unsigned int window, bool initializeWindow, bool destroyOldWindow) 行 C++
QWidget::create(unsigned int window, bool initializeWindow, bool destroyOldWindow) 行 C++
QWidgetPrivate::createWinId(unsigned int winid) 行 C++
void WindowCreationData::fromWindow(const QWindow *w, const Qt::WindowFlags flagsIn,
unsigned creationFlags)
{
if (popup || (type == Qt::ToolTip) || (type == Qt::SplashScreen)) {
style = WS_POPUP;
} else if (topLevel && !desktop) {
if (flags & Qt::FramelessWindowHint)
style = WS_POPUP; // no border
else if (flags & Qt::WindowTitleHint)
style = WS_OVERLAPPED;
else
style = ;
} else {
style = WS_CHILD;
} if (!desktop) {
if (topLevel) {
if ((type == Qt::Window || dialog || tool)) {
if (!(flags & Qt::FramelessWindowHint)) {
style |= WS_POPUP;
if (flags & Qt::MSWindowsFixedSizeDialogHint) {
style |= WS_DLGFRAME;
} else {
style |= WS_THICKFRAME;
}
if (flags & Qt::WindowTitleHint)
style |= WS_CAPTION; // Contains WS_DLGFRAME
}
} else {
exStyle |= WS_EX_TOOLWINDOW;
}
}
}
}

  上面一个是调用堆栈(从下往上看),一个是关键函数(函数中不重要的内容已经删除)。从代码中可以看出,设置Qt::FramelessWindowHint标志会改变窗口样式,从而影响创建的窗口,现在基本已经知道,"屏幕边缘自动排列"功能与窗口样式有关。

  新建一个win32窗口程序,不断改变窗口的样式,最后得出结论:只有在窗口拥有WS_MAXIMIZEBOX | WS_THICKFRAME样式时,"屏幕边缘自动排列"功能才有效,最好还要添加WS_CAPTION样式,否则窗口最大化会覆盖任务栏。

原本以为完美结束了,但是不要高兴的太早,经过不断测试,还有几个问题:

  1)在任务栏点击窗口时,不能最小化:

    只要加上Qt::WindowMinimizeButtonHint标志即可解决该问题。

  2)如果有多个显示器,在辅屏上直接显示最大化,窗口显示不完整:

QWindowsWindow::show_sys() 行     C++
QWindowsWindow::setVisible(bool visible) 行 C++
QWindow::setVisible(bool visible) 行 C++
QWidgetPrivate::show_sys() 行 C++
QWidgetPrivate::show_helper() 行 C++
QWidget::setVisible(bool visible) 行 C++
QWidget::showMaximized() 行 C++
void QWindowsWindow::show_sys() const
{
int sm = SW_SHOWNORMAL;
bool fakedMaximize = false;
const QWindow *w = window();
const Qt::WindowFlags flags = w->flags();
const Qt::WindowType type = w->type();
if (w->isTopLevel()) {
const Qt::WindowState state = w->windowState();
if (state & Qt::WindowMinimized) {
sm = SW_SHOWMINIMIZED;
if (!isVisible())
sm = SW_SHOWMINNOACTIVE;
} else {
updateTransientParent();
if (state & Qt::WindowMaximized) {
sm = SW_SHOWMAXIMIZED;
// Windows will not behave correctly when we try to maximize a window which does not
// have minimize nor maximize buttons in the window frame. Windows would then ignore
// non-available geometry, and rather maximize the widget to the full screen, minus the
// window frame (caption). So, we do a trick here, by adding a maximize button before
// maximizing the widget, and then remove the maximize button afterwards.
if (flags & Qt::WindowTitleHint &&
!(flags & (Qt::WindowMinMaxButtonsHint | Qt::FramelessWindowHint))) {
fakedMaximize = TRUE;
setStyle(style() | WS_MAXIMIZEBOX);
}
} // Qt::WindowMaximized
} // !Qt::WindowMinimized
}
if (type == Qt::Popup || type == Qt::ToolTip || type == Qt::Tool || testShowWithoutActivating(w))
sm = SW_SHOWNOACTIVATE; if (w->windowState() & Qt::WindowMaximized)
setFlag(WithinMaximize); // QTBUG-8361 ShowWindow(m_data.hwnd, sm); clearFlag(WithinMaximize); if (fakedMaximize) {
setStyle(style() & ~WS_MAXIMIZEBOX);
SetWindowPos(m_data.hwnd, , , , , ,
SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER
| SWP_FRAMECHANGED);
}
}

  还是老样子,上面一个是调用堆栈,一个是关键函数,我们可以看到最后QT调用了ShowWindow函数来显示最大化窗口,但是为什么会显示不完整呢?

  通常遇到一个复杂的问题,我会新建一个简单的项目来做实验。新建一个win32项目,最开始显示就让它最大化,结果显示正常,证明还是QT自己处理的问题,应该是在ShowWindow之后进行其他的处理,导致窗口显示不完整,最后发现是

处理WM_GETMINMAXINFO消息导致的,接下来我们看看QT如何处理WM_GETMINMAXINFO消息。

QWindowsWindow::getSizeHints(tagMINMAXINFO * mmi) 行     C++
QWindowsContext::windowsProc 行 C++
qWindowsWndProc(HWND__ * hwnd, unsigned int message, unsigned int wParam, long lParam) 行 C++
void QWindowsWindow::getSizeHints(MINMAXINFO *mmi) const
{
const QWindowsGeometryHint hint(window(), m_data.customMargins);
hint.applyToMinMaxInfo(m_data.hwnd, mmi); if ((testFlag(WithinMaximize) || (window()->windowState() == Qt::WindowMinimized))
&& (m_data.flags & Qt::FramelessWindowHint)) {
// This block fixes QTBUG-8361: Frameless windows shouldn't cover the
// taskbar when maximized
const QScreen *screen = window()->screen(); // Documentation of MINMAXINFO states that it will only work for the primary screen
if (screen && screen == QGuiApplication::primaryScreen()) {
mmi->ptMaxSize.y = screen->availableGeometry().height(); // Width, because you can have the taskbar on the sides too.
mmi->ptMaxSize.x = screen->availableGeometry().width(); // If you have the taskbar on top, or on the left you don't want it at (0,0):
mmi->ptMaxPosition.x = screen->availableGeometry().x();
mmi->ptMaxPosition.y = screen->availableGeometry().y();
} else if (!screen){
qWarning() << "window()->screen() returned a null screen";
}
} qCDebug(lcQpaWindows) << __FUNCTION__ << window() << *mmi;
}

  当程序在辅屏上时,它的screen是辅屏,如果当前screen不等于QGuiApplication::primaryScreen(主屏),则不设置MINMAXINFO结构,但是由于它已经处理了WM_GETMINMAXINFO消息,导致这个消息不会被系统默认的窗口处理函数处理(DefWindowProc),所以才会显示不完整,解决办法是优先响应WM_GETMINMAXINFO消息,让后交给系统默认的窗口处理函数进行处理。

  3)最大化后,窗口内容变小,最明显的就是最小化、最大化、关闭按钮变小了:

  窗口最大化时,系统会在屏幕上面显示所有的客户区域,此时系统会计算边框的大小,然后超出屏幕范围进行显示,例如边框的宽为8高为8,则系统会在(-8,-8,宽度+8,高度+8)的位置显示窗口,给人的感觉窗口的内容变小了,

  除去底部的任务栏,程序最大化可显示的最大宽度是1600*860,而窗口的实际位置是(-8,-8,1608,868)。这样我们可以添加一个QWidget作为主显示窗口,然后在程序最大化时,添加一个外边框,让它向内部缩一点。

最后的解决方案是:

  1. 在窗口的构造函数中添加以下代码,改变窗口的样式:

this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinimizeButtonHint);
// QMainWindow透明显示,当设置主显示窗口的外边距时,防止外边距显示出来。
this->setAttribute(Qt::WA_TranslucentBackground, true); HWND hwnd = (HWND)this->winId();
DWORD style = ::GetWindowLong(hwnd, GWL_STYLE);
::SetWindowLong(hwnd, GWL_STYLE, style | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_CAPTION);

  2. 重载nativeEvent函数,处理WM_NCHITTEST、WM_NCCALCSIZE和WM_GETMINMAXINFO消息

bool CustomWindow::nativeEvent(const QByteArray &eventType, void *message, long *result)
{
MSG* msg = (MSG*)message;
switch (msg->message) { case WM_NCHITTEST:
{
int xPos = GET_X_LPARAM(msg->lParam) - this->frameGeometry().x();
int yPos = GET_Y_LPARAM(msg->lParam) - this->frameGeometry().y();
if (m_title->isCaption(xPos, yPos)) {
*result = HTCAPTION;
return true;
}
}
break;
case WM_NCCALCSIZE:
return true; case WM_GETMINMAXINFO:
{
if (::IsZoomed(msg->hwnd)) {
// 最大化时会超出屏幕,所以填充边框间距
RECT frame = { , , , };
AdjustWindowRectEx(&frame, WS_OVERLAPPEDWINDOW, FALSE, );
frame.left = abs(frame.left);
frame.top = abs(frame.bottom);
this->setContentsMargins(frame.left, frame.top, frame.right, frame.bottom);
}
else {
this->setContentsMargins(, , , );
} *result = ::DefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam);
return true;
}
break;
} return QMainWindow::nativeEvent(eventType, message, result);
}

显示效果:

  

最后完成的Demo:CustomWindow.zip

如果觉得好用,可以给我留个言,支持一下。

QT自绘标题和边框的更多相关文章

  1. mfc删除标题和边框

    //删除标题和边框WS_CAPTION和WS_BORDER风格 ModifyStyle(WS_CAPTION, 0);ModifyStyle(WS_BORDER, 0);

  2. Qt 无标题无边框程序的拖动和改变大小

    最近做项目遇到的问题,总结下. 有时候我们觉得系统的标题栏和按钮太丑太呆板,想做自己的标题栏以及最大化.最小化.关闭,菜单按钮,我们就需要 setWindowFlags(Qt::FramelessWi ...

  3. Qt:无标题栏无边框程序的拖动和改变大小

    From: http://blog.csdn.net/kfbyj/article/details/9284923 最近做项目遇到的问题,总结下. 有时候我们觉得系统的标题栏和按钮太丑太呆板,想做自己的 ...

  4. Qt自绘窗体

    也许大部分情况下我们不需要自己手动绘制一个窗体,大部分可以通过图片来实现,本篇仅以学习的态度来初略的理解Qt界面的自定义绘制功能.   本篇将实现以下功能: 1.绘制一个椭圆形 2.支持界面的移动操作 ...

  5. Delphi实现无标题有边框的窗体

    1.在delphi中新建窗体程序,然后设置窗口的 BorderStyle属性为bsNone 2.在窗体的public区写下这一句: Procedure CreateParams(var Params ...

  6. Qt 技巧:去除对话框边框 + 设置窗口可移动和透明

    1.去除对话框标题栏和边框 在构造函数里设置:    this->setWindowFlags(Qt::FramelessWindowHint); Qt::Dialog     (按照对话框的形 ...

  7. Qt重绘之update,repaint详解

    Qt里面的重绘和Windows编程里面的重绘差不多.但是Qt的重绘更有特色,更加智能. 在讲之前,先说说paintEvent() paintEvent()是一个虚函数槽(slot),子类可以对父类的p ...

  8. Qt::QWidget 无默认标题栏边框的拖拽修改大小方式

    开发环境:win10+vs2015+qt5.9.1 背景:开发过程中,一般很少会使用系统提供的标题栏和边框:往往都是自定义一个自己设计的方案.这时候在QWidget中需要加上flag:Qt::Fram ...

  9. Qt 创建圆角、无边框、有阴影、可拖动的窗口 good

    程序窗口的边框,标题栏等是系统管理的,Qt 不能对其进行定制,为了实现定制的边框.标题栏.关闭按钮等,需要把系统默认的边框.标题栏去掉,然后使用 Widget 来模拟它们.这里介绍使用 QSS + Q ...

随机推荐

  1. computer专业术语总结

    计算机专业真可谓是博大精深,光语言就有N多种,而且各种算法计数,各个领域,加之新技术的不断出现, 如果想要穷尽 计算机的知识,那绝对是不那个可能的,只能在若干领域才能取得一定的成果,但是多了解一些专业 ...

  2. android fragment addToBackStack bug

    In FragmentActivity.First, I add two fragment by FragmentTransaction. See the flowing code : /****** ...

  3. 背水一战 Windows 10 (39) - 控件(布局类): VariableSizedWrapGrid, Border, Viewbox, SplitView

    [源码下载] 背水一战 Windows 10 (39) - 控件(布局类): VariableSizedWrapGrid, Border, Viewbox, SplitView 作者:webabcd ...

  4. android 在5.0以后不允许使用隐式Intent方式来启动Service

    android5.0以后不能使用隐式intent :需要指定Intent的ComponentName信息:intent.setComponent(xxx),或指定Intent的setPackage(& ...

  5. iOS之崩溃处理:This application is modifying the autolayout engine from a background thread

    一.错误提示 今天在开发的时候遇到一个崩溃问题,"This application is modifying the autolayout engine from a background ...

  6. iOS 通知的使用

    学习通知,我们要掌握:通知的发布 , 通知的监听 , 通知的移除 在通知里面,有一个非常重要的东西: 通知中心(NSNotificationCenter); 每一个应用程序,都有一个通知中心,专门用来 ...

  7. python——爬虫

    网络爬虫(又被称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本.另外一些不常使用的名字还有蚂蚁.自动索引.模拟程序或者蠕 ...

  8. python爬虫框架scrapy初识(一)

    Scrapy介绍 Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架. 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中.所谓网络爬虫,就是一个在网上到处或定向抓取数据的 ...

  9. NoSQL数据建模技术

    原文来自“NoSQL Data Modeling Techniques”,由酷壳网陈皓编译<NoSQL数据建模技术>.这篇文章看完之后,你可能会对NoSQL的数据结构会有些感觉.我的感觉是 ...

  10. [Python]-类型转换

    1.字符串到数值的转换:int(s [,base ]) 将表达式s转换为一个整数 ,s可以是整数,与数字有关的字符串,布尔类型long(s [,base ]) 将表达式s转换为一个长整数 s可以是整数 ...