一.概述
在做系统集成框架软件时,经常遇到需要将已有程序的界面嵌入到统一框架软件的情形,下面主要描述了在Qt5平台下面,使用QProcess启动并嵌入带界面应用程序的方法。该方法在Windows、Linux等操作系统下测试通过,可以实现启动带界面应用程序并嵌入到软件集成框架中的效果。
二.运行效果图
1.Windows平台启动操作系统的记事本程序(Notepad)并将界面嵌入本程序中来
2.Linux平台启动操作系统的终端程序(xterm)并将界面嵌入本程序中来
三.QProcess嵌入带界面的应用程序的核心方法
QProcess *pProc = new QProcess(this);
pProc->start("Notepad");
pProc->waitForFinished(1000);
WId wid = Pid2Wid(pProc->processId(), "Notepad");//获取窗口标识
QWindow *pWin = QWindow::fromWinId(wid);
QWidget *pWid = QWidget::createWindowContainer(pWin, this);
m_Layout.addWidget(pWid);
四.Windows平台根据进程号、窗口类名获得窗口唯一标识
#include <windows.h>
WId Pid2Wid(quint64 procID, const char *lpszClassName)
{
char szBuf[256];
HWND hWnd = GetTopWindow(GetDesktopWindow());
while (hWnd)
{
DWORD wndProcID = 0;
GetWindowThreadProcessId(hWnd, &wndProcID);
if (wndProcID == procID)
{
GetClassName(hWnd, szBuf, sizeof(szBuf));
if (strcmp(szBuf, lpszClassName) == 0)
{
return (WId)hWnd;
}
}
hWnd = GetNextWindow(hWnd, GW_HWNDNEXT);
}
return 0;
}
网上大部分FindWindow查找窗口句柄,该方法还需要应用程序的窗口标题,否则如果已经运行了被启动程序的多个实例,查找的结果不唯一,无法将由QProcess启动的进程被正确嵌入进来。再则窗口的标题会经常变化,比如启动了记事本之后窗口标题一般情况下为“无标题 - 记事本”,只要该文档被保存,窗口的标题的前半部分就变为被保存的文件名称了。
WId wid = (WId)FindWindow("Notepad", "无标题 - 记事本");
五.Linux平台根据进程号获得窗口唯一标识
#include <X11/Xlib.h>
#include <X11/Xatom.h>
class WindowsMatchingPid
{
public:
WindowsMatchingPid(Display *display, Window wRoot, unsigned long pid)
: _display(display)
, _pid(pid)
{
// Get the PID property atom.
_atomPID = XInternAtom(display, "_NET_WM_PID", True);
if (_atomPID == None)
{
cout << "No such atom" << endl;
return;
}
search(wRoot);
}
inline const list<Window> &result() const { return _result; }
private:
unsigned long _pid;
Atom _atomPID;
Display *_display;
list<Window> _result;
private:
void search(Window w)
{
// Get the PID for the current Window.
Atom type;
int format;
unsigned long nItems;
unsigned long bytesAfter;
unsigned char *propPID = 0;
if (Success == XGetWindowProperty(_display,
w,
_atomPID,
0,
1,
False,
XA_CARDINAL,
&type,
&format,
&nItems,
&bytesAfter,
&propPID))
{
if (propPID != 0)
{
// If the PID matches, add this window to the result set.
if (_pid == *((unsigned long *)propPID))
_result.push_back(w);
XFree(propPID);
}
}
// Recurse into child windows.
Window wRoot;
Window wParent;
Window *wChild;
unsigned nChildren;
if (0 != XQueryTree(_display, w, &wRoot, &wParent, &wChild, &nChildren))
{
for (unsigned i = 0; i < nChildren; i++)
search(wChild[i]);
}
}
};
int Pid2Wid(int nPid)
{
// Start with the root window.
char *pDsp = getenv("DISPLAY");
Display *display = XOpenDisplay(pDsp);
if (display == NULL)
{
return 0;
}
WindowsMatchingPid match(display, XDefaultRootWindow(display), nPid);
const list<Window> &result = match.result();
if (result.size())
{
return (unsigned long)(*result.begin());
}
else
{
return 0;
}
}
六.完整源代码
//widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QVBoxLayout>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void on_pushButton_clicked();
private:
Ui::Widget *ui;
QVBoxLayout m_Layout;
};
#endif // WIDGET_H
//widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <iostream>
#include <list>
#include <stdlib.h>
#include <QProcess>
#include <QWindow>
using namespace std;
#ifdef WIN32
#include <windows.h>
WId Pid2Wid(quint64 procID, const char *lpszClassName)
{
char szBuf[256];
HWND hWnd = GetTopWindow(GetDesktopWindow());
while (hWnd)
{
DWORD wndProcID = 0;
GetWindowThreadProcessId(hWnd, &wndProcID);
if (wndProcID == procID)
{
GetClassName(hWnd, szBuf, sizeof(szBuf));
if (strcmp(szBuf, lpszClassName) == 0)
{
return (WId)hWnd;
}
}
hWnd = GetNextWindow(hWnd, GW_HWNDNEXT);
}
return 0;
}
#else
#include <X11/Xlib.h>
#include <X11/Xatom.h>
class WindowsMatchingPid
{
public:
WindowsMatchingPid(Display *display, Window wRoot, unsigned long pid)
: _display(display)
, _pid(pid)
{
// Get the PID property atom.
_atomPID = XInternAtom(display, "_NET_WM_PID", True);
if (_atomPID == None)
{
cout << "No such atom" << endl;
return;
}
search(wRoot);
}
inline const list<Window> &result() const { return _result; }
private:
unsigned long _pid;
Atom _atomPID;
Display *_display;
list<Window> _result;
private:
void search(Window w)
{
// Get the PID for the current Window.
Atom type;
int format;
unsigned long nItems;
unsigned long bytesAfter;
unsigned char *propPID = 0;
if (Success == XGetWindowProperty(_display,
w,
_atomPID,
0,
1,
False,
XA_CARDINAL,
&type,
&format,
&nItems,
&bytesAfter,
&propPID))
{
if (propPID != 0)
{
// If the PID matches, add this window to the result set.
if (_pid == *((unsigned long *)propPID))
_result.push_back(w);
XFree(propPID);
}
}
// Recurse into child windows.
Window wRoot;
Window wParent;
Window *wChild;
unsigned nChildren;
if (0 != XQueryTree(_display, w, &wRoot, &wParent, &wChild, &nChildren))
{
for (unsigned i = 0; i < nChildren; i++)
search(wChild[i]);
}
}
};
int Pid2Wid(int nPid)
{
// Start with the root window.
char *pDsp = getenv("DISPLAY");
Display *display = XOpenDisplay(pDsp);
if (display == NULL)
{
return 0;
}
WindowsMatchingPid match(display, XDefaultRootWindow(display), nPid);
const list<Window> &result = match.result();
if (result.size())
{
return (unsigned long)(*result.begin());
}
else
{
return 0;
}
}
#endif
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget), m_Layout(this)
{
ui->setupUi(this);
m_Layout.addWidget(ui->pushButton);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()
{
QProcess *pProc = new QProcess(this);
pProc->setProcessChannelMode(QProcess::ForwardedErrorChannel);
#ifdef WIN32
pProc->start("Notepad");
pProc->waitForFinished(1000);
WId wid = Pid2Wid(pProc->processId(), "Notepad");
#else
pProc->start("xterm");// X11自带的xterm、xclock可以正确地被嵌入启动,xedit等能够被启动,不能得到窗口ID,故不能被正确嵌入。Qt编写的程序应该均能被正确嵌入。
pProc->waitForFinished(1000);
WId wid = Pid2Wid(pProc->processId());
#endif
QWindow *pWin = QWindow::fromWinId(wid);
printf("%d\t%p\t%d\n", (int)pProc->processId(), pWin, (int)wid);
if (pWin != NULL)
{
QWidget *pWid = QWidget::createWindowContainer(pWin, this);
m_Layout.addWidget(pWid);
m_Layout.update();
}
}
//main.cpp
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.showMaximized();
return a.exec();
}
#QProcessEmbedApp.pro
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = QProcessEmbedApp
TEMPLATE = app
DEFINES += QT_DEPRECATED_WARNINGS
DEFINES -= UNICODE _UNICODE
CONFIG += c++11
SOURCES += main.cpp widget.cpp
HEADERS += widget.h
FORMS += widget.ui
unix: LIBS += -lX11
widget.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Widget</class>
<widget class="QWidget" name="Widget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>700</width>
<height>495</height>
</rect>
</property>
<property name="windowTitle">
<string>QProcessEmbedApp</string>
</property>
<widget class="QPushButton" name="pushButton">
<property name="geometry">
<rect>
<x>140</x>
<y>0</y>
<width>93</width>
<height>28</height>
</rect>
</property>
<property name="text">
<string>StartApp</string>
</property>
</widget>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources/>
<connections/>
</ui>