Qt实现窗口靠边半屏,靠顶全屏的效果

前言

  前两天登录github看到一个网友的留言,这两天比较闲就研究了下。
在这里插入图片描述

实现效果

在这里插入图片描述

实现思路

  实现一个继承自QWidget的类,我们就叫MuWinWindow,然后设置无边框setWindowFlags(Qt::FramelessWindowHint);然后实现bool MuWinWindow::nativeEvent(const QByteArray &eventType, void *message, long *result)此时我们拖动窗体的任何部分都能实现上面的效果。我们想要的是只能通过拖动标题栏来实现上述的效果,在MuWinWindow添加QVBoxLayou布局来添加Titlebar和ClientWidget,此时点击ClientWidget区域就不会移动窗体,在Titlebar中通过setMask来设置我们需要Titlebar接收鼠标事件的地方(最小化、放大还原和关闭等按钮),其余区域将不会显示,鼠标事件直接由MuWinWindow接收。但是这里面有一个问题,如果我们在Titlebar中显示窗口图标和标题的话,需要通过setMask设置他们,但是此时拖动窗口图标和标题的区域是不起作用的,因此我们只能将图标和标题信息绘制到了MuWinWindow上面。
  这样一来,设置MuWinWindow的背景颜色其实就是设置所谓的标题栏的颜色,窗口图标和标题都是在MuWinWindow中完成的。

代码

MuWinWindow.h

#ifndef MUWINWINDOW_H
#define MUWINWINDOW_H

#include <QWidget>
#include <Windows.h>

namespace Ui {
class MuWinWindow;
}

class QHBoxLayout;
class QVBoxLayout;
class MuWinWindow : public QWidget
{
    Q_OBJECT

public:
    explicit MuWinWindow(QWidget *parent = 0);
    ~MuWinWindow();

    void paintEvent(QPaintEvent *e);

    QString iconFileName() const;
    void setWindowIcon(const QString &fileName);
    void setWindowTitle(const QString &title);

    bool isMaximized() const
    { return isMaximized_; }


protected:
    virtual bool nativeEvent(const QByteArray &eventType,
                             void *message,
                             long *result);

private:
    LRESULT calculateBorder(const QPoint &pt);

private:
    Ui::MuWinWindow *ui;

    QString iconFileName_;
    QString title_;

    QVBoxLayout *mainLayout_;
    QWidget *clientWidget_;

    bool isMaximized_;
};

#endif // MUWINWINDOW_H

MuWinWindow.cpp

#include "MuWinWindow.h"
#include "ui_muwinwindow.h"

#include <QLayout>
#include <QPainter>
#include <QDesktopWidget>
#include <QDebug>

#include <QtWinExtras/QtWin>
#include <dwmapi.h>

#include "MuWinTitlebar.h"

#pragma comment (lib, "user32.lib")

#ifndef GET_X_LPARAM
#define GET_X_LPARAM(lParam)    ((int)(short)LOWORD(lParam))
#endif
#ifndef GET_Y_LPARAM
#define GET_Y_LPARAM(lParam)    ((int)(short)HIWORD(lParam))
#endif

MuWinWindow::MuWinWindow(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::MuWinWindow),
    clientWidget_(new QWidget(this))
{
    setWindowFlags(Qt::FramelessWindowHint);

    HWND hwnd = reinterpret_cast<HWND>(this->winId());
    DWORD style = GetWindowLong(hwnd, GWL_STYLE);
    SetWindowLongPtr(hwnd, GWL_STYLE, style | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_CAPTION);

    bool enabled = QtWin::isCompositionEnabled();
    if (enabled) {
        HWND hwnd = (HWND)this->winId();
        DWORD style = ::GetWindowLong(hwnd, GWL_STYLE);
        ::SetWindowLong(hwnd, GWL_STYLE, style | WS_THICKFRAME | WS_CAPTION | WS_BORDER);
        QtWin::extendFrameIntoClientArea(this, 1, 1, 1, 1);
    }

    // 其实是设置的标题栏的颜色
    setStyleSheet("background-color: #52baff");
    clientWidget_->setStyleSheet("background-color: #FFFFFF");

    MuWinTitlebar *titleBar = new MuWinTitlebar(this);
    titleBar->setFixedHeight(50);

    mainLayout_ = new QVBoxLayout(this);
    mainLayout_->addWidget(titleBar);
    mainLayout_->addWidget(clientWidget_);
    mainLayout_->setContentsMargins(0, 0, 0, 0);

    installEventFilter(titleBar);

    setWindowIcon(":/1.jpg");
    setWindowTitle("WinWindow");

    connect(titleBar, &MuWinTitlebar::ShowMinimized, this, &MuWinWindow::showMinimized);
    connect(titleBar, &MuWinTitlebar::ShowMaximized, this, &MuWinWindow::showMaximized);
    connect(titleBar, &MuWinTitlebar::ShowRestoreSize, this, &MuWinWindow::showNormal);
    connect(titleBar, &MuWinTitlebar::Close, this, &MuWinWindow::close);
}

MuWinWindow::~MuWinWindow()
{
}

///
/// \brief MuWinWindow::paintEvent 通过paintEvent绘制图标和标题,如果用MuTitleBar来显示的图标和标题的话,
///                                当鼠标点击图标和标题时无法移动窗体
/// \param e
///
void MuWinWindow::paintEvent(QPaintEvent *e)
{
    QPainter p(this);

    // 绘制窗口图标
    QRect imgTarget(10, 10, 30, 30);
    QImage img(iconFileName_);
    p.drawImage(imgTarget, img.scaled(30, 30, Qt::KeepAspectRatio, Qt::SmoothTransformation));

    // 绘制标题
    QFont font;
    font.setPixelSize(14);
    QFontMetrics fm(font);
    QRect titleTarget(45, 10, fm.width(title_), 30);

    p.setFont(font);
    p.setPen(Qt::white);
    p.drawText(titleTarget, title_, QTextOption(Qt::AlignCenter));

    QWidget::paintEvent(e);
}

QString MuWinWindow::iconFileName() const
{
    return iconFileName_;
}

void MuWinWindow::setWindowIcon(const QString &fileName)
{
    iconFileName_ = fileName;
    this->QWidget::setWindowIcon(QIcon(fileName));
}

void MuWinWindow::setWindowTitle(const QString &title)
{
    title_ = title;
    this->QWidget::setWindowTitle(title);
}

bool MuWinWindow::nativeEvent(const QByteArray &eventType,
                              void *message,
                              long *result)
{

#ifdef Q_OS_WIN
    if (eventType != "windows_generic_MSG")
        return false;

    MSG* msg = static_cast<MSG*>(message);

    QWidget* widget = QWidget::find(reinterpret_cast<WId>(msg->hwnd));
    if (!widget)
        return false;

    switch (msg->message) {
    case WM_NCCALCSIZE: {
        *result = 0;
        return true;
    }

    case WM_NCHITTEST: {
        int x = GET_X_LPARAM(msg->lParam);
        int y = GET_Y_LPARAM(msg->lParam);

        QPoint pt = mapFromGlobal(QPoint(x, y));
        *result = calculateBorder(pt);
        if (*result == HTCLIENT) {
            QWidget* tempWidget = this->childAt(pt.x(), pt.y());
            if (tempWidget == NULL) {
                *result = HTCAPTION;
            }
        }
        return true;
    }

    case WM_GETMINMAXINFO: {
        if (::IsZoomed(msg->hwnd)) {
            isMaximized_ = true;
            RECT frame = { 0, 0, 0, 0 };
            AdjustWindowRectEx(&frame, WS_OVERLAPPEDWINDOW, FALSE, 0);
            frame.left = abs(frame.left);
            frame.top = abs(frame.bottom);
            widget->setContentsMargins(frame.left, frame.top, frame.right, frame.bottom);
        }
        else {
            widget->setContentsMargins(0, 0, 0, 0);
            isMaximized_ = false;
        }

        *result = ::DefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam);
        return true;
    }
    break;

    default:
        break;
    }

#endif

    return QWidget::nativeEvent(eventType, message, result);
}

LRESULT MuWinWindow::calculateBorder(const QPoint &pt)
{
    if (::IsZoomed((HWND)this->winId())) {
        return HTCLIENT;
    }
    int borderSize = 4;
    int cx = this->size().width();
    int cy = this->size().height();

    QRect rectTopLeft(0, 0, borderSize, borderSize);
    if (rectTopLeft.contains(pt)) {
        return HTTOPLEFT;
    }

    QRect rectLeft(0, borderSize, borderSize, cy - borderSize * 2);
    if (rectLeft.contains(pt)) {
        return HTLEFT;
    }

    QRect rectTopRight(cx - borderSize, 0, borderSize, borderSize);
    if (rectTopRight.contains(pt)) {
        return HTTOPRIGHT;
    }

    QRect rectRight(cx - borderSize, borderSize, borderSize, cy - borderSize * 2);
    if (rectRight.contains(pt)) {
        return HTRIGHT;
    }

    QRect rectTop(borderSize, 0, cx - borderSize * 2, borderSize);
    if (rectTop.contains(pt)) {
        return HTTOP;
    }

    QRect rectBottomLeft(0, cy - borderSize, borderSize, borderSize);
    if (rectBottomLeft.contains(pt)) {
        return HTBOTTOMLEFT;
    }

    QRect rectBottomRight(cx - borderSize, cy - borderSize, borderSize, borderSize);
    if (rectBottomRight.contains(pt)) {
        return HTBOTTOMRIGHT;
    }

    QRect rectBottom(borderSize, cy - borderSize, cx - borderSize * 2, borderSize);
    if (rectBottom.contains(pt)) {
        return HTBOTTOM;
    }

    return HTCLIENT;
}

MuWinTitlebar.h

#ifndef MUTITLEBAR_H
#define MUTITLEBAR_H

#include <QWidget>

class QHBoxLayout;
class QPushButton;
class MuWinTitlebar : public QWidget
{
    Q_OBJECT
public:
    explicit MuWinTitlebar(QWidget *parent = nullptr);

    bool eventFilter(QObject *watched, QEvent *event);
    void resizeEvent(QResizeEvent *e);

signals:
    void ShowMinimized();
    void ShowMaximized();
    void ShowRestoreSize();
    void Close();

public slots:

private:
    QPushButton *minButton_;
    QPushButton *maxRestoreButton_;
    QPushButton *closeButton_;

    QHBoxLayout *mainLayout_;
};

#endif // MUTITLEBAR_H

MuWinTitlebar.cpp

#include "MuWinTitlebar.h"
#include <QEvent>
#include <QLabel>
#include <QHBoxLayout>
#include <QIcon>
#include <QPushButton>
#include <QApplication>
#include <QPainter>
#include <QDebug>

#include "muwinwindow.h"

MuWinTitlebar::MuWinTitlebar(QWidget *parent)
    : QWidget(parent),
      minButton_(new QPushButton()),
      maxRestoreButton_(new QPushButton(this)),
      closeButton_(new QPushButton(this)),
      mainLayout_(new QHBoxLayout(this))
{
//    setAttribute(Qt::WA_TransparentForMouseEvents);
    maxRestoreButton_->setCheckable(true);

    mainLayout_->addStretch();
    mainLayout_->addWidget(minButton_);
    mainLayout_->addWidget(maxRestoreButton_);
    mainLayout_->addWidget(closeButton_);

    minButton_->setFixedSize(35, 35);
    maxRestoreButton_->setFixedSize(35, 35);
    closeButton_->setFixedSize(35, 35);

    minButton_->setObjectName("minButton");
    minButton_->setStyleSheet("QPushButton { "
                              "    border: none;"
                              "    padding: 3px;"
                              "    image: url(:/minimize.png);"
                              "}"
                              "QPushButton:hover { "
                              "    border: none;"
                              "    background-color: #4baeeb;"
                              "}");

    maxRestoreButton_->setStyleSheet("QPushButton { "
                                     "    border: none;"
                                     "    padding: 8px;"
                                     "    image: url(:/maximize.png);"
                                     "}"
                                     "QPushButton:hover { "
                                     "    border: none;"
                                     "    background-color: #4baeeb;"
                                     "}"
                                     "QPushButton::checked { "
                                     "    border: none;"
                                     "    padding: 8px;"
                                     "    image: url(:/restore.png);"
                                     "}"
                                     "QPushButton::checked:hover { "
                                     "    border: none;"
                                     "    background-color: #4baeeb;"
                                     "}"
                                     );

    closeButton_->setStyleSheet("QPushButton { "
                                "    border: none;"
                                "    padding: 8px;"
                                "    image: url(:/close.png);"
                                "}"
                                "QPushButton:hover { "
                                "    border: none;"
                                "    background-color: #4baeeb;"
                                "}");

    connect(minButton_, &QPushButton::clicked, this, &MuWinTitlebar::ShowMinimized);
    connect(closeButton_, &QPushButton::clicked, this, &MuWinTitlebar::Close);
    connect(maxRestoreButton_, &QPushButton::clicked, [this](bool checked) {
        if (checked)
            emit ShowMaximized();
        else
            emit ShowRestoreSize();
    });
}

bool MuWinTitlebar::eventFilter(QObject *watched, QEvent *event)
{
    MuWinWindow *window = qobject_cast<MuWinWindow *>(watched);
    if (window == nullptr)
        return false;

    switch (event->type()) {
    case QEvent::Resize: {
        if (window->isMaximized()) {
            maxRestoreButton_->setChecked(true);
        } else {
            maxRestoreButton_->setChecked(false);
        }
        return true;
    }

    default:
        break;
    }
    return QWidget::eventFilter(watched, event);
}

void MuWinTitlebar::resizeEvent(QResizeEvent *e)
{
    QRegion reg(frameGeometry());
    reg -= QRegion(geometry());
    reg += childrenRegion();
    setMask(reg);
    QWidget::resizeEvent(e);
}

代码下载

本文代码
更多自定义无边框代码

评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值