如何使用PyQT5附加和分离外部应用程序或使外部应用程序停靠?

时间:2019-07-13 03:38:04

标签: python qt ubuntu pyqt5 ros

我正在使用ROS开发用于多机器人系统的GUI,但是我在界面中的最后一件事就是冻结:将RVIZ,GMAPPING或其他屏幕嵌入到我的应用程序中。我已经在界面中放置了一个终端,但是我无法绕开如何向我的应用程序添加外部应用程序窗口的问题。我知道PyQt5具有createWindowContainer,并使用窗口ID来停靠外部应用程序,但是我没有找到任何示例可以帮助我。

如果可能的话,我想在应用程序中将外部窗口拖放到选项卡式框架内。但是,如果这不可能或太难了,那么在单击按钮后仅在选项卡式框架内打开窗口是很好的选择。

我已经尝试打开类似于终端方法的窗口(请参见下面的代码),但是RVIZ窗口在我的应用程序外部打开。

已经尝试使用wmctrl命令将attaching/detaching code代码转换为linux,但无法正常工作。参见my code here

也已经尝试过rviz Python Tutorial,但我收到了错误消息:

回溯(最近通话最近):   在第23行的文件“ rvizTutorial.py”中     导入rviz   在第19行的“ /opt/ros/indigo/lib/python2.7/dist-packages/rviz/init.py”文件中     导入librviz_shiboken ImportError:没有名为librviz_shiboken的模块

#  Frame where i want to open the external Window embedded
self.Simulation = QtWidgets.QTabWidget(self.Base)
self.Simulation.setGeometry(QtCore.QRect(121, 95, 940, 367))
self.Simulation.setTabPosition(QtWidgets.QTabWidget.North)
self.Simulation.setObjectName("Simulation")
self.SimulationFrame = QtWidgets.QWidget()
self.SimulationFrame.setObjectName("SimulationFrame")
self.Simulation.addTab(rviz(), "rViz")

# Simulation Approach like Terminal
class rviz(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(rviz, self).__init__(parent)
        self.process = QtCore.QProcess(self)
        self.rvizProcess = QtWidgets.QWidget(self)
        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.rvizProcess)
        # Works also with urxvt:
        self.process.start('rViz', [str(int(self.winId()))])
        self.setGeometry(121, 95, 940, 367)

2 个答案:

答案 0 :(得分:1)

我无法在公认的答案中获得代码以在Ubuntu 18.04.3 LTS上工作;即使我摆脱了阻止代码运行的异常,我仍然会得到一个单独的PyQt5窗口和一个单独的xterm窗口。

最后,经过一番尝试,我在标签页中打开了xterm窗口;这是我在Ubuntu 18.04.3 LTS中工作的代码(注释了所有未命中的内容):

#!/usr/bin/env python3
# (same code seems to run both with python3 and python2 with PyQt5 in Ubuntu 18.04.3 LTS)
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
import gi
gi.require_version('Wnck', '3.0')
from gi.repository import Wnck, Gdk
import time

class Container(QtWidgets.QTabWidget):
    def __init__(self):
        QtWidgets.QTabWidget.__init__(self)
        self.embed('xterm')

    def embed(self, command, *args):
        proc = QtCore.QProcess()
        proc.setProgram(command)
        proc.setArguments(args)
        #started, procId = proc.startDetached()
        #pid = None
        #started = proc.startDetached(pid)
        # https://stackoverflow.com/q/31519215 : "overload" startDetached : give three arguments, get a tuple(boolean,PID)
        # NB: we will get a failure `xterm: No absolute path found for shell: .` even if we give it an empty string as second argument; must be a proper abs path to a shell
        started, procId = proc.startDetached(command, ["/bin/bash"], ".")
        if not started:
            QtWidgets.QMessageBox.critical(self, 'Command "{}" not started!'.format(command), "Eh")
            return
        attempts = 0
        while attempts < 10:
            screen = Wnck.Screen.get_default()
            screen.force_update()
            # do a bit of sleep, else window is not really found
            time.sleep(0.1)
            # this is required to ensure that newly mapped window get listed.
            while Gdk.events_pending():
                Gdk.event_get()
            for w in screen.get_windows():
                print(attempts, w.get_pid(), procId, w.get_pid() == procId)
                if w.get_pid() == procId:
                    self.window = QtGui.QWindow.fromWinId(w.get_xid())
                    #container = QtWidgets.QWidget.createWindowContainer(window, self)
                    proc.setParent(self)
                    #self.scrollarea = QtWidgets.QScrollArea()
                    #self.container = QtWidgets.QWidget.createWindowContainer(self.window)
                    # via https://vimsky.com/zh-tw/examples/detail/python-method-PyQt5.QtCore.QProcess.html
                    #pid = proc.pid()
                    #win32w = QtGui.QWindow.fromWinId(pid) # nope, broken window
                    win32w = QtGui.QWindow.fromWinId(w.get_xid()) # this finally works
                    win32w.setFlags(QtCore.Qt.FramelessWindowHint)
                    widg = QtWidgets.QWidget.createWindowContainer(win32w)

                    #self.container.layout = QtWidgets.QVBoxLayout(self)
                    #self.addTab(self.container, command)
                    self.addTab(widg, command)
                    #self.scrollarea.setWidget(self.container)
                    #self.container.setParent(self.scrollarea)
                    #self.scrollarea.setWidgetResizable(True)
                    #self.scrollarea.setFixedHeight(400)
                    #self.addTab(self.scrollarea, command)
                    self.resize(500, 400) # set initial size of window
                    return
            attempts += 1
        QtWidgets.QMessageBox.critical(self, 'Window not found', 'Process started but window not found')


app = QtWidgets.QApplication(sys.argv)
w = Container()
w.show()
sys.exit(app.exec_())

答案 1 :(得分:0)

我尚未对此进行专门测试,因为我的Qt5版本太旧,现在无法升级,而从Qt5 5.10开始,startDetached还返回pid以及启动过程中的布尔结果。 在我的测试中,在开始等待窗口创建的while周期之前,我手动设置了procId(通过静态QInputBox.getInt())。 显然,还有其他方法可以做到这一点(并获取窗口的xid)。

import sys
from PyQt5 import QtCore, QtGui, QtWidgets
import gi
gi.require_version('Wnck', '3.0')
from gi.repository import Wnck, Gdk


class Container(QtWidgets.QTabWidget):
    def __init__(self):
        QtWidgets.QTabWidget.__init__(self)
        self.embed('xterm')

    def embed(self, command, *args):
        proc = QtCore.QProcess()
        proc.setProgram(command)
        proc.setArguments(args)
        started, procId = proc.startDetached()
        if not started:
            QtWidgets.QMessageBox.critical(self, 'Command "{}" not started!')
            return
        attempts = 0
        while attempts < 10:
            screen = Wnck.Screen.get_default()
            screen.force_update()
            # this is required to ensure that newly mapped window get listed.
            while Gdk.events_pending():
                Gdk.event_get()
            for w in screen.get_windows():
                if w.get_pid() == procId:
                    window = QtGui.QWindow.fromWinId(w.get_xid())
                    container = QtWidgets.QWidget.createWindowContainer(window, self)                    
                    self.addTab(container, command)
                    return
            attempts += 1
        QtWidgets.QMessageBox.critical(self, 'Window not found', 'Process started but window not found')


app = QtWidgets.QApplication(sys.argv)
w = Container()
w.show()
sys.exit(app.exec_())