Marc
Marc

Reputation: 25

Interact with continuously running Python Gui by external and exchangeable python script

my long term goal is to build a gui for an experiment in experimental physics which has a continuously running gui. By pushing a button I would like to be able to run a pyhton script of my choice which can interact with the running gui. For example setting a number to a spin box. I attached a starting project. A spinbox and a button. If the button is pressed a random number is set to the spinbox and as soon as the number in the spinbox changes, it prints the number. Is there a way to call a script (at the moment with a hard coded path) by pushing the button, which then sets the number in the gui to my choice. The content of the script (in this case the number which is set to the spin box) has to be editable during the runtime of the gui. If you could provide an example for this, I would be grateful and could build the rest myself.

Thanks in advance!

import sys
import random
from PyQt5 import QtCore, QtGui
from PyQt5.QtWidgets import QApplication, QWidget, QDoubleSpinBox, QPushButton

class GuiInteraction(QWidget):

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

    def initGUI(self):
        self.resize(400, 500)
        self.move(300, 300)
        self.setWindowTitle('Gui Interaction')
        self.doubleSpinBox = QDoubleSpinBox(self)
        self.doubleSpinBox.setGeometry(QtCore.QRect(120, 130, 120, 25))
        self.doubleSpinBox.setDecimals(5)
        self.doubleSpinBox.setMaximum(1000)
        self.doubleSpinBox.setObjectName("doubleSpinBox")
        self.pushButton = QPushButton("Run Script", self)
        self.pushButton.setGeometry(QtCore.QRect(100, 300, 100, 40))
        self.pushButton.setObjectName("pushButton")

    def CallBackFunctions(self):
        self.pushButton.clicked.connect(self.buttonClicked)
        self.doubleSpinBox.valueChanged.connect(self.valueChanged)

    def buttonClicked(self):
        self.doubleSpinBox.setValue(random.uniform(1, 200))

    def valueChanged(self):
        print(self.doubleSpinBox.value())


if __name__ == '__main__':

        app = QApplication(sys.argv)

        MyWindow = GuiInteraction()
        MyWindow.show()

        sys.exit(app.exec_())

Upvotes: 0

Views: 332

Answers (3)

Marc
Marc

Reputation: 25

I went for the solution of @armatita, even though I changed it a little. After a brief research __import__ seems to be replaced by the libimport libary, which I now use. The following lines have been added:

Header:

import importlib
import threading

and in the main function:

def buttonClicked(self):
    ScriptControlModule = importlib.import_module("ExternalScript")
    ScriptControlModule = importlib.reload(ScriptControlModule)
    ScriptControlThread = threading.Thread(target=ScriptControlModule.execute,args=(self,))
    ScriptControlThread.start()

My question is answered by the importlib lines. I also wanted to start the script in a subthread, so in the case it crashes due to a typo or anything else, the whole gui does not follow the crash.

The ExternalScript is in the same folder and named ExternalScript.py

import random
def execute(self):
    self.doubleSpinBox.setValue(random.uniform(1, 5))

Three simple lines of code. I can change these lines while running and get different values in the SpinBox. Works out perfectly!

Upvotes: 0

Brendan Abel
Brendan Abel

Reputation: 37559

One way would be to just communicate with an external script through stdin/stdout

my_script.py

def main():
    print '4.2'

if __name__ == '__main__':
    main()

gui_script.py

import subprocess

...

    def buttonClicked(self):
        try:
            value = float(subprocess.check_output(['python', 'my_script.py']).strip())
        except ValueError:
            value = 1.0
        self.doubleSpinBox.setValue(value)

If you need to pass arguments to your function you can just pass them as additional arguments to the subprocess.check_output call, and them read them from sys.argv (they'll all come in as strings, so you'd have to convert the types if necessary, or use a library like argparse, which can do the type-casting for you) in the called script.

Upvotes: 0

armatita
armatita

Reputation: 13475

I'm thinking you can call a FileDialog, pick a script and use:

   mod =  __import__(path)

, and than should the script be adequately built with a "run" function of some kind you can just launch it by:

   mod.run()

Check this question also.

Upvotes: 1

Related Questions