0

I am currently exploring the possibilities of displaying and working with PowerPoint presentations in a GUI using PyQt5/PyQt6 and Python. For that I found the most promising solution to be using a QAxWidget. Loading and displaying the pptx-file works fine, but unfortunately I noticed glitches when resizing the window of the GUI.

As a minimal example I used the following tutorial from the official Qt docs: https://doc.qt.io/qtforpython/examples/example_axcontainer__axviewer.html?highlight=qaxwidget

After the QAxWidget()-initialization i just added the following line:

self.axWidget.setControl(r"C:\path\to\file\presentation.pptx")

Full code (taken from: https://doc.qt.io/qtforpython/examples/example_axcontainer__axviewer.html?highlight=qaxwidget):

# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

"""PySide6 Active Qt Viewer example"""

import sys

from PyQt5.QtWidgets import qApp
from PySide6.QtAxContainer import QAxSelect, QAxWidget
from PySide6.QtGui import QAction
from PySide6.QtWidgets import (QApplication, QDialog,
                               QMainWindow, QMessageBox, QToolBar)


class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()

        toolBar = QToolBar()
        self.addToolBar(toolBar)
        fileMenu = self.menuBar().addMenu("&File")
        loadAction = QAction("Load...", self, shortcut="Ctrl+L", triggered=self.load)
        fileMenu.addAction(loadAction)
        toolBar.addAction(loadAction)
        exitAction = QAction("E&xit", self, shortcut="Ctrl+Q", triggered=self.close)
        fileMenu.addAction(exitAction)

        aboutMenu = self.menuBar().addMenu("&About")
        aboutQtAct = QAction("About &Qt", self, triggered=qApp.aboutQt)
        aboutMenu.addAction(aboutQtAct)
        self.axWidget = QAxWidget()
        self.axWidget.setControl(r"C:\path\to\file\presentation.pptx")
        self.setCentralWidget(self.axWidget)

    def load(self):
        axSelect = QAxSelect(self)
        if axSelect.exec() == QDialog.Accepted:
            clsid = axSelect.clsid()
            if not self.axWidget.setControl(clsid):
                QMessageBox.warning(self, "AxViewer", f"Unable to load {clsid}.")


if __name__ == '__main__':
    app = QApplication(sys.argv)
    mainWin = MainWindow()
    availableGeometry = mainWin.screen().availableGeometry()
    mainWin.resize(availableGeometry.width() / 3, availableGeometry.height() / 2)
    mainWin.show()
    sys.exit(app.exec())

When resizing the window of the GUI, there appear some glitches underneath:

Glitched GUI window

An animation that shows the result while resizing:

Animation

Unfortunately I haven't found many resources I could use where a QAxWidget is used in combination with Python to figure this out myself. That's why I'm here to ask if anyone out there might have a solution for getting rid of those glitches.

musicamante
  • 41,230
  • 6
  • 33
  • 58
  • Is that glitch permanent, meaning that the *contents* of the QAxWidget stays at the same size as when it's shown the first time? Also be aware that while PyQt and PySide aim to do the same thing, they should **never** be used together; while your specific case may not be a huge risk (`qApp` is used for basic purposes), those two imports should not coexist in the same code, especially considering that you used PyQt for Qt5 and PySide for Qt6. If you want to use PySide, *remove* any PyQt reference, and **never** use two versions of the same libraries, unless you *really* know what you're doing. – musicamante Feb 10 '23 at 00:38
  • If you want a reference to the QApplication, just use `QApplication.instance()`. If you're using static functions, `QApplication.functionName` is fine enough (in your case: `QApplication.aboutQt`). – musicamante Feb 10 '23 at 00:41
  • @musicamante Thanks for your response, I used this code from the official Qt site and slightly modified it to give a minimal example as this glitch also applies here. Generally I intend to use PyQt5 only and I've already worked on a more fleshed out GUI using it. There I also have a QAxWidget inside the Layout of a QGroupBox and the glitches are the same there. I initialize the QAxWidget and use setControl() like in the example given above but with the QGroupBox as parent. – humanbydefinition Feb 10 '23 at 12:34
  • @musicamante When the QAxWidget and the presentation is loaded the Widget itself is functional and I can switch slides or alter slide objects. I kind of now assume that the reason might be that the presentation isn't taking up the entire space of the QAxWidget. Below the presentation might still be QAxWidget-territory, which causes those glitches when resizing? I will look into this later, thanks for giving me ideas! Also, the content (the presentation) resizes accordingly when resizing the window. – humanbydefinition Feb 10 '23 at 12:34
  • You say that the content resize accordingly to the window, so, when are those glitches visible? – musicamante Feb 10 '23 at 17:09
  • @musicamante Thanks for sticking around, I have set up a GIF that hopefully clears up what I mean: [link](https://im4.ezgif.com/tmp/ezgif-4-2cac32354d.gif) There you can see this demo after startup and how I resize it. Given the code above and a random pptx-file, this should be reproducible. – humanbydefinition Feb 10 '23 at 22:25
  • Ok, now I understand: the problem comes from the fact that the *contents* of the ActiveX control always respect the aspect ratio of the file, but the paint surface isn't drawn for the area that is not covered by the scaled size. Unfortunately, I'm not on Windows, and trying to solve this problem requires some inspection on how the QAxWidget behaves. The simplest idea that comes to mind is to try to override the `paintEvent()` in a QAxWidget subclass, but from what I can understand, that is actually a container widget that creates an internal control, so painting will probably fail. – musicamante Feb 10 '23 at 22:48
  • Still, it doesn't hurt to try: can you create a QAxWidget subclass, override its `paintEvent()` and just add a `pass` there? If it still paints the AX control (as I'm afraid), then that's not an option, but if it does, that might allow a possible solution. Also, while you're there, try to add a `print(self.children())` and post the results. Since painting might not happen at all, you could add that `print()` in the `resizeEvent()` (which will obviously *not* resize the control unless you call the base `super().resizeEvent(event)`. – musicamante Feb 10 '23 at 22:51
  • @musicamante I tried your approach but the `paintEvent()` of the `QAxWidget`-subclass was never called and thus no print. Maybe I did something wrong? Nonetheless I just found another approach I am happy with for now. I don't know the height of the space the powerpoint occupies, only the full height of the whole `QAxWidget`. But I found out that the ratio for a powerpoint is 16:9 and by setting this ratio to the `QAxWidget` as fixed height inside the `eventFilter(widget, event)` of the `QMainWindow`, I've been able to get rid of the glitches and still remain resizeability. – humanbydefinition Feb 12 '23 at 01:13
  • @musicamante Also, I set a vertical `QSpacerItem` below the `QAxWidget`, which I guess overlays those glitches appearing below. Same can be applied to the glitches on the right. – humanbydefinition Feb 12 '23 at 01:14
  • If you didn't get the `paintEvent()`, that was expected (see the last part of my second to last comment). Using a layout system was my next suggestion, but I see that you found it on your own, so I'd suggest you to add your own answer with that. – musicamante Feb 12 '23 at 01:30

1 Answers1

0

I got rid of the glitches by installing an event filter to the QAxWidget using self.axWidget.installEventFilter(self).

This will call the eventFilter()-method of the QMainWindow which I set up like this: (ReportDefinitionTool is the subclass of QMainWindow here.)

    def eventFilter(self, widget: QWidget, event: QEvent):
        if event.type() == QEvent.Resize and widget is self.pptx_axwidget:
            self.pptx_axwidget.setFixedHeight(int(self.pptx_axwidget.width() / 16 * 9))

        return super(ReportDefinitionTool, self).eventFilter(widget, event)

Since the PowerPoint-presentation is displayed in a 16:9 format, this will make sure the QAxWidget does only occupy this space. The glitchy space from the initial question came from the unused space of the QAxWidget.