C++ QT无边框界面

效果预览

众所周知, QT开发默认的窗口不可以定制, 而且因为不可定制, 在外观方面就不可以根据自己项目的主体样式进行修改, 统一风格。
image
image

项目布局

项目布局使用的是上下分割式, header为标题栏, Main为主内容栏。
image

实现代码

void BorderlessWidget::init()
{
    layoutMain = new QVBoxLayout(this);
    layoutContent = new QVBoxLayout();

    // 添加标题栏
    titleBar = new TitleBar(this);

    layoutMain->addWidget(titleBar);
    auto *spacer = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding);
    layoutMain->addItem(spacer);
    layoutMain->addLayout(layoutContent);
    layoutMain->addItem(spacer);

    setLayout(layoutMain);
}

标题栏页面设置

image
本项目所使用的页面 主要有 logo标签,title标签,以及四个按钮,分别是固定窗口于最上方,最小化窗口,最大化窗口,关闭应用。当然可以根据自己需要定制标题栏。

代码实现

Qlabel 实现

void TitleBar::initLabel()
{
    // 创建文本水平布局
    layoutLabel = new QHBoxLayout;

    iconLabel = new QLabel(this);
    // 设置图标大小
    iconLabel->setFixedSize(LabelBtnSize);
    // 设置图标
    iconLabel->setPixmap(QPixmap(":/res/application.svg").scaled(iconSize, iconSize));

    titleLabel = new QLabel(this);
    // 设置标题字体
    titleLabel->setFont(QFont("Microsoft Yahei", 10));
    setTitle("Test Title");

    // 创建弹簧
    // title显示在左边,所以这里创建一个弹簧,使得title显示在左边
    auto *spacer = new QSpacerItem(btnLabelWidth, btnLabelWidth, QSizePolicy::Fixed, QSizePolicy::Fixed);

    // title显示在中间, 使用这个弹簧,可以使得title显示在中间
    // auto *spacer = new QSpacerItem(btnLabelWidth, btnLabelWidth, QSizePolicy::Expanding, QSizePolicy::Fixed);

    layoutLabel->addWidget(iconLabel);
    layoutLabel->addItem(spacer);
    layoutLabel->addWidget(titleLabel);
}

QToolbutton实现

void TitleBar::initButton()
{
    // 创建按钮水平布局
    layoutBtn = new QHBoxLayout;

    fixedButton = new QToolButton;
    // 设置按钮大小
    fixedButton->setFixedSize(LabelBtnSize);
    // 设置固定按钮的图标
    fixedButton->setIcon(QIcon(":/res/guding.svg").pixmap(iconSize, iconSize));


    minimizeButton = new QToolButton;
    // 设置按钮大小
    minimizeButton->setFixedSize(LabelBtnSize);
    // 设置最小化按钮的图标
    minimizeButton->setIcon(QIcon(":/res/zuixiaohua.svg").pixmap(iconSize, iconSize));


    maximizeButton = new QToolButton;
    // 设置按钮大小
    maximizeButton->setFixedSize(LabelBtnSize);
    // 设置最大化按钮的图标
    maximizeButton->setIcon(QIcon(":/res/quanping.svg").pixmap(iconSize, iconSize));

    closeButton = new QToolButton(this);
    // 设置按钮大小
    closeButton->setFixedSize(LabelBtnSize);
    // 设置关闭按钮的图标
    closeButton->setIcon(QIcon(":/res/guanbi.svg").pixmap(iconSize, iconSize));
    // 设置按钮的样式表
    closeButton->setStyleSheet("QToolButton:hover {"
                               "  border-radius: 4px;"
                               "  border: 1px solid rgba(252, 53, 3, 50%);"
                               "  background-color: rgba(252, 53, 3, 10%);"
                               "}"
                               "QToolButton:pressed {"
                               "  background-color: rgba(252, 53, 3, 100%);"
                               "}");

    auto spacer = new QSpacerItem(btnLabelWidth, btnLabelWidth, QSizePolicy::Fixed, QSizePolicy::Fixed);

    layoutBtn->addWidget(fixedButton);
    layoutBtn->addItem(spacer);
    layoutBtn->addWidget(minimizeButton);
    layoutBtn->addItem(spacer);
    layoutBtn->addWidget(maximizeButton);
    layoutBtn->addItem(spacer);
    layoutBtn->addWidget(closeButton);
}

主框架实现

void TitleBar::init()
{
    setMinimumSize(QSize(300, 50));

    // 关闭窗口边界
    setWindowFlags(Qt::FramelessWindowHint);

    // 设置窗口颜色为灰色
    QPalette pal = palette();
    pal.setColor(QPalette::Window, QColor(236, 236, 236));
    setAutoFillBackground(true);
    setPalette(pal);

    // 创建一个水平弹簧
    auto *spacer = new QSpacerItem(btnLabelWidth, btnLabelWidth, QSizePolicy::Expanding, QSizePolicy::Fixed);

    // 初始化布局
    layoutBtn = new QHBoxLayout;
    layoutLabel = new QHBoxLayout;
    layout = new QHBoxLayout;

    // 初始化标题栏
    initLabel();
    // 初始化按钮
    initButton();
    layout->addLayout(layoutLabel);
    layout->addItem(spacer);
    layout->addLayout(layoutBtn);

    // 设置布局
    this->setLayout(layout);
}

标题栏功能设置

功能概述

  • 四个按钮均可以实现对应的功能
  • 双击标题栏窗口会在 全屏状态正常状态 之间切换
  • 标题栏 logo 图标,title 内容都可以改变
  • 点击左键移动鼠标,窗口会一起移动

按钮功能设置

在TitleBar页面

public slots:
    void minimizeClicked();             // 最小化按钮点击
    void maximizeClicked();             // 最大化和窗口化按钮点击
    void closeWindowClicked();          // 关闭按钮点击
    void fixedWindowClicked();          // 固定按钮点击

signals:
    void minimizeWindow();              // 最小化窗口 信号
    void restoreWindow();               // 还原窗口 信号
    void maximizeWindow();              // 最大化窗口 信号
    void closeWindow();                 // 关闭窗口 信号
    void fixedWindow();                 // 固定窗口 信号
    void unFixedWindow();               // 取消固定窗口 信号

void TitleBar::minimizeClicked()
{
    emit minimizeWindow();
}

void TitleBar::maximizeClicked()
{
    isMaximized = !isMaximized;

    if (isMaximized)
    {
        maximizeButton->setIcon(QIcon(":/res/chuangkouhua.svg").pixmap(iconSize, iconSize));
        emit maximizeWindow();
    }
    else
    {
        maximizeButton->setIcon(QIcon(":/res/quanping.svg").pixmap(iconSize, iconSize));
        emit restoreWindow();
    }
}

void TitleBar::closeWindowClicked()
{
    emit closeWindow();
}

void TitleBar::fixedWindowClicked()
{
    isFixed = !isFixed;

    if (isFixed)
    {
        fixedButton->setIcon(QIcon(":/res/relieve-full.svg").pixmap(iconSize, iconSize));
        emit fixedWindow();
    }
    else
    {
        fixedButton->setIcon(QIcon(":/res/guding.svg").pixmap(iconSize, iconSize));
        emit unFixedWindow();
    }
}

在父页面 borderlessWidget 页面连接connect

 	// 设置connect
    connect(titleBar, &TitleBar::closeWindow, this, &QWidget::close);
    connect(titleBar, &TitleBar::minimizeWindow, this, &QWidget::showMinimized);
    connect(titleBar, &TitleBar::maximizeWindow, this, &QWidget::showMaximized);
    connect(titleBar, &TitleBar::restoreWindow, this, &QWidget::showNormal);
    // 窗口始终在最上方显示
    connect(titleBar, &TitleBar::fixedWindow, this, &BorderlessWidget::setWindowOnTop);
    connect(titleBar, &TitleBar::unFixedWindow, this, &BorderlessWidget::unsetWindowOnTop);

按钮功能的设置中, 固定窗口于最上方有个小问题 点击按钮之后,窗口会闪烁,也尝试了其他方法,但是没有解决,可能要重写某一部分,有些麻烦。放在这以后有时间再优化(欢迎指点)。

双击标题栏窗口变化

这里直接使用了最大最小按钮的函数的实现

void TitleBar::mouseDoubleClickEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton)
    {
        maximizeClicked();
    }
}

标题栏logo与title更换 // 这个比较简单

void TitleBar::setTitle(const QString &title)
{
    if (!title.isEmpty())
    {
        windowTitle = title;
    }
    titleLabel->setText(windowTitle);
}

void TitleBar::setIcon(const QIcon &icon)
{
    if (icon.isNull())
    {
        return;
    }
    iconLabel->setPixmap(icon.pixmap(16, 16));
}

移动窗口

移动窗口复杂一点, 我在写的时候为了避免与BorderlessWidget中窗口大小变化的函数搞混,故而在TitleBar中实现了该功能。

实现代码

在title.h中定义一个父窗口并且初始化. 切记要初始化parentWidget。如果窗口为全屏模式,则在鼠标变动时,需要将窗口正常化,并且尽可能保证相对位置不变。

    // 父窗口
    QWidget *parentWidget;
	void TitleBar::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton)
    {
        isPressed = true;
        m_startPoint = event->globalPos();

        if (parentWidget)
        {
            m_windowPoint = parentWidget->pos();
        }
    }
}

void TitleBar::mouseMoveEvent(QMouseEvent *event)
{
    if (isPressed && parentWidget)
    {
       if (!isMaximized)
       {
           parentWidget->move(m_windowPoint + event->globalPos() - m_startPoint);
       }
       else {
           float width_ratio = static_cast<float>(event->globalPos().x()) / static_cast<float>(parentWidget->width());
           maximizeClicked();
           //
           auto new_x = static_cast<float>(last_width * width_ratio);
           int new_width = static_cast<int>(new_x);
           int new_height = this->height() / 4;

           m_windowPoint = event->globalPos() - QPoint(new_width, new_height);
       }
    }
}

void TitleBar::mouseReleaseEvent(QMouseEvent *event)
{
    Q_UNUSED(event);
    isPressed = false;
}

父窗口

父窗口需要实现的是拖动窗口边缘可以缩放窗口。
实现逻辑是在窗口的四周定义一个PADDING 当鼠标在这个边框内, 并且鼠标处在左键按压状态, 随着光标的移动, 窗口会按照相应的逻辑变化。直接看代码吧。

void BorderlessWidget::mouseReleaseEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton)
    {
        isPressed = false;
        if (dir != NONE) {
            this->releaseMouse();
            this->setCursor(QCursor(Qt::ArrowCursor));
            dir = NONE;
        }
    }
}

void BorderlessWidget::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton)
    {
        isPressed = true;
        if (dir != NONE)
        {
            BorderlessWidget::mouseGrabber();
        }
        else
        {
            dragPosition = event->globalPos() - this->frameGeometry().topLeft();
        }
    }
}

void BorderlessWidget::mouseMoveEvent(QMouseEvent *event)
{
    // 获取鼠标当前位置
    QPoint gloPoint = event->globalPos();
    // 获取鼠标在窗口的位置
    QRect rect = this->rect();

    QPoint tl = this->mapToGlobal(rect.topLeft());
    QPoint br = this->mapToGlobal(rect.bottomRight());

    if (!isPressed)
    {
        this->region(gloPoint);
    }
    else
    {
        if (dir != NONE) {
            QRect rMove(tl, br);
            switch (dir) {
                case LEFT:
                    if (br.x() - gloPoint.x() <= this->minimumWidth()) {
                        rMove.setX(tl.x());
                    }
                    else {
                        rMove.setX(gloPoint.x());
                    }
                    break;
                case RIGHT:
                    rMove.setWidth(gloPoint.x() - tl.x());
                    break;
                case UP:
                    if (br.y() - gloPoint.y() <= this->minimumHeight()) {
                        rMove.setY(tl.y());
                    }
                    else {
                        rMove.setY(gloPoint.y());
                    }
                    break;
                case DOWN:
                    rMove.setHeight(gloPoint.y() - tl.y());
                    break;
                case LEFTTOP:
                    if (br.x() - gloPoint.x() <= this->minimumWidth()) {
                        rMove.setX(tl.x());
                    }
                    else {
                        rMove.setX(gloPoint.x());
                    }
                    if (br.y() - gloPoint.y() <= this->minimumHeight()) {
                        rMove.setY(tl.y());
                    }
                    else {
                        rMove.setY(gloPoint.y());
                    }
                    break;
                case RIGHTTOP:
                    rMove.setWidth(gloPoint.x() - tl.x());
                    rMove.setY(gloPoint.y());
                    break;
                case LEFTBOTTOM:
                    rMove.setX(gloPoint.x());
                    rMove.setHeight(gloPoint.y() - tl.y());
                    break;
                case RIGHTBOTTOM:
                    rMove.setWidth(gloPoint.x() - tl.x());
                    rMove.setHeight(gloPoint.y() - tl.y());
                    break;
                default:
                    break;
            }
            this->setGeometry(rMove);
        }
    }
    QWidget::mouseMoveEvent(event);
}

void BorderlessWidget::changeEvent(QEvent *event)
{
    if (event->type() == QEvent::WindowStateChange)
    {
        if (this->windowState() == Qt::WindowMaximized)
        {
            setWindowStyle(true);

            event->ignore();        // 忽略事件
        }
        else
        {
            setWindowStyle(false);

            event->ignore();        // 忽略事件
        }
    }
    event->accept();                // 接受事件
}

bool BorderlessWidget::eventFilter(QObject *watched, QEvent *event)
{
    if(isMaximized())
    {
       return QWidget::eventFilter(watched, event);
    }

    // 鼠标事件

    switch (event->type())
    {
        case QEvent::MouseMove:
        {
            auto *mouseEvent = dynamic_cast<QMouseEvent *>(event);
            if (mouseEvent)
            {
                mouseMoveEvent(mouseEvent);
            }
            break;
        }
        case QEvent::MouseButtonPress:
        {
            if(watched == this) {
                auto *mouseEvent = dynamic_cast<QMouseEvent *>(event);
                if (mouseEvent) {
                    mousePressEvent(mouseEvent);
                }
            }
            break;
        }
        case QEvent::MouseButtonRelease:
        {
            if(isPressed) {
                auto *mouseEvent = dynamic_cast<QMouseEvent *>(event);
                if (mouseEvent) {
                    mouseReleaseEvent(mouseEvent);
                }
            }
            break;
        }
        default:
            break;
    }

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

void BorderlessWidget::region(const QPoint &cursorGlobalPoint)
{
    QRect rect = this->rect();
    QPoint tl = this->mapToGlobal(rect.topLeft());
    QPoint br = this->mapToGlobal(rect.bottomRight());

    int x = cursorGlobalPoint.x();
    int y = cursorGlobalPoint.y();

    if(tl.x() + PADDING >= x && tl.x() <= x && tl.y() + PADDING >= y && tl.y() <= y)
    {
        this->dir = LEFTTOP;
        this->setCursor(QCursor(Qt::SizeFDiagCursor));
    }
    else if(br.x() - PADDING <= x && br.x() >= x && br.y() - PADDING <= y && br.y() >= y)
    {
        this->dir = RIGHTBOTTOM;
        this->setCursor(QCursor(Qt::SizeFDiagCursor));
    }
    else if(tl.x() + PADDING >= x && tl.x() <= x && br.y() - PADDING <= y && br.y() >= y)
    {
        this->dir = LEFTBOTTOM;
        this->setCursor(QCursor(Qt::SizeBDiagCursor));
    }
    else if(br.x() - PADDING <= x && br.x() >= x && tl.y() + PADDING >= y && tl.y() <= y)
    {
        this->dir = RIGHTTOP;
        this->setCursor(QCursor(Qt::SizeBDiagCursor));
    }
    else if(tl.x() + PADDING >= x && tl.x() <= x)
    {
        this->dir = LEFT;
        this->setCursor(QCursor(Qt::SizeHorCursor));
    }
    else if(br.x() - PADDING <= x && br.x() >= x)
    {
        this->dir = RIGHT;
        this->setCursor(QCursor(Qt::SizeHorCursor));
    }
    else if(tl.y() + PADDING >= y && tl.y() <= y)
    {
        this->dir = UP;
        this->setCursor(QCursor(Qt::SizeVerCursor));
    }
    else if(br.y() - PADDING <= y && br.y() >= y)
    {
        this->dir = DOWN;
        this->setCursor(QCursor(Qt::SizeVerCursor));
    }
    else
    {
        this->dir = NONE;
        this->setCursor(QCursor(Qt::ArrowCursor));
    }

}

void BorderlessWidget::setWindowStyle(bool isMaximized)
{
    if(isMaximized)
    {
        this->layoutMain->setContentsMargins(0, 0, 0, 0);
        this->layoutMain->setSpacing(0);
    }
    else
    {
        this->layoutMain->setContentsMargins(PADDING, PADDING, PADDING, PADDING);
        this->layoutMain->setSpacing(0);
    }
}

demo地址

gitee

posted @ 2024-03-09 21:51  yuchq  阅读(276)  评论(0)    收藏  举报