Qt界面程序嵌入另一个Qt界面程序[Linux]

Qt的界面程序出现的问题

在Qt界面程序里,主线程是界面线程,并且有且只有主线程才能完成界面的渲染操作,这样就会带来问题。一旦主进程一直处于一个非常耗时的函数中(如构建一个庞大的界面、处理大量的数据),无法处理事件循环中积压的事件以及回调,那么整个界面都会处于未响应状态。

Qt处理问题的传统方法

  1. 方法1 常规的处理方式,也是Qt官方的处理方式是调用 processEvents() 函数,在长期占用主线程的函数的合适地方不断地调用该函数,这样就可以不断处理各种积压的界面刷新动作、用户操作响应等
  2. 方法2 另一种处理方式,可以将数据的处理放置于另一个线程中处理,这样就可以解放主线程,让主线程专心应对于界面相关的工作。

传统方法的缺陷

有一种情况,上述两种方式都无法处理。比如说,程序在收到某个消息时创建一个新的界面,而创建这个界面非常耗时,在这种情况下,首先方法2是无法使用的,因为这是界面控件的创建必须在主线程。

然后是方法1,方法1存在风险,设想这样一个情况,程序收到消息 M_A 将数据 dataA 存储在对象 * structA* 上,然后界面 WidgetA 根据 dataA 来创建控件,在创建 widgetA 的中途中调用 processEvents() ,这时又收到一次消息 M_B ,那么调用processEvents()就会处理该消息的对应的函数,那么就会把 dataB 存在对象 structA 上,就会将原本的 dataA 数据给覆盖,那么一旦 processEvents() 结束,回到WidgetA的创建函数,但此时的数据已经不是原来的数据了,这样就会带来未知的后果。所以说,需要确保事件队列的所有动作不影响本函数(调用 processEvents() 的函数),才能调用 processEvents() 函数。

那么至少,我们在加载又大又多的界面时,需要给用户一个等待条的提示,这样才能避免界面未响应的不良效果。那等待条在加载界面时是无法刷新的,界面的刷新需要不断处理事件队列里关于绘制的事件,处理事件队列需要调用 processEvents() 函数,但是该函数是一股脑的把所有积压的事件处理了(当然Qt的 processEvents() 可以过滤用户动作产生的事件),只要其中有一个事件对正在进行创建的界面产生了影响,那么就会出现问题,那么这种情况下有什么解决方案呢?当然有,多进程界面开发。

多进程界面开发

参考blog:
https://www.cnblogs.com/swarmbees/p/11100513.html
https://blog.csdn.net/u013394556/article/details/78534833
https://blog.csdn.net/r5014/article/details/79286444

既然主线程的随心的调用processEvents(),而我们又需要刷新等待界面,而刷新界面必须主线程,既然如此,那就再创建一个主线程不就好了么!但是一个进程只有一个主线程啊,所以说我们需要再创建一个界面进程,然后把这个子界面进程嵌入到主界面进程当中,在主界面进程忙于工作时,子界面进程仍可以不断刷新窗口,这样就可以避免给用户界面未响应的体验。

关键函数:
QWindow::fromWinId(winId),可以通过句柄返回一个window对象
QWidget::createWindowContainer(), 可以为QWindow创建一份widget对象

示例代码

子窗口进程:
main.cpp

#include <QApplication>
#include <QDebug>
#include <stdio.h>
#include <QLabel>
#include <QMovie>
#include <QWindow>

int main(int argc, char *argv[])
{
    if(argc == 2)
    {
        QApplication a(argc, argv);
		
		//这里是子窗口的顶层窗口
        QLabel w;
        QMovie movie(&w);
        movie.setFileName("XXX.gif");
        w.setMovie(&movie);
        movie.start();
        //end

        WId wid = WId(QString(argv[1]).toInt());//通过参数列表获取父进程窗口的WinId
        QWindow *window = QWindow::fromWinId(wid);//获取父进程窗口

        w.setProperty("_q_embedded_native_parent_handle", QVariant(wid));//设置属性,这句是必须的

        w.winId();//必须调用一次,生成winId
        w.windowHandle()->setParent(window);//设置父窗口
        w.show();//最后调用show,提前调用qt会为其生成窗口控件,这样就会和你原本想要嵌入进的父进程界面产生冲突

        fprintf(stderr, "%lld", w.winId());//写入标准错误输出,stderr能立即输出,stdout则不行

        return a.exec();
    }

    return 0;
}

主窗口进程
waitingbarwin.h

#ifndef WAITINGBARWIN_H
#define WAITINGBARWIN_H

#include <QWidget>

namespace Ui {
class WaitingBarWin;
}

class QProcess;

class WaitingBarWin : public QWidget
{
    Q_OBJECT

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

private:
    Ui::WaitingBarWin *ui;
    QProcess *m_process;

private slots:
    void slot_createWaitingBar();
};

#endif // WAITINGBARWIN_H

waitingbarwin.cpp

#include "waitingbarwin.h"
#include "ui_waitingbarwin.h"

#include <QProcess>
#include <QDebug>
#include <QWindow>
#include <QPixmap>
#include <QBitmap>

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

    QPixmap pixmap;
    pixmap.load("XXX.gif");

    setFixedSize(pixmap.size());
    setWindowFlags(Qt::Dialog | Qt::FramelessWindowHint);
    setMask(pixmap.mask());

    if(m_process == nullptr)
    {
        QString cmd = "WaitingBar";//子程序执行文件地址
        QStringList argList;
        argList << QString::number(this->winId());//把父窗口的id给子进程传递过去
        m_process = new QProcess(this);//使用进程运行子进程窗口
        connect(m_process, &QProcess::readyReadStandardError, this, &WaitingBarWin::slot_createWaitingBar);//等待子进程窗口把自身的winId传递过来
        m_process->start(cmd, argList);
    }
}

WaitingBarWin::~WaitingBarWin()
{
    delete ui;
    m_process->terminate();//在父窗口关闭时,主动终止子窗口进程
    m_process->waitForFinished(50);
}

void WaitingBarWin::slot_createWaitingBar()
{
    quint64 winId = m_process->readAllStandardError().toLongLong();
    QWindow *childWin = QWindow::fromWinId(winId);
    if(childWin)
    {
        QWidget *widget = QWidget::createWindowContainer(childWin);//获取一个子进程窗口的widget
        ui->verticalLayout->addWidget(widget);//这里是可以使用布局器管理子进程窗口的,不管理的话就在坐标0,0处
    }
}

main.cpp

#include "waitingbarwin.h"
#include <QApplication>
#include <QMainWindow>
#include <QPushButton>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QPushButton win;
    win.show();

    WaitingBarWin w(&win);

	//测试,主进程陷入长时间工作时,子进程窗口仍然正常刷新
    QObject::connect(&win, &QPushButton::clicked, [&w](){
        w.show();
        while (1) {
        }
    });

    return a.exec();
}

结语

关于windows平台的使用我没有测试,网上也大多是windows平台的,感觉是获取winId的方式可以使用windows系统的函数实现,这样就可以去嵌入更多的应用。linux平台的获取WinId的方法我暂时没有实现,只是通过Qt自带的方式获取本进进程界面的WinId,如果想要嵌入其他程序,能找到WinId的话理论上是可行的。

  • 10
    点赞
  • 98
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值