码迷,mamicode.com
首页 > 其他好文 > 详细

QT自绘标题和边框

时间:2016-06-19 15:26:42      阅读:1434      评论:0      收藏:0      [点我收藏+]

标签:

在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() 行 1854    C++
QWindowsWindow::frameMargins() 行 188    C++
QWidgetPrivate::updateFrameStrut() 行 11824    C++
QWidget::create(unsigned int window, bool initializeWindow, bool destroyOldWindow) 行 1358    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(0, 0, 0, 0)
            : 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) 行 519    C++
QWindowsWindowData::create(const QWindow * w, const QWindowsWindowData & parameters, const QString & title) 行 1075    C++
QWindowsIntegration::createWindowData(QWindow * window) 行 316    C++
QWindowsIntegration::createPlatformWindow(QWindow * window) 行 340    C++
QWindowPrivate::create(bool recursive) 行 392    C++
QWindow::create() 行 549    C++
QWidgetPrivate::create_sys(unsigned int window, bool initializeWindow, bool destroyOldWindow) 行 1456    C++
QWidget::create(unsigned int window, bool initializeWindow, bool destroyOldWindow) 行 1321    C++
QWidgetPrivate::createWinId(unsigned int winid) 行 2528    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 = 0;
    } 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. 在窗口的构造函数中添加以下代码,改变窗口的样式:

    this->setWindowFlags(Qt::FramelessWindowHint);

    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消息

bool wnd_title::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 (yPos < 32) {
            *result = HTCAPTION;
            return true;
        }
    }
        break;

    case WM_NCCALCSIZE:
        return true;

    }

    return false;
}

显示效果:

  技术分享

QT自绘标题和边框

标签:

原文地址:http://www.cnblogs.com/dongc/p/5598053.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!