How to disable button in PyQT5 while another method is running to prevent user to click it and restart the method multiple times? - pyqt5

I would like to know how to disable the button while another method is running by that button, and then re enable it when that method is finished. I tried with the sample code, but that logic does not work, it still clickable and only becomes disabled when the loop finishes in another method. If I click it multiple times during the while loop, then it restart it again and again.
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton
import sys
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Demo")
self.button = QPushButton("Press me!")
self.button.clicked.connect(self.the_button_was_clicked)
self.setCentralWidget(self.button)
def the_button_was_clicked(self):
self.button.setText("You already pressed it.")
self.runner()
self.setWindowTitle("Changed title")
def runner(self):
self.button.setEnabled(False)
i = 0
while i < 5000000:
i = i+1
print(i, end=" ")
self.button.setEnabled(True)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()

You have 2 problems.
Almost any UI based toolkit uses an event loop, meaning that it has a while-like loop that waits for any event in order to react; as long as you run a blocking function in the same thread of that event loop, it means that that loop is not able to process its events, which will be queued instead and lately processed as soon as control returns to it; among these events there are paint events (those responsible of painting the UI) and input events (those coming from the user interaction); your while loop blocks both of them, meaning that the button will not be displayed as disabled, and all input events will be queued and processed only after the function returns;
the Python buffer (used by print()) is somehow limited, which can block furthermore anything else;
Since Python doesn't allow direct concurrence, you only have two choices: periodically ensure that the event loop process its events (see QCoreApplication.processEvents()), or use multiprocessing capabilities (which can be very tricky if you need UI interaction).
For this simple case, the suggested solution is the former one (including skipping the buffer at all until the string is completed):
def runner(self):
self.button.setEnabled(False)
QApplication.processEvents()
i = 0
print('started')
while i < 5000000:
i = i+1
if not i % 100000:
QApplication.processEvents()
print('finished')
self.button.setEnabled(True)
Remember that processEvents() is possible and tolerated, but you should not use it without awareness: it should only be used when you really know what you are doing and any other alternative is not a viable option. If the processing does not directly deal with CPU-bound aspects (eg. IO management, like file system operation or network retrieval), you should consider threading instead, and always use QThreads and custom signals whenever any result of the processing requires interaction with UI elements, since widgets are NOT thread-safe.

Related

How to close a QInputDialog with after a defined amount of time

I'm currently working on an application that run in the background and sometime create an input dialog for the user to answer. If the user doesn't interact, I'd like to close the dialog after 30 seconds. I made a QThread that act like a timer and the "finished" signal should close the dialog. I unfortunately cannot find a way to close it.
At this point I'm pretty much lost. I completely new to QThread and a beginner in PyQt5
Here is a simplified version of the code (we are inside a class running a UI):
def Myfunction(self,q):
# q : [q1,q2,q3]
self.popup = counter_thread()
self.popup.start()
self.dial = QInputDialog
self.popup.finished.connect(self.dial.close)
text, ok = self.dial.getText(self, 'Time to compute !', '%s %s %s = ?'%(q[0], q[2], q[1]))
#[...]
I tried ".close()" and others but i got this error message:
TypeError: close(self): first argument of unbound method must have type 'QWidget'
I did it in a separated function but got the same problem...
You cannot close it because the self.dial you created is just an alias (another reference) to a class, not an instance.
Also, getText() is a static function that internally creates the dialog instance, and you have no access to it.
While it is possible to get that dialog through some tricks (installing an event filter on the QApplication), there's no point in complicating things: instead of using the static function, create a full instance of QInputDialog.
def Myfunction(self,q):
# q : [q1,q2,q3]
self.popup = counter_thread()
self.dial = QInputDialog(self) # <- this is an instance!
self.dial.setInputMode(QInputDialog.TextInput)
self.dial.setWindowTitle('Time to compute !')
self.dial.setLabelText('%s %s %s = ?'%(q[0], q[2], q[1]))
self.popup.finished.connect(self.dial.reject)
self.popup.start()
if self.dial.exec():
text = self.dial.textValue()
Note that I started the thread just before showing the dialog, in the rare case it may return immediately, and also because, for the same reason, the signal should be connected before starting it.

GtkTreeView stops updating unless I change the focus of the window

I have a GtkTreeView object that uses a GtkListStore model that is constantly being updated as follows:
Get new transaction
Feed data into numpy array
Convert numbers to formatted strings, store in pandas dataframe
Add updated token info to GtkListStore via GtkListStore.set(titer, liststore_cols, liststore_data), where liststore_data is the updated info, liststore_cols is the name of the columns (both are lists).
Here's the function that updates the ListStore:
# update ListStore
titer = ls_full.get_iter(row)
liststore_data = []
[liststore_data.append(df.at[row, col])
for col in my_vars['ls_full'][3:]]
# check for NaN value, add a (space) placeholder is necessary
for i in range(3, len(liststore_data)):
if liststore_data[i] != liststore_data[i]:
liststore_data[i] = " "
liststore_cols = []
[liststore_cols.append(my_vars['ls_full'].index(col) + 1)
for col in my_vars['ls_full'][3:]]
ls_full.set(titer, liststore_cols, liststore_data)
Class that gets the messages from the websocket:
class MyWebsocketClient(cbpro.WebsocketClient):
# class exceptions to WebsocketClient
def on_open(self):
# sets up ticker Symbol, subscriptions for socket feed
self.url = "wss://ws-feed.pro.coinbase.com/"
self.channels = ['ticker']
self.products = list(cbp_symbols.keys())
def on_message(self, msg):
# gets latest message from socket, sends off to be processed
if "best_ask" and "time" in msg:
# checks to see if token price has changed before updating
update_needed = parse_data(msg)
if update_needed:
update_ListStore(msg)
else:
print(f'Bad message: {msg}')
When the program first starts, the updates are consistent. Each time a new transaction comes in, the screen reflects it, updating the proper token. However, after a random amount of time - seen it anywhere from 5 minutes to over an hour - the screen will stop updating, unless I change the focus of the window (either activate or inactive). This does not last long, though (only enough to update the screen once). No other errors are being reported, memory usage is not spiking (constant at 140 MB).
How can I troubleshoot this? I'm not even sure where to begin. The data back-ends seem to be OK (data is never corrupted nor lags behind).
As you've said in the comments that it is running in a separate thread then i'd suggest wrapping your "update liststore" function with GLib.idle_add.
from gi.repository import GLib
GLib.idle_add(update_liststore)
I've had similar issues in the past and this fixed things. Sometimes updating liststore is fine, sometimes it will randomly spew errors.
Basically only one thread should update the GUI at a time. So by wrapping in GLib.idle_add() you make sure your background thread does not intefer with the main thread updating the GUI.

Twisted deferreds block when URI is the same (multiple calls from the same browser)

I have the following code
# -*- coding: utf-8 -*-
# 好
##########################################
import time
from twisted.internet import reactor, threads
from twisted.web.server import Site, NOT_DONE_YET
from twisted.web.resource import Resource
##########################################
class Website(Resource):
def getChild(self, name, request):
return self
def render(self, request):
if request.path == "/sleep":
duration = 3
if 'duration' in request.args:
duration = int(request.args['duration'][0])
message = 'no message'
if 'message' in request.args:
message = request.args['message'][0]
#-------------------------------------
def deferred_activity():
print 'starting to wait', message
time.sleep(duration)
request.setHeader('Content-Type', 'text/plain; charset=UTF-8')
request.write(message)
print 'finished', message
request.finish()
#-------------------------------------
def responseFailed(err, deferred):
pass; print err.getErrorMessage()
deferred.cancel()
#-------------------------------------
def deferredFailed(err, deferred):
pass; # print err.getErrorMessage()
#-------------------------------------
deferred = threads.deferToThread(deferred_activity)
deferred.addErrback(deferredFailed, deferred) # will get called indirectly by responseFailed
request.notifyFinish().addErrback(responseFailed, deferred) # to handle client disconnects
#-------------------------------------
return NOT_DONE_YET
else:
return 'nothing at', request.path
##########################################
reactor.listenTCP(321, Site(Website()))
print 'starting to serve'
reactor.run()
##########################################
# http://localhost:321/sleep?duration=3&message=test1
# http://localhost:321/sleep?duration=3&message=test2
##########################################
My issue is the following:
When I open two tabs in the browser, point one at http://localhost:321/sleep?duration=3&message=test1 and the other at http://localhost:321/sleep?duration=3&message=test2 (the messages differ) and reload the first tab and then ASAP the second one, then the finish almost at the same time. The first tab about 3 seconds after hitting F5, the second tab finishes about half a second after the first tab.
This is expected, as each request got deferred into a thread, and they are sleeping in parallel.
But when I now change the URL of the second tab to be the same as the one of the first tab, that is to http://localhost:321/sleep?duration=3&message=test1, then all this becomes blocking. If I press F5 on the first tab and as quickly as possible F5 on the second one, the second tab finishes about 3 seconds after the first one. They don't get executed in parallel.
As long as the entire URI is the same in both tabs, this server starts to block. This is the same in Firefox as well as in Chrome. But when I start one in Chrome and another one in Firefox at the same time, then it is non-blocking again.
So it may not neccessarily be related to Twisted, but maybe because of some connection reusage or something like that.
Anyone knows what is happening here and how I can solve this issue?
Coincidentally, someone asked a related question over at the Tornado section. As you suspected, this is not an "issue" in Twisted but rather a "feature" of web browsers :). Tornado's FAQ page has a small section dedicated to this issue. The proposed solution is appending an arbitrary query string.
Quote of the day:
One dev's bug is another dev's undocumented feature!

What's the equivalent of moment-yielding (from Tornado) in Twisted?

Part of the implementation of inlineCallbacks is this:
if isinstance(result, Deferred):
# a deferred was yielded, get the result.
def gotResult(r):
if waiting[0]:
waiting[0] = False
waiting[1] = r
else:
_inlineCallbacks(r, g, deferred)
result.addBoth(gotResult)
if waiting[0]:
# Haven't called back yet, set flag so that we get reinvoked
# and return from the loop
waiting[0] = False
return deferred
result = waiting[1]
# Reset waiting to initial values for next loop. gotResult uses
# waiting, but this isn't a problem because gotResult is only
# executed once, and if it hasn't been executed yet, the return
# branch above would have been taken.
waiting[0] = True
waiting[1] = None
As it is shown, if in am inlineCallbacks-decorated function I make a call like this:
#inlineCallbacks
def myfunction(a, b):
c = callsomething(a)
yield twisted.internet.defer.succeed(None)
print callsomething2(b, c)
This yield will get back to the function immediately (this means: it won't be re-scheduled but immediately continue from the yield). This contrasts with Tornado's tornado.gen.moment (which isn't more than an already-resolved Future with a result of None), which makes the yielder re-schedule itself, regardless the future being already resolved or not.
How can I run a behavior like the one Tornado does when yielding a dummy future like moment?
The equivalent might be something like a yielding a Deferred that doesn't fire until "soon". reactor.callLater(0, ...) is generally accepted to create a timed event that doesn't run now but will run pretty soon. You can easily get a Deferred that fires based on this using twisted.internet.task.deferLater(reactor, 0, lambda: None).
You may want to look at alternate scheduling tools instead, though (in both Twisted and Tornado). This kind of re-scheduling trick generally only works in small, simple applications. Its effectiveness diminishes the more tasks concurrently employ it.
Consider whether something like twisted.internet.task.cooperate might provide a better solution instead.

Run Python with IDLE on a Windows machine, put a part of the code on background so that IDLE is still active to receive command

class PS():
def __init__(self):
self.PSU_thread=Process(target=self.read(199),)
self.PSU_thread.start()
def read():
while running:
"read the power supply"
def set(current):
"set the current"
if __name__ == '__main__':
p=PS()
Basically the idea of the code is to read the data of the power supply and at the same time to have the IDLE active and can accept command to control it set(current). The problem we are having is once the object p is initialized, the while loop will occupy the IDLE terminal such that the terminal cannot accept any command any more.
We have consider to create a service, but does it mean we have to make the whole code into a service?
Please suggest me any possible solutions, we want it to run but still be able to receive my command from IDLE.
Idle, as its name suggests, is a program development environment. It is not meant for production running, and you should not use it for that, especially not for what you describe. Once you have a program written, just run it with Python.
It sounds like what you need is a gui program, such as one based on tkinter. Here is a simulation of what I understand you to be asking for.
import random
import tkinter as tk
root = tk.Tk()
psu_volts = tk.IntVar(root)
tk.Label(root, text='Mock PSU').grid(row=0, column=0)
psu = tk.Scale(root, orient=tk.HORIZONTAL, showvalue=0, variable=psu_volts)
psu.grid(row=0, column=1)
def drift():
psu_volts.set(psu_volts.get() + random.randint(0, 8) - 4)
root.after(200, drift)
drift()
volts_read=tk.IntVar(root)
tk.Label(root, text='PSU Volts').grid(row=1, column=0)
tk.Label(root, textvariable=volts_read).grid(row=1, column=1)
def read_psu():
volts_read.set(psu_volts.get())
root.after(2000, read_psu)
read_psu()
lb = tk.Label(root, text="Enter 'from=n' or 'to=n', where n is an integer")
lb.grid(row=2, column=0, columnspan=2)
envar = tk.StringVar()
entry = tk.Entry(textvariable=envar)
entry.grid(row=3, column=0)
def psu_set():
try:
cmd, val = envar.get().split('=')
psu[cmd.strip()] = val
psu_volts.set((psu['to']-psu['from'])//2)
except Exception:
pass
envar.set('')
tk.Button(root, text='Change PSU', command=psu_set).grid(row=3, column=1)
root.mainloop()
Think of psu as a 'black box' and psu_volts.get and .set as the means of interacting with the box. You would have to substitute your in read and write code. Copy and save to a file. This either run it with Python or open in Idle to change it and run it.