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 )