79423858

Date: 2025-02-08 20:20:46
Score: 2
Natty:
Report link

Here is an example that displays PySide6 widgets in a QDockWidget native C++/Qt6 Application.

I'm sure there's lots to criticize about the code, please keep in mind this is just code to show a concept not to provide a full fool-proof implementation.

The process is somewhat straight forward but not intuitive.

Lets start with a very generic and probably bad Python implementation in __init__.py in a folder called "example":

import pyQtEmbed
from PySide6 import QtCore, QtWidgets

pyQtEmbed.Console.PrintLog("Hello World!\n")

class MyViewProvider():
    def __init__(self):
        pass

    def show(self):
        gui = pyQtEmbed.GUI()
        self.widget = QtWidgets.QWidget()
        self.layout = QtWidgets.QVBoxLayout(self.widget)
        self.button = QtWidgets.QPushButton("Button") #MyWidget()
        self.layout.addWidget(self.button)

        print("Parent Before: " + str(self.widget.nativeParentWidget()) + "\n")
    
        gui.setTaskWidget(self.widget)
        self.widget.show()

        print("Parent After: " + str(self.widget.nativeParentWidget()) + "\n")

viewProvider=MyViewProvider()

We have a pyQtEmbed module that exports several items a console and GUI.

GUI provides a method to set the QDockWidget widget.

Here is the code that provides the implementation in C++

#include <iostream>
#include <string>
#include "pybind11/pybind11.h"
#include "include/PythonConsole.hpp"

#include "PythonGUI.hpp"

#include <QDockWidget>
#include <QWidget>
#include <QWindow>

namespace pyQtEmbed {

struct PyGUIPimpl {
    PyGUIPimpl() {}

    ~PyGUIPimpl() {}

    QDockWidget *taskPanelWidget;
};

struct PyGUIPimpl* PyGUI::d = nullptr;

// Constructor for Python
PyGUI::PyGUI() {
    if(nullptr == d)
        d = new PyGUIPimpl();
}

// Constructor for QtApplication (not exported to python)
PyGUI::PyGUI(QDockWidget* taskPanelWidget) {
    if(nullptr == d)
        d = new PyGUIPimpl();

    d->taskPanelWidget = taskPanelWidget;
}

PyGUI::~PyGUI() {
    if(d) {
        delete d;
        d=nullptr;
    }
}

std::string PyGUI::repr() {
    return std::string("GUI");
}

PyGUI& PyGUI::setTaskWidget(py::object taskWindow) {
    // Get the WindowID from the PySide6 Widget
    int windowID = py::cast<int>(taskWindow.attr("winId")());
    // Find the QtWidget from the WindowID
    QWidget* _taskWindow = QWidget::find(windowID);
    // Set the QDockWidget's widget to our Python Implemented widget
    d->taskPanelWidget->setWidget(_taskWindow);
    _taskWindow->show();

    return *this;
}

QDockWidget* PyGUI::getTaskWidget() {
    if(d) {
        return d->taskPanelWidget;
    }
    return nullptr;
}

};

The above code uses the Window ID from the QT object to find the QWidget associated to the windowID. This may not be the best method or the fastest, but it produced acceptable results. Also, we shouldn't need to worry much about the Python garbage collection since we are not retaining any pointers in memory for longer than the execution of setTaskWidget.

The last thing to do is to bind the C++ code to a python module:

/**
 * Binds the Python Console class to Python
 */
#include <pybind11/pybind11.h>
#include "include/PythonConsole.hpp"
#include "include/PythonGUI.hpp"

namespace py = pybind11;

PYBIND11_MODULE(pyQtEmbed, m) {
    py::class_<pyQtEmbed::Console>(m, "Console")
        .def_static("PrintLog", &pyQtEmbed::Console::PrintLog, py::arg("logString"));

    py::class_<pyQtEmbed::PyGUI>(m, "GUI")
        .def(py::init<>())
        .def("__repr__", &pyQtEmbed::PyGUI::repr, py::return_value_policy::take_ownership)
        .def("setTaskWidget", &pyQtEmbed::PyGUI::setTaskWidget);
}

I hope that this example helps someone else. Or if you have implemented something similar please share your methods.

Image of Qt App Running with Button Embedded

Reasons:
  • RegEx Blacklisted phrase (2.5): please share your
  • Long answer (-1):
  • Has code block (-0.5):
  • Low reputation (1):
Posted by: Brian Shea