PyQt5 Connect not working when embedding Widget inside Qt C++ Application

302 views Asked by At

I am trying to create an application in which user can create screens( .ui and generate corresponding ui_.py file using pyuic5.bat ) and then write scripts ( in .py file ) for them in PyQt5. I want to embed these python scripts in my Qt based C++ application which will load these Scripts( because my rest of the application is in Qt ).

I am able to embed this screens created by user into the application but for some strange reason, the "connect" written inside the scripts fail. [ I have tried a timer's timeout and still the slots don't get called. ]

Could someone tell me why this might be happening or at-least a way to get some debug logs which might help me knowing what is going wrong.

ui_Form1.py:

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_Form(object):
    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.resize(400, 300)
        self.pushButton = QtWidgets.QPushButton(Form)
        self.pushButton.setGeometry(QtCore.QRect(40, 60, 75, 23))
        self.pushButton.setObjectName("pushButton")
        self.pushButton_2 = QtWidgets.QPushButton(Form)
        self.pushButton_2.setGeometry(QtCore.QRect(170, 60, 75, 23))
        self.pushButton_2.setObjectName("pushButton_2")

        self.retranslateUi(Form)
        QtCore.QMetaObject.connectSlotsByName(Form)

    def retranslateUi(self, Form):
        _translate = QtCore.QCoreApplication.translate
        Form.setWindowTitle(_translate("Form", "Form"))
        self.pushButton.setText(_translate("Form", "PushButton"))
        self.pushButton_2.setText(_translate("Form", "PushButton"))

Form1.py:

import sys
sys.dont_write_bytecode = True

from PyQt5 import QtCore, QtGui, QtWidgets

from ui_Form1 import Ui_Form

class Form(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(Form, self).__init__(parent)
        self.ui=Ui_Form();
        self.ui.setupUi(self)
        self.ui.pushButton.clicked.connect(self.changeColor)

    def changeColor(self):
        self.ui.pushButton_2.setStyleSheet( "background-color: rgb(255, 255, 0); " )

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv);
    ex = Form();
    ex.show();
    sys.exit(app.exec_())

Simple Qt-C++ code that basically embeds the script using Sip:

#include <Python.h>
#include <QApplication>
#include <QLayout>
#include <QWidget>
#include <QMessageBox>
#include <QFileInfo>
#include <QXmlStreamReader>
#include <QMainWindow>
#include <QDesktopWidget>

PyObject* call_function(PyObject *callable, PyObject *args)
{
    PyObject *result = NULL, *pArgs;

    if (callable == NULL)
    {
        return NULL;
    }

    if (PyCallable_Check(callable))
    {
        if (args == NULL)
            pArgs = PyTuple_New(0);
        else
            pArgs = args;

        result = PyObject_CallObject(callable, pArgs);

        if (args == NULL)
            Py_XDECREF(pArgs);
            // pDict and pFunc are borrowed and must not be Py_DECREF-ed.

        if (result == NULL)
        {
            PyErr_Clear();
        }
    }

    return result;
}

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    if( argc < 3 ){
        QMessageBox::warning( 0 , "Error" , "Pass Name of File as argument\n eg. <exe Name>.exe <Full FileName(.py)> <ClassName>" );
        exit(0);
    }

    QWidget *parentWidget = new QWidget;
    parentWidget->setWindowTitle( "HMI - Viewer");

    Py_DontWriteBytecodeFlag = 1;
    Py_Initialize();

    QFileInfo objFile( argv[1] );
    QString strFileName = objFile.baseName();
    QString strFilePath = objFile.absolutePath();
    QString strClassName = argv[2];

    PyRun_SimpleString("import os, sys\n");
    PyRun_SimpleString(QString("sys.path.append('"+QString(strFilePath)+"')\n").toStdString().c_str());

    PyObject *pyModule = PyImport_ImportModule(strFileName.toStdString().c_str());

    if (!pyModule) {
        QMessageBox::warning( 0 , "Error" , "Cannot create module" );
        return 1;
    }

    PyObject *pyDict = PyModule_GetDict(pyModule);
    /* pDict is a borrowed reference */

    Py_XDECREF(pyModule);


    QString strString = QString(
                QString("import PyQt5.QtGui, PyQt5.QtWidgets, sip\n"
                                 "def __embedded_factory__wrap_widget__(parentPtr):\n"
                                 "    parent = sip.wrapinstance(parentPtr, PyQt5.QtWidgets.QWidget)\n"
                                 "    return parent\n"
                                 "def __embedded_factory__create__(parent):\n"
                                 "    widget = " ) + strClassName + QString("(parent)\n"
                                 "    return sip.unwrapinstance(widget)\n" )
                );

    PyRun_String(strString.toStdString().c_str(), Py_file_input, pyDict, pyDict);
    PyObject *pyWrapper = PyDict_GetItemString(pyDict, "__embedded_factory__wrap_widget__");
    PyObject *pyFactory = PyDict_GetItemString(pyDict, "__embedded_factory__create__");

    if (!pyFactory || !pyWrapper) {
        QMessageBox::warning( 0 , "Error" , "Could Not create pyFactory or pyWrapper" );
        return 2;
    }

    PyObject *wrapperArgs = PyTuple_New(1);
    PyObject *pyParentWidgetPtr = PyLong_FromVoidPtr(parentWidget);
    PyTuple_SetItem(wrapperArgs, 0, pyParentWidgetPtr);

    PyObject *pyParentWidget = call_function(pyWrapper, wrapperArgs);
    Py_XDECREF(pyWrapper);
    Py_XDECREF(wrapperArgs);

    PyObject *args = PyTuple_New(1);
    PyTuple_SetItem(args, 0, pyParentWidget);

    QGridLayout *layout = new QGridLayout(parentWidget);
    layout->setContentsMargins(0, 0, 0, 0);

    PyObject *pyWidgetPtr = call_function(pyFactory, args);

    if (pyWidgetPtr)
    {
        // Extract the C++ pointer from the PyObject.
        QWidget *widget = (QWidget*)PyLong_AsVoidPtr(pyWidgetPtr);
        if( widget){
        }else{
            QMessageBox::warning( 0 , "Error" , "Position17- Widget NOT Created" );
        }
        Py_XDECREF(pyWidgetPtr);
        layout->addWidget(widget, 0, 0);
    }
    else
    {
        QMessageBox::warning( 0 , "Error" , "Could not create Widget instance" );
        return 2;
    }

    Py_XDECREF(args);
    Py_XDECREF(pyFactory);

    QRect objRect = QApplication::desktop()->screenGeometry();
    objRect.setY(objRect.y() + 20 );
    objRect.setHeight(objRect.height() - 10 - 45 );
    parentWidget->setGeometry(objRect);
    parentWidget->show();
    int result = app.exec();

    Py_Finalize();
    return result;
}

Run the applications as: Test.exe D:/Test/Form1.py Form

System under consideration is - Windows 7 , 64 bit machine, PyQt5 , Qt 5.4.0 ( 64 bit ) , MSVC 2012

PS: If I run these scripts using the Python directly, the scrips work fine as they are simple PyQt5 Scripts.

e.g. python.exe D:/Test/Form1.py Form Works Fine ( i.e. the color of pushButton_2 changes in color )

0

There are 0 answers