0

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_())
Marc
  • 25
  • 4
  • Maybe to give a few ideas where I think a solution could exist: By pushing the Button I execute a script, which runs in a sub thread of my gui. So it has access to the variables of the gui, but the external script can be modified everytime before the sub thread is stared. Unfortunately I do not know if this way could work and I don't know how to implement or test it. – Marc Mar 17 '16 at 20:47

3 Answers3

1

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.

Community
  • 1
  • 1
armatita
  • 12,825
  • 8
  • 48
  • 49
  • Sorry, I misunderstood your answer the first time. It works partly. Yes I can load and an external script, but unfortunately it does not load a new version while running. Means: Gui starts, external module is loaded once and never refreshed. Is there a way to force it to do so? – Marc Mar 17 '16 at 21:18
  • And I have to apologize again, yes of course, a brief research moved me to the importlib, where I can force the reload. Its working perfectly. Thanks a lot! I will post my full solution soon. – Marc Mar 17 '16 at 21:23
0

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.

Brendan Abel
  • 35,343
  • 14
  • 88
  • 118
  • Yep would work for single parameters, unfortunately the external script will become quite quickly so complex, that the return of the script cannot be only a single number. It probably has to push other buttons etc. Of course, this could be also initiated by a combination of numbers, but this is more work than directly accessing the main gui. – Marc Mar 18 '16 at 12:56
0

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!

Marc
  • 25
  • 4
  • You may have just gotten lucky here, but GUI elements generally aren't thread safe. Trying to modify GUI elements in a separate thread will lead to random crashes. Trying to create GUI elements in a separate thread usually causes instant crashes. – Brendan Abel Mar 18 '16 at 13:16
  • Somehow I can't confirm that issue. If I run ScriptControlModule in the same thread as the gui it does not crash at all? Do you have any idea how I can try to trigger this issue? – Marc Mar 20 '16 at 12:41