2

I am building a GUI program with PyQt5 (Qt Designer) which also uses the pptk library. This library can plot huge amount of points which is very interesting for my purpose (display finite element post processing results).

As it is explained in this post, the viewer class from pptk is a standalone window. Like the author of the previous post, I would like to embed the viewer in my GUI. It seems that I need to write some wrapper. After some research, I still don't know if that means that I have to look inside the C++ code to re-write some stuff. In that case, it'll be more complex than I thought and I'll have to give up for the moment. In the end, if I could create a viewer widget that can be integrated inside my main window, it would be perfect.

Can someone please clarify for me what I have to go through?

Foussy
  • 287
  • 2
  • 11
  • I don't have the experience with QtDesigner to make this an answer, but if there's any `addWidget` methods, you should be able to just feed the viewer to those and it should become a child widget instead of a window. – Gloweye Nov 12 '19 at 11:50
  • 2
    See: [QWidget.createWindowContainer](https://doc.qt.io/qt-5/qwidget.html#createWindowContainer) and [QWindow.fromWinId](https://doc.qt.io/qt-5/qwindow.html#fromWinId). Or wrap the library using [sip](https://www.riverbankcomputing.com/static/Docs/sip/). – ekhumoro Nov 12 '19 at 12:36
  • @ekhumoro it is not possible to call the process : [see this link](https://stackoverflow.com/a/57189391/12273727). Could you recommend me a good tutorial to start learning about wrapping C++ libraries ? – Foussy Nov 15 '19 at 10:08
  • @ChristianHiricoiu What evidence do you have for that? The answer you linked to is totally bogus - it makes no difference how the external program is started. I can't guarantee that it will work (a lot depends on what the local system supports), but you won't know for sure unless you actually try it. – ekhumoro Nov 15 '19 at 16:56
  • @ChristianHiricoiu I tested this on linux and was able to embed a pptk viewer in a pyqt window without any problems. The only issue is how to get the window id of the viewer programmatically. I used `wmctrl`, but each platform will have its own way to do it. – ekhumoro Nov 15 '19 at 19:24
  • @ekhumoro I did it, it worked but not the way I expected. Did your embedded pptk viewer under linux present itself well ? – Foussy Nov 18 '19 at 10:13
  • @ChristianHiricoiu Yes, it displays as normal on linux. The `VWidget` in your example needs to go in a layout (just like any other widget) - but it's not clear whether you've done that. – ekhumoro Nov 18 '19 at 13:05
  • @ChristianHiricoiu It would help if you showed a [mcve] that demonstrated the problem more clearly. It's impossible to diagnose without seeing all the code. – ekhumoro Nov 18 '19 at 16:48
  • @ekhumoro I did not try to resize the windowcontainer, that was the problem... Thanks a lot for your patience and your help – Foussy Nov 19 '19 at 10:06

3 Answers3

4

Below is a demo script that shows how to add the viewer to a layout. I cannot test it on Windows, but on Linux (without the win32gui part), I get the results show below. As you can see, there is no weird border, and the window can be freely resized as normal.

enter image description here

from PyQt5 import QtWidgets, QtGui
import numpy as np
import pptk
import win32gui
import sys

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()

        widget = QtWidgets.QWidget()
        layout = QtWidgets.QGridLayout(widget)
        self.setCentralWidget(widget)

        self.cloudpoint = np.random.rand(100, 3)
        self.v = pptk.viewer(self.cloudpoint)
        hwnd = win32gui.FindWindowEx(0, 0, None, "viewer")
        self.window = QtGui.QWindow.fromWinId(hwnd)    
        self.windowcontainer = self.createWindowContainer(self.window, widget)

        layout.addWidget(self.windowcontainer, 0, 0)

if __name__ == '__main__':

    app = QtWidgets.QApplication(sys.argv)
    app.setStyle("fusion")
    form = MainWindow()
    form.setWindowTitle('PPTK Embed')
    form.setGeometry(100, 100, 600, 500)
    form.show()
    sys.exit(app.exec_())
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • I copied and paste your code, still got this weird black borders. – Foussy Nov 20 '19 at 09:03
  • @ChristianHiricoiu Bah - that's a shame. What happens when you manually resize the window? Does anything change? It might also be worth testing with a standard Windows app like `calc` or `notepad` to see if that behaves normally. When I tested on Linux, I found that manually resizing the window sometimes briefly showed the black borders. So maybe there's some kind of communication lag between Qt and the window manager. – ekhumoro Nov 20 '19 at 14:19
  • when I manually resize the window, the bars remain visible but their width doesn't change (so in fullscreen mode on a big screen, it becomes less annoying, but still there...). Tested it with `calc` and it doesn't embed it within the window... I noticed that if I run the `calc.exe` before running the program instead of launching it programmatically, it moves the calc window behind the QMainWindow generated. It seems that it recognizes the window and tries to contain it but somehow fails to do so. nb : I am under windows 10 – Foussy Nov 20 '19 at 15:29
  • @ChristianHiricoiu It does seem buggy on Windows. What specific versions of Qt and PyQt5 are you using? – ekhumoro Nov 20 '19 at 15:37
  • @ChristianHiricoiu According to [QTBUG-44404](https://bugreports.qt.io/browse/QTBUG-44404), Qt currently doesn't officially support using `createWindowContainer` with external windows. Also, the article [Qt and Foreign Windows](https://gist.github.com/torarnv/c5dfe2d2bc0c089910ce) suggests this is all still a work in progress. So perhaps we should just be grateful that it works *at all* and hope for improvements in the future. – ekhumoro Nov 20 '19 at 16:04
  • `Qt : 5.11.1` and `PyQt5 : 5.13.1` – Foussy Nov 20 '19 at 16:04
  • @ChristianHiricoiu I'm using Qt-5.13.2 and PyQt-5.13.2, so maybe upgrading might help. – ekhumoro Nov 20 '19 at 16:07
  • Ah ok thanks for the info ! And thanks a lot for your help, I now have a functionnal tool and that's what matters the most. – Foussy Nov 20 '19 at 16:07
  • How did you run it on Linux? What about lines that uses win32GUI? – CVDE Jul 17 '22 at 04:22
  • 1
    @Farhad You can use an external linux utility with subprocess to get the window ID of the pptk viewer (e.g. `xprop -name viewer _NET_WM_PID`). – ekhumoro Jul 17 '22 at 10:19
3

Here's what I did from the begining to make it work :

# imports
from PyQt5 import QtWidgets, QtGui
import numpy as np
import pptk
import win32gui
import sys

# local imports
from designer import Ui_MainWindow


class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self):
        super(self.__class__, self).__init__()
        self.setupUi(self)

        self.cloudpoint = np.random.rand(100, 3)
        self.v = pptk.viewer(self.cloudpoint)                # generate the viewer window
        hwnd = win32gui.FindWindowEx(0, 0, None, "viewer")   # retrieve the window ID of the viewer
        self.window = QtGui.QWindow.fromWinId(hwnd)          # get the viewer inside a window

        # embed the window inside the centralwidget of the MainWindow :
        self.windowcontainer = self.createWindowContainer(self.window, self.centralwidget)

        # finally, resize the container as you wish.
        self.windowcontainer.resize(self.width() - 100 , self.height() - 100)


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    app.setStyle("fusion")
    form = MainWindow()
    form.show()
    sys.exit(app.exec_())

the designer is as simple scratch from QtDesigner (QMainWindow with its QWidget centralwidget). I just saved the scratch, converted it in a .py file.

Here's what I got :

Embedded pptk viewer

There's still black bars on the sides of the windowcontainer, I haven't discovered yet how to make them disappear.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Foussy
  • 287
  • 2
  • 11
  • Can you please try the demo script shown in my answer, which uses a proper layout? It should work on Windows, but I cannot test it myself. – ekhumoro Nov 19 '19 at 15:22
  • How did you run it on Linux? What about lines that uses win32GUI? – CVDE Jul 17 '22 at 04:22
1

Found out how to overcome the black border issue. The PPTK viewer needs to be maximised before being embedded in PyQt, like so:

    hwnd = win32gui.FindWindowEx(0, 0, None, "viewer")
    win32gui.ShowWindow(hwnd, win32con.SW_MAXIMIZE)
    window = QtGui.QWindow.fromWinId(hwnd)
    windowContainer = QtWidgets.QWidget.createWindowContainer(window)

And then just add windowContainer to the widget you're displaying it in (as explained in other answers above). The second line is the key one which should solve the black border problem.