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.