Qt 主界面弹出分界面

Qt中的主界面,通过双击将当前界面弹出,点击还原按钮弹出界面嵌回住界面中。程序中最多弹出两个界面。

主界面程序,通过创建四个分界面

复制代码
#ifndef WIDGET_H
#define WIDGET_H

#pragma execution_character_set("utf-8")

#include <QWidget>
#include <QTabBar>
#include <QDesktopWidget>
#include <QLabel>
#include <QMessageBox>

#include "form1.h"
#include "form2.h"
#include "form3.h"
#include "form4.h"
#include "detachwindow.h"

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget();

protected:
    bool eventFilter(QObject *watched, QEvent *event) override;

    void closeEvent(QCloseEvent *event) override;


private slots:
    void on_pushButton_1_clicked();

    void on_pushButton_2_clicked();

    void on_pushButton_3_clicked();

    void on_pushButton_4_clicked();

private:
    Ui::Widget *ui;

    Form1 *m_form1;
    Form2 *m_form2;
    Form3 *m_form3;
    Form4 *m_form4;

    void detachForm(QWidget *form);
    void restoreForm(QWidget *form);

    QHash<QWidget*, DetachWindow*> m_detachWins;    // Form → 独立窗口
    QHash<QWidget*, QWidget*>      m_placeholders;  // Form → 占位页

    struct PageInfo { int index = -1; QString title; };
    QHash<QWidget*, PageInfo> m_pageInfos;          // Form → 原 index + 标题

    int m_maxDetach = 2;        // 最多同时弹出数量
    int m_offsetStep = 40;      // 窗口错开步长
};

#endif // WIDGET_H

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);

    m_form1 = new Form1();   // 指定父对象
    m_form2 = new Form2();
    m_form3 = new Form3();
    m_form4 = new Form4();

    ui->tabWidget->insertTab(0, m_form1, tr("界面-1"));
    ui->tabWidget->insertTab(1, m_form2, tr("界面-2"));
    ui->tabWidget->insertTab(2, m_form3, tr("界面-3"));
    ui->tabWidget->insertTab(3, m_form4, tr("界面-4"));
    ui->tabWidget->tabBar()->hide();

    // 给 tabWidget 安装事件过滤器,捕获双击
    ui->tabWidget->installEventFilter(this);
    // 给每个页面也装过滤器,这样点页面内部也能双击
    m_form1->installEventFilter(this);
    m_form2->installEventFilter(this);
    m_form3->installEventFilter(this);
    m_form4->installEventFilter(this);
}

Widget::~Widget()
{
    delete ui;
}

bool Widget::eventFilter(QObject *watched, QEvent *event)
{
    bool isTarget = (watched == ui->tabWidget
                     || watched == m_form1
                     || watched == m_form2
                     || watched == m_form3
                     || watched == m_form4);

    if (isTarget && event->type() == QEvent::MouseButtonDblClick)
    {
        QMouseEvent *me = static_cast<QMouseEvent*>(event);
        if (me->button() == Qt::LeftButton) {

            QWidget *cur = ui->tabWidget->currentWidget();
            if (!cur) return false;

            // 当前页是占位页 → 忽略
            if (m_placeholders.values().contains(cur))
                return true;

            // 当前页已弹出 → 忽略
            if (m_detachWins.contains(cur))
                return true;

            detachForm(cur);
            return true;
        }
    }

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

void Widget::closeEvent(QCloseEvent *event)
{
    // 遍历所有独立窗口,强制关闭并清理
    const QList<QWidget*> forms = m_detachWins.keys();
    for (QWidget *form : forms) {
        DetachWindow *win = m_detachWins.value(form);
        if (!win) continue;

        // 关键:断开还原信号,避免关闭时又触发 restoreForm
        disconnect(win, nullptr, this, nullptr);

        win->hide();            // hide 不触发 closeEvent,安全
        win->deleteLater();     // 交给事件循环销毁
    }

    m_detachWins.clear();
    m_placeholders.clear();
    m_pageInfos.clear();

    event->accept();            // 继续关闭主窗口
}

void Widget::on_pushButton_1_clicked()
{
    ui->tabWidget->setCurrentIndex(0);
}

void Widget::on_pushButton_2_clicked()
{
    ui->tabWidget->setCurrentIndex(1);
}

void Widget::on_pushButton_3_clicked()
{
    ui->tabWidget->setCurrentIndex(2);
}

void Widget::on_pushButton_4_clicked()
{
    ui->tabWidget->setCurrentIndex(3);
}

void Widget::detachForm(QWidget *form)
{
    if (!form) return;
    if (m_detachWins.contains(form)) return;

    // 限制最多弹出数量
    if (m_detachWins.size() >= m_maxDetach) {
        QMessageBox::information(this,
            tr("提示"),
            tr("最多同时弹出 %1 个窗口").arg(m_maxDetach));
        return;
    }

    int idx = ui->tabWidget->indexOf(form);
    if (idx < 0) return;

    // 1. 记录信息
    PageInfo info;
    info.index = idx;
    info.title = ui->tabWidget->tabText(idx);
    m_pageInfos.insert(form, info);

    // 2. 创建占位页
    QWidget *placeholder = new QWidget();
    QVBoxLayout *phLayout = new QVBoxLayout(placeholder);
    QLabel *label = new QLabel(tr("该界面已在独立窗口中显示"));
    QPushButton *pushButton = new QPushButton(tr("还原"));
    label->setAlignment(Qt::AlignCenter);
    label->setStyleSheet("color: #888; font-size: 16px;");
    phLayout->addWidget(label);
    phLayout->addWidget(pushButton);
    m_placeholders.insert(form, placeholder);
    connect(pushButton, &QPushButton::clicked,
            this, [this, form]() { restoreForm(form); });

    // 3. 摘掉真实 Form,插入占位页
    ui->tabWidget->removeTab(idx);
    ui->tabWidget->insertTab(idx, placeholder, info.title);
    ui->tabWidget->setCurrentIndex(idx);

    // 4. 创建独立窗口
    DetachWindow *win = new DetachWindow(nullptr, info.title);
    m_detachWins.insert(form, win);

    form->setParent(nullptr);
    win->setContent(form);

    // 位置错开:以主窗口为基准,按已弹出数量偏移
    int n = m_detachWins.size() - 1;   // 第几个(0 开始)
    QPoint base = this->pos();
    QSize  sz   = win->size();
    int offsetX = n * m_offsetStep;
    int offsetY = n * m_offsetStep;

    // 防止跑出屏幕
    QRect screen = QApplication::desktop()->availableGeometry(this);
    int x = base.x() + 60 + offsetX;
    int y = base.y() + 60 + offsetY;
    if (x + sz.width()  > screen.right())  x = screen.right()  - sz.width()  - 10;
    if (y + sz.height() > screen.bottom()) y = screen.bottom() - sz.height() - 10;
    win->move(x, y);

    connect(win, &DetachWindow::restoreRequested,
            this, [this, form]() { restoreForm(form); });

    win->show();
    win->raise();
    win->activateWindow();
}

void Widget::restoreForm(QWidget *form)
{
    if (!form) return;
    if (!m_detachWins.contains(form)) return;

    DetachWindow *win = m_detachWins.take(form);
    QWidget *placeholder = m_placeholders.take(form);
    PageInfo info = m_pageInfos.take(form);

    // 1. 取回 Form
    QWidget *content = win->takeContent();
    if (!content) content = form;

    // 2. 移除占位页
    if (placeholder) {
        int pIdx = ui->tabWidget->indexOf(placeholder);
        if (pIdx >= 0)
            ui->tabWidget->removeTab(pIdx);
        placeholder->deleteLater();
    }

    // 3. 插回真实 Form
    content->setParent(ui->tabWidget);
    ui->tabWidget->insertTab(info.index, content, info.title);
    ui->tabWidget->setCurrentIndex(info.index);

    // 4. 清理独立窗口
    win->hide();
    win->deleteLater();
}

分界面处理主要函数

复制代码
#ifndef DETACHWINDOW_H
#define DETACHWINDOW_H

#include <QWidget>

#include <QVBoxLayout>
#include <QPushButton>
#include <QCloseEvent>

class DetachWindow : public QWidget
{
    Q_OBJECT
public:
    explicit DetachWindow(QWidget *parent = nullptr, const QString &title = QString());

    // 设置要显示的界面
    void setContent(QWidget *content);

    // 取出当前内容(还原时用)
    QWidget *takeContent();

signals:
    void restoreRequested();   // 点击"还原"时发出

protected:
    void closeEvent(QCloseEvent *event) override;   // 关闭自动还原

private:
    QVBoxLayout *m_layout;
    QPushButton *m_btnRestore;
    QWidget     *m_content = nullptr;
    QString m_title ;
};

#endif // DETACHWINDOW_H

#include "detachwindow.h"

DetachWindow::DetachWindow(QWidget *parent, const QString &title)
    : QWidget(parent)
    , m_title(title)
{
    setWindowTitle(m_title);
    resize(600, 400);
    setAttribute(Qt::WA_DeleteOnClose, false);  // 自己管理生命周期

    // 顶部工具条:还原按钮
    m_btnRestore = new QPushButton(QStringLiteral("还原"), this);

    QHBoxLayout *topBar = new QHBoxLayout;
    topBar->addStretch();
    topBar->addWidget(m_btnRestore);

    // 内容区
    m_layout = new QVBoxLayout(this);
    m_layout->setContentsMargins(0, 0, 0, 0);
    m_layout->addLayout(topBar);

    connect(m_btnRestore, &QPushButton::clicked,
            this, &DetachWindow::restoreRequested);
}

void DetachWindow::setContent(QWidget *content)
{
    m_content = content;
    if (m_content) {
        m_content->setParent(this);
        m_layout->addWidget(m_content);
        m_content->show();
    }
}

QWidget *DetachWindow::takeContent()
{
    QWidget *c = m_content;
    if (c) {
        m_layout->removeWidget(c);
        c->setParent(nullptr);   // 先脱离
    }
    m_content = nullptr;
    return c;
}

void DetachWindow::closeEvent(QCloseEvent *event)
{
    emit restoreRequested();   // 通知主窗口还原
    event->ignore();           // 由主窗口 hide + deleteLater 真正关闭
}

实现

已弹出界面会显示该界面已在独立窗口中显示。

这样可以实现多个屏幕时不同屏幕显示不同的界面

相关推荐
你怎么知道我是队长1 小时前
C++零基础第3周:if/else、switch、for/while/do-while 控制流完全指南(附20段可运行代码+避坑清单)
java·开发语言·c++
冻柠檬飞冰走茶2 小时前
PTA刷题集之Airline Routes(C语言实现)
c语言·开发语言·数据结构·算法
damoluomu2 小时前
Node 的 Headless 出厂没有前台,Java 的 Headless 是从门户里长出来的
java·开发语言·node.js
我命由我123452 小时前
Python 实用操作 - 文件操作(打开与关闭文件、读取文件、文件写入)
java·开发语言·前端·javascript·数据库·python·java-ee
冻柠檬飞冰走茶2 小时前
PTA刷题集之Battle Over Cities - Hard Version(C语言实现)
c语言·开发语言·数据结构·算法
Дерек的学习记录3 小时前
Unreal Engine 5:第三人称运动系统和人物动画
开发语言·学习·ue5
Ramble_Naylor3 小时前
Rust深度解读:异步流(Async Streams)与生成器的处理艺术
开发语言·后端·rust
努力实干3 小时前
简单工厂模式(Simple Factory Pattern)——C++
开发语言·c++·简单工厂模式
熊猫钓鱼>_>3 小时前
平行视界兼容模式与魔法变量配置避坑【鸿蒙心迹】
运维·服务器·开发语言·笔记·华为·harmonyos·openharmony