如何使用 PyQT5 附加和分离外部应用程序或停靠外部应用程序?
How to attach and detach an external app with PyQT5 or dock an external application?
我正在使用 ROS 为多机器人系统开发 GUI,但我对界面中最不想要的东西感到困惑:在我的应用程序中嵌入 RVIZ、GMAPPING 或其他屏幕。我已经在界面中放置了一个终端,但我不知道如何将外部应用程序 window 添加到我的应用程序。我知道 PyQt5 有 createWindowContainer,使用 window ID 来停靠外部应用程序,但我没有找到任何例子来帮助我。
如果可能,我想将外部 window 拖放到我的应用程序的选项卡式框架内。但是,如果这不可能或太难,我可以只在单击按钮后打开选项卡式框架内的 window。
我已经尝试打开 window 类似于终端方法(请参阅下面的代码),但是 RVIZ window 在我的应用程序之外打开。
已尝试翻译 attaching/detaching code code to linux using the wmctrl command, but didn't work wither. See my code here。
也已经尝试了 rviz Python Tutorial 但我收到错误消息:
回溯(最后一次调用):
文件 "rvizTutorial.py",第 23 行,位于
导入 rviz
文件“/opt/ros/indigo/lib/python2.7/dist-packages/rviz/init.py”,第 19 行,在
导入 librviz_shiboken
导入错误:没有名为 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)
我没有具体测试过这个,因为我有旧版本的 Qt5 我现在不能升级,而从 Qt5 5.10 startDetached 也 returns pid 和 bool 结果开始的过程。
在我的测试中,我在开始等待创建 window 的 while 循环之前手动设置 procId(通过静态 QInputBox.getInt())。
显然还有其他方法可以做到这一点(并获得 window 的 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_())
我无法在 Ubuntu 18.04.3 LTS 上获得已接受答案中的代码;即使我摆脱了阻止代码 运行 的异常,我仍然会得到一个单独的 PyQt5 window 和单独的 xterm
window。
经过一些尝试,我终于在选项卡中打开了 xterm
window;这是我在 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)
# : "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_())
我正在使用 ROS 为多机器人系统开发 GUI,但我对界面中最不想要的东西感到困惑:在我的应用程序中嵌入 RVIZ、GMAPPING 或其他屏幕。我已经在界面中放置了一个终端,但我不知道如何将外部应用程序 window 添加到我的应用程序。我知道 PyQt5 有 createWindowContainer,使用 window ID 来停靠外部应用程序,但我没有找到任何例子来帮助我。
如果可能,我想将外部 window 拖放到我的应用程序的选项卡式框架内。但是,如果这不可能或太难,我可以只在单击按钮后打开选项卡式框架内的 window。
我已经尝试打开 window 类似于终端方法(请参阅下面的代码),但是 RVIZ window 在我的应用程序之外打开。
已尝试翻译 attaching/detaching code code to linux using the wmctrl command, but didn't work wither. See my code here。
也已经尝试了 rviz Python Tutorial 但我收到错误消息:
回溯(最后一次调用): 文件 "rvizTutorial.py",第 23 行,位于 导入 rviz 文件“/opt/ros/indigo/lib/python2.7/dist-packages/rviz/init.py”,第 19 行,在 导入 librviz_shiboken 导入错误:没有名为 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)
我没有具体测试过这个,因为我有旧版本的 Qt5 我现在不能升级,而从 Qt5 5.10 startDetached 也 returns pid 和 bool 结果开始的过程。 在我的测试中,我在开始等待创建 window 的 while 循环之前手动设置 procId(通过静态 QInputBox.getInt())。 显然还有其他方法可以做到这一点(并获得 window 的 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_())
我无法在 Ubuntu 18.04.3 LTS 上获得已接受答案中的代码;即使我摆脱了阻止代码 运行 的异常,我仍然会得到一个单独的 PyQt5 window 和单独的 xterm
window。
经过一些尝试,我终于在选项卡中打开了 xterm
window;这是我在 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)
# : "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_())