[PyQt] how to use the button to control the program

David Hoese dhoese at gmail.com
Fri Aug 9 04:40:16 BST 2013


Hi Harry,

Sorry, I think you misunderstood me. When I said you might be able to
use a loop that didn't return I meant you could use it with a
QTimer...BUT I had time to try this and it doesn't work. Before I give
you more hints I'll say it again, research the Qt Event Loop, but at
this point this example might end up being a good tutorial.

First, I'll point out that you should never have an endless loop in a
program that doesn't have any delay (like your while loop). Normally you
would add a time.sleep() call, but with GUIs you usually want to use the
timers provided by the GUI Framework (Qt). My next example will work for
the simple program you have, but I advise you to keep reading my
response and learn about QThreads because they are much more flexible.

Change:
startbtn.clicked.connect(self.newtime)
to:
startbtn.clicked.connect(self.start_timer)

Create the start_timer method:
    def start_timer(self):
        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.newtime)
        self.timer.start(2000)

Remove the "while True:" from your newtime method (obviously, fix the
indentation).

This will work and will seem to be perfect until your example start
communicating with your hardware (I'm guessing that is your intention).
If the "newtime" method in your example starts taking more time to
complete (from talking to hardware for example), your GUI will seem to
be unresponsive. You can see this if you add a time.sleep(10) call or
similar to the end of the "newtime" method. Whenever the newtime method
is running you won't be able to click the quit button. This is because
of how GUI event loops work, they can only do one thing at a time
(update label text, respond to a button click, call a function, etc.).
The long term solution to this is to use QThreads, here's a working
example of how your example might look using QThreads (if you are
unfamiliar with how linux/unix threads work, please research them):

import sys
from PyQt4 import QtGui
from PyQt4 import QtCore
import time

class Example(QtGui.QWidget):
    def __init__(self, hardware_obj):
        super(Example, self).__init__()
        self.initUI()
        self.hardware_obj = hardware_obj

    def initUI(self):
        startbtn = QtGui.QPushButton('Start', self)
        startbtn.setToolTip('Click it to <b>start</b> the program')
        startbtn.clicked.connect(self.connect_hardware_signal)
        startbtn.resize(startbtn.sizeHint())
        startbtn.move(200, 340)
        nowtime = '0000-00-00 00:00:00'
        self.timeEdit = QtGui.QLabel(str(nowtime),self)
        self.timeEdit.move(110,30)

        qbtn = QtGui.QPushButton('Quit', self)
        qbtn.setToolTip('Click it and <b>quit</b> the program')
        qbtn.clicked.connect(QtCore.QCoreApplication.instance().quit)
        qbtn.resize(qbtn.sizeHint())
        qbtn.move(400, 340)

        self.setGeometry(300, 200, 600, 400)
        self.setWindowTitle('Battery status')
        self.show()

    def connect_hardware_signal(self):
        self.hardware_obj.new_time.connect(self.handle_new_hardware_time)

    def handle_new_hardware_time(self, time_int):
        nowtime = time.strftime('%Y-%m-%d %H:%M:%S', time_int)
        self.timeEdit.setText(str(nowtime))

class MyObject(QtCore.QObject):
    # the signal will be emitted with
    # one python object as an argument
    new_time = QtCore.pyqtSignal(object)

    def run(self):
        # this is running in a new thread affinity and not blocking your GUI
        # set up timer and start the timer
        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.do_stuff)
        self.timer.start(0)

    def do_stuff(self):
        # Gets called as fast as possible
        # The sleep is mimicking the time it would take to talk to
        # your hardware
        time.sleep(10)
        self.new_time.emit(time.localtime())

def main():
    app = QtGui.QApplication(sys.argv)

    # create a separate thread to do the hardwork
    # QThread is just a *handle* to the other thread
    my_thread = QtCore.QThread()
    my_obj = MyObject()
    # we move our object to the other thread before we use it
    my_obj.moveToThread(my_thread)
    my_thread.started.connect(my_obj.run)
    my_thread.start()

    ex = Example(my_obj)

    app.exec_()

    # wait for the threads to finish
    # if you have hardware resources allocated in your thread you'll
    # need to go through the work of figuring out how to close them
    # properly
    # depending on your OS you may get a warning here that you killed
    # a thread while it was still running. I'll leave it up to you how
    # you want to handle that

    return 0

if __name__ == '__main__':
    sys.exit(main())


Good luck.

-Dave

On 8/8/13 5:25 PM, 吉文 wrote:
> Hello,Dave, thank you very much for helping me, I am so sorry for 
> replying later, I did the hardware of my project in these days.
> According to your hints, I changed my code, but when I click the start 
> button, the program is dead, no any response. The following is my code.
> import sys
> from PyQt4 import QtGui
> from PyQt4 import QtCore
> import time
> import functools
> 
> class Example(QtGui.QWidget):
>      def __init__(self):
>          super(Example, self).__init__()
>          self.initUI()
> 
>      def initUI(self):
> 
>          startbtn = QtGui.QPushButton('Start', self)
>          startbtn.setToolTip('Click it to <b>start</b> the program')
>          startbtn.clicked.connect(self.newtime)
>          startbtn.resize(startbtn.sizeHint())
>          startbtn.move(200, 340)
>          nowtime = '0000-00-00 00:00:00'
>          self.timeEdit = QtGui.QLabel(str(nowtime),self)
>          self.timeEdit.move(110,30)
> 
>          qbtn = QtGui.QPushButton('Quit', self)
>          qbtn.setToolTip('Click it and <b>quit</b> the program')
>          qbtn.clicked.connect(QtCore.QCoreApplication.instance().quit)
>          qbtn.resize(qbtn.sizeHint())
>          qbtn.move(400, 340)
> 
>          self.setGeometry(300, 200, 600, 400)
>          self.setWindowTitle('Battery status')
>          self.show()
> 
>      def newtime(self):
>          while True:
>              nowtime = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
>              self.timeEdit.setText(str(nowtime))
> def main():
> 
>      app = QtGui.QApplication(sys.argv)
>      ex = Example()
>      sys.exit(app.exec_())
> 
> if __name__ == '__main__':
>      main()
> If I remove 'while True:',it can work, but only show the local time, how 
> to fix the loop.
> Thank you in advance.
> Best regards
> Harry
> 
> 
> 2013/8/1 David Hoese <dhoese at gmail.com <mailto:dhoese at gmail.com>>
> 
>     The way a timer works, especially the singleShot you are using, is
>     that once it is time to act it calls the function pointer you've
>     provided. You *must* provide a callable. Every tick of the timer is
>     just calling that callable with no parameters. You could provide a
>     function pointer to a function that will never return, but I think
>     this will block your GUI event loop (see below). I'd have to test it
>     myself, but don't really have the time right now, sorry.
> 
>     If I understand you correctly, what you would like to happen is:
>     1. User clicks "Start"
>     2. newtime is called
>     3. newtime "creates" a new value
>     4. The time label (or other widget) gets updated with this new value
> 
>     If you are just updating the time based on a certain interval, use a
>     QTimer without the singleShot, similar to what you are doing in your
>     example.
> 
>     If updating a time is just an example in the code you've provided
>     and you would like to update a value I would suggest still using a
>     QTimer or using threads (QThread). It depends on why your "newtime"
>     function needs to be a long running loop that never returns.
> 
>     If it is a long running loop that never returns because it
>     continuously updates the values, then you could change newtime to be
>     "per iteration", use QTimers, and call newtime every "tick" of the
>     timer. So go from a function like this:
> 
>          def newtime(self):
>              i = 0
>              while True:
>                  timeEdit.setText(str(i))
>                  i += 1
> 
>     to:
> 
>          def newtime(self):
>              timeEdit.setText(str(self.i))
>              self.i += 1
> 
>     If your loop is long running because it blocks on system IO (network
>     communications, file reading, database, etc.) then you should use
>     QThreads. Any type of long running code that runs in the main/GUI
>     thread will block your GUI and the user won't be able to interact. I
>     suggest researching PyQt Event loops, QThreads, and after that read
>     http://blog.qt.digia.com/blog/__2010/06/17/youre-doing-it-__wrong/
>     <http://blog.qt.digia.com/blog/2010/06/17/youre-doing-it-wrong/>
>     I've posted on this mailing list before helping users with their
>     QThread problems, if you look those up they may help.
> 
>     Let me know what you decide to do and hopefully this rambling made
>     sense.
> 
>     -Dave
> 
> 
>     On 7/31/2013 7:10 PM, 吉文 wrote:
> 
>         Hi, Dave, thank you very much for helping me. I am a newcomer to
>         pyqt4,
>         so maybe my questions are low-level.Thanks again. If the
>         function does
>         not have returns, can the button connect the function? The newtime
>         function realizes a loop to change the time every 1s, there is no
>         return. I want to click the 'start' button to begin the
>         function, how to
>         realize that?
>         Thanks in advance
>         -Harry
> 
> 
>         2013/7/31 David Hoese <dhoese at gmail.com
>         <mailto:dhoese at gmail.com> <mailto:dhoese at gmail.com
>         <mailto:dhoese at gmail.com>>>
> 
> 
>              Hi Harry,
> 
>              There are a couple missing things in your program. First,
>         if you
>              read the exception you notice the line of the error and some
>              information about what's happening. The message you are
>         getting is
>              saying that the argument to "connect" is not callable
>         (meaning a
>              function or method or object with a __call__ method). If
>         you look at
>              that line you'll see you are calling your "newTime"
>         function and
>              passing what it returns. Instead what you want is to pass the
>              function itself. Right above that you use a lambda which
>         will work,
>              but I would recommend using the functools.partial function:
>         http://docs.python.org/2/____library/functools.html#____functools.partial
>         <http://docs.python.org/2/__library/functools.html#__functools.partial>
>         <http://docs.python.org/2/__library/functools.html#__functools.partial
>         <http://docs.python.org/2/library/functools.html#functools.partial>>
> 
> 
>              You will want to remove the timer call that you have right
>         before
>              declaring the button (line 19) otherwise the timer starts
>         before the
>              "Start" button is clicked.
> 
>              I think you could also use a QTimer without doing a
>         singleShot, but
>              what you have works.
> 
>              Please CC me in any replies. Good luck.
> 
>              -Dave
> 
> 
>              On 7/31/13 6:00 AM, pyqt-request at __riverbankcomput__ing.com
>         <http://riverbankcomputing.com>
> 
>              <mailto:pyqt-request at __riverbankcomputing.com
>         <mailto:pyqt-request at riverbankcomputing.com>> wrote:
> 
>                  Hi,all, I want to use a button to control when the
>         program start
>                  in pyqt4,
>                  in other words, when I press the start button, the
>         program will
>                  work. I
>                  wrote some code, but it doesn't work. please help me to
>         correct
>                  it. Thanks
>                  in advance.
> 
>                  Best regards
>                  Harry
> 
>                  import sys
>                  from PyQt4 import QtGui
>                  from PyQt4 import QtCore
>                  import time
> 
>                  class Example(QtGui.QWidget):
>                        def __init__(self):
>                            super(Example, self).__init__()
>                            self.initUI()
> 
>                        def initUI(self):
> 
>                            nowtime = '0000-00-00 00:00:00'
>                            timeEdit = QtGui.QLabel(str(nowtime),____self)
>                            timeEdit.resize(timeEdit.____sizeHint())
>                            timeEdit.move(110,30)
> 
> 
>                   
>         QtCore.QTimer.singleShot(1000,____lambda:self.newtime(__timeEdit))
> 
> 
>                            startbtn = QtGui.QPushButton('Start', self)
>                            startbtn.setToolTip('Click it to <b>start</b> the
>                  program')
>                           
>         startbtn.clicked.connect(self.____newtime(timeEdit))
>                            startbtn.resize(startbtn.____sizeHint())
> 
>                            startbtn.move(200, 340)
> 
>                            qbtn = QtGui.QPushButton('Quit', self)
>                            qbtn.setToolTip('Click it and <b>quit</b> the
>         program')
> 
>                   
>         qbtn.clicked.connect(QtCore.____QCoreApplication.instance().____quit)
> 
>                            qbtn.resize(qbtn.sizeHint())
>                            qbtn.move(400, 340)
> 
>                            self.setGeometry(300, 200, 600, 400)
>                            self.setWindowTitle('Battery status')
>                            self.show()
> 
>                        def newtime(self,timeEdit):
>                            nowtime = time.strftime('%Y-%m-%d %H:%M:%S',
>                  time.localtime())
>                            timeEdit.setText(str(nowtime))
> 
>                   
>         QtCore.QTimer.singleShot(1000,____lambda:self.newtime(__timeEdit))
> 
> 
>                  def main():
> 
>                        app = QtGui.QApplication(sys.argv)
>                        ex = Example()
>                        sys.exit(app.exec_())
> 
>                  if __name__ == '__main__':
>                        main()
> 
>                  *when executing the program, there is something wrong:*
>                  **
>                  *Traceback (most recent call last):
> 
>                      File "C:\Python\calendar.py", line 62, in <module>
>                        main()
>                      File "C:\Python\calendar.py", line 57, in main
>                        ex = Example()
>                      File "C:\Python\calendar.py", line 15, in __init__
>                        self.initUI()
>                      File "C:\Python\calendar.py", line 32, in initUI
>                        startbtn.clicked.connect(self.____newtime(timeEdit))
> 
>                  TypeError: connect() slot argument should be a callable
>         or a
>                  signal, not
>                  'NoneType'*
> 
> 
> 
> 



More information about the PyQt mailing list