views:

284

answers:

2

I'm having a problem, where I wish to run several command line functions from a python program using a GUI. I don't know if my problem is specific to PyQt4 or if it has to do with my bad use of python code.

What I wish to do is have a label on my GUI change its text value to inform the user which command is being executed. My problem however, arises when I run several commands using a for loop. I would like the label to update itself with every loop, however, the program is not updating the GUI label with every loop, instead, it only updates itself once the entire for loop is completed, and displays only the last command that was executed.

I am using PyQt4 for my GUI environment. And I have established that the text variable for the label is indeed being updated with every loop, but, it is not actually showing up visually in the GUI.

Is there a way for me to force the label to update itself? I have tried the update() and repaint() methods within the loop, but they don't make any difference.

I would really appreciate any help. Thank you.

Ronny.

Here is the code I am using:

# -*- coding: utf-8 -*-
import sys, os
from PyQt4 import QtGui, QtCore
Gui = QtGui
Core = QtCore

# ================================================== CREATE WINDOW OBJECT CLASS
class Win(Gui.QWidget):
    def __init__(self, parent = None):
        Gui.QWidget.__init__(self, parent)

        # --------------------------------------------------- SETUP PLAY BUTTON
        self.but1 = Gui.QPushButton("Run Commands",self)
        self.but1.setGeometry(10,10, 200, 100)

        # -------------------------------------------------------- SETUP LABELS
        self.label1 = Gui.QLabel("No Commands running", self)
        self.label1.move(10, 120)

        # ------------------------------------------------------- SETUP ACTIONS
        self.connect(self.but1, Core.SIGNAL("clicked()"), runCommands)


# =======================================================  RUN COMMAND FUNCTION
def runCommands():
    for i in commands:
        win.label1.setText(i)       # Make label display the command being run
        print win.label1.text()     # This shows that the value is actually
                                    # changing with every loop, but its just not
                                    # being reflected in the GUI label
        os.system(i)

# ======================================================================== MAIN

# ------------------------------------------------------  THE TERMINAL COMMANDS
com1 = "espeak 'senntence 1'"
com2 = "espeak 'senntence 2'"
com3 = "espeak 'senntence 3'"
com4 = "espeak 'senntence 4'"
com5 = "espeak 'senntence 5'"
commands = (com1, com2, com3, com4, com5)

# --------------------------------------------------- SETUP THE GUI ENVIRONMENT
app = Gui.QApplication(sys.argv)
win = Win()
win.show()

sys.exit(app.exec_())
+2  A: 

The label gets updated all right, but the GUI isn't redrawn before the end of your loop.

Here's what you can do about it:

  • Move your long-running loop to a secondary thread, drawing the GUI is happening in the main thread.

  • Call app.processEvents() in your loop. This gives Qt the chance to process events and redraw the GUI.

  • Break up your loop and let it run using a QTimer with a timeout of 0.

Using a thread is the best option, but involves quite a bit more work than just calling processEvents. Doing it with a timer is the old fashioned way and is not recommanded anymore. (see the documentation)

Georg
Thank you so much!I found that app.processEvents() only changed the label after each command had already been completed, which was too late, and also skipped a few commands altoghether.What did work was creating a new QThread object with a run method, and calling the run method when the button is pressed. Is this what you meant?Here is the code I used. I have never learnt about threading before, so please tell me if I have applied it unwisely.class RunCommands(Core.QThread): def run(self): for i in commands: win.label1.setText(i) os.system(i)
Ronny
Ohh, geez, the comment didnt print out with the new lines and tabs i had entered :(
Ronny
@Ronny: That sounds about right, but you should call `start()` instead of `run()`.
Georg
Ooops, yep. Cheers :)
Ronny
+2  A: 

You have a basic misunderstanding of how such a GUI works. A Qt GUI has to run in an event loop of its own. Your loop runs instead, and the GUI can't do its work between the executions of your loop. That is, while your for loop is running the GUI code doesn't get CPU time and won't update.

You can set up a timer with an event, and execute your code in handlers of this event a set amount of time - this will solve your problem.

Eli Bendersky
Thanks for the explanation in the 1st paragraph of your reply. It now gives me a bit more of an understanding of what's going on, and helps me understand the reasoning behind Georg's answer a bit better too. However I don't understand the actual solution you are proposing. I am a very much a beginner in programming. Perhaps a simple outline sketch of the code that would be involved would be helpful. Even though I have now found a solution that seems to work, I would be very curious to learn your proposed alternative as well.
Ronny
@Ronny: learn about QTimer and look at some examples of its usage - I think it will become clear to you
Eli Bendersky