Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Issues when attaching and detaching external app from QDockWidget

Consider this little piece of code:

import subprocess import win32gui import win32con import time import sys from PyQt5.Qt import *  # noqa   class Mcve(QMainWindow):      def __init__(self, path_exe):         super().__init__()          menu = self.menuBar()          attach_action = QAction('Attach', self)         attach_action.triggered.connect(self.attach)         menu.addAction(attach_action)          detach_action = QAction('Detach', self)         detach_action.triggered.connect(self.detach)         menu.addAction(detach_action)          self.dock = QDockWidget("Attach window", self)         self.addDockWidget(Qt.RightDockWidgetArea, self.dock)          p = subprocess.Popen(path_exe)         time.sleep(0.5)  # Give enough time so FindWindowEx won't return 0         self.hwnd = win32gui.FindWindowEx(0, 0, "CalcFrame", None)         if self.hwnd == 0:             raise Exception("Process not found")      def detach(self):         try:             self._window.setParent(None)             # win32gui.SetWindowLong(self.hwnd, win32con.GWL_EXSTYLE, self._style)             self._window.show()             self.dock.setWidget(None)             self._widget = None             self._window = None         except Exception as e:             import traceback             traceback.print_exc()      def attach(self):         # self._style = win32gui.GetWindowLong(self.hwnd, win32con.GWL_EXSTYLE)         self._window = QWindow.fromWinId(self.hwnd)         self._widget = self.createWindowContainer(self._window)         self.dock.setWidget(self._widget)   if __name__ == '__main__':     app = QApplication(sys.argv)     w = Mcve("C:\\Windows\\system32\\calc.exe")     w.show()     sys.exit(app.exec_()) 

The goal here is to fix the code so the window attaching/detaching into a QDockWidget will be made properly. Right now, the code has 2 important issues.

Issue1

Style of the original window is screwed up:

a) Before attaching (the calculator has a menu bar)

enter image description here

b) When attached (the calculator menu bar is gone)

enter image description here

c) When detached (the menu bar hasn't been restored properly)

enter image description here

I've already tried using flags/setFlags qt functions or getWindowLong/setWindowLong but I haven't had luck with all my attempts

Issue2

If you have attached and detached the calculator to the mainwindow, and then you decide to close the mainwindow, you definitely want everything (pyqt process) to be closed and cleaned properly. Right now, that won't be the case, why?

In fact, when you've attached/detached the calculator to the mainwindow, the python process will hold and you'll need to force the termination of the process manually (i.e. ctrl+break conemu, ctrl+c cmd prompt)... which indicates the code is not doing things correctly when parenting/deparenting

Additional notes:

  • http://doc.qt.io/qt-5/qwindow.html#fromWinId
  • http://doc.qt.io/qt-5/qwidget.html#createWindowContainer
  • In the above minimal code I'm spawning calc.exe as a child process but you can assume calc.exe is an existing non-child process spawned by let's say explorer.exe
like image 988
BPL Avatar asked Jan 27 '19 13:01

BPL


Video Answer


1 Answers

I found part of the issue wrt to closing. So when you are creating the self._window in the attach function and you close the MainWindow, that other window (thread) is sitting around still. So if you add a self._window = None in the __init__ function and add a __del__ function as below, that part is fixed. Still not sure about the lack of menu. I'd also recommend holding onto the subprocess handle with self.__p instead of just letting that go. Include that in the __del__ as well.

    def __del__(self):         self.__p.terminate()         if self._window:             print('terminating window')             self._window.close 

Probably better yet would be to include a closeEvent

    def closeEvent(self, event):         print('Closing time')         self.__p.terminate()         if self._window is not None:             print('terminating window')             self._window.close 
like image 125
Tom Myddeltyn Avatar answered Sep 23 '22 04:09

Tom Myddeltyn