tkinter state of button for multi-clicks - multithreading

I have written a simple code. The button is disabled when process is running and it is enabled after the process finishes. It works as expected for the first time, but when I re-click the button (do not close the window), the button could not be disabled while the process is ok. Can anyone gives some hints.
import time
import tkinter as tk
import threading
win = tk.Tk()
class test(threading.Thread):
def __init__(self):
super(test, self).__init__()
def run(self):
print("start !!!")
time.sleep(3)
print("end !!!")
def monitor_state(thread):
if not thread.is_alive():
btn.configure(state=tk.NORMAL)
win.after(50, monitor_state, thread)
def py1_go():
btn.configure(state=tk.DISABLED)
test_thread = test()
test_thread.setDaemon(True)
test_thread.start()
monitor_state(test_thread)
btn = tk.Button(text='Display Results', command = py1_go)
btn.pack()
win.mainloop()

A Tkinter Button has three states : active, normal, disabled.
Snippet:
def run(self):
print("start !!!")
btn["state"] = "disabled"
time.sleep(3)
print("end !!!")
btn["state"] = "normal"

Related

Python kivy and multiprocessing bug

I am stuck on writing a very simple kivy gui with 2 buttons.
button_1 launches the countdown in multiprocessing. It works.
button_2 is supposed to end the countdown in multiprocessing. That does not work...
Can anyone please point out what I am doing wrong and why?
Thank you kindly in advance.
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
import multiprocessing
import time
class MyApp(App):
list_of_objects_in_class = []
def build(self):
layout = BoxLayout()
button_1 = Button(text='launch_countdown', on_press=self.launch_countdown)
button_2 = Button(text='end_the_countdown', on_press=self.end_the_countdown)
layout.add_widget(button_1)
layout.add_widget(button_2)
return layout
#staticmethod
def actual_countdown_function(*args):
print('I am launching the countdown!')
for count in range(5, 0, -1):
print(count)
time.sleep(1)
print('Countdown Finished!!!') # When I press end_the_countdown button, this is NOT supposed to be printed.
def launch_countdown(self, *args):
MyApp.list_of_objects_in_class.append(multiprocessing.Process(target=MyApp.actual_countdown_function, args=()))
MyApp.list_of_objects_in_class[0].start()
def end_the_countdown(self, *args):
print('I am terminating the countdown early!')
try:
MyApp.list_of_objects_in_class[0].terminate()
MyApp.list_of_objects_in_class.clear()
except:
pass
if __name__ == "__main__":
MyApp().run()
I tested to see if .terminate() works on my system, it works. The script below terminates successfully but the script above does NOT...
import multiprocessing
import time
def print_one():
time.sleep(3)
print('This does NOT get printed because proc.terminate() works on my Linux Mint OS')
def print_two():
print('This gets printed')
process_to_terminate = multiprocessing.Process(target=print_one, args=())
process_to_terminate.start()
process_to_keep_and_NOT_terminate = multiprocessing.Process(target=print_two, args=())
process_to_keep_and_NOT_terminate.start()
process_to_terminate.terminate() # Works and kills print_one function
It's better to use threads indeed to run such a small tasks.
Anyway, I think, the original problem could be solved by setting SIGTERM handler in a child process, because it seems to be ignoring SIGTERM by default:
def actual_countdown_function(self, *args):
+ signal.signal(signal.SIGTERM, lambda *_, **__: sys.exit(0))
print('I am launching the countdown!')
But, TBH, if you will really need a way to manage child process, I strongly recommend to use Locks/Conditions/Queues instead. They have almost the same API like all the threading primitives, but built to interact over pipes/shared-memory/sockets/etc.
after many different attempts, I was able to solve my own problem by using threading instead of multiprocessing.
Here is the code:
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
import threading
import time
class MyApp(App):
my_var = True
list_of_objects_in_class = []
def build(self):
layout = BoxLayout(size =(300, 300))
button_1 = Button(text='launch_countdown', on_press=self.launch_countdown)
button_2 = Button(text='end_the_countdown', on_press=self.end_the_countdown, height = "100", width = 140)
layout.add_widget(button_1)
layout.add_widget(button_2)
return layout
def actual_countdown_function(self, *args):
print('I am launching the countdown!')
x = 5
while x != 0:
new_var = self.my_var
if new_var == False:
break
time.sleep(1)
print(x, new_var)
x -=1
del(new_var)
print('Countdown Finished!!!') # When I press end_the_countdown button, this is NOT supposed to be printed.
def launch_countdown(self, *args):
t1 = threading.Thread(target=self.actual_countdown_function)
t1.start()
def end_the_countdown(self, *args):
print('I am terminating the countdown early!')
self.my_var = not self.my_var
print(self.my_var)
self.list_of_objects_in_class.clear()
if __name__ == "__main__":
MyApp().run()

slot to right click menu action does does not work

I have written the below code to which I finally managed to add menu but connecitn menu to a function doesnt seem to work:
import os
from PyQt5 import uic
from PyQt5 import QtWidgets
from PyQt5 import QtCore
FILE_LOCATION = os.path.dirname(os.path.realpath(__file__))
class MainDialogWindow(QtWidgets.QDialog):
def __init__(self):
super(MainDialogWindow,self).__init__()
ui_file = os.path.join(FILE_LOCATION, "example.ui")
self._ui = uic.loadUi(ui_file, self)
self.registerCallbacks()
self.initUI()
def initUI(self):
"""Initialize the UI.
"""
self.textBrowser.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
def registerCallbacks(self):
self.textBrowser.customContextMenuRequested.connect(self.context_menu)
# self.connect(self.textBrowser, QtCore.Signal('customContextMenuRequested(const QPoint &)'), self.context_menu)
def context_menu(self, pos):
menu = QtWidgets.QMenu(self)
action = menu.addAction("clear")
menu.exec_(self.mapToGlobal(pos))
action.trigered.connect(self.clear)
def clear(self):
"""Slot to claer text.
"""
print("clear")
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = MainDialogWindow()
window.show()
window.setGeometry(500, 300, 300, 300)
sys.exit(app.exec_())
please helpp,, I want call the clear function from the right click menu
I don't seem to understand how the menu.exec_() method works, that method blocks the execution of sequential tasks until the user selects a QAction from the QMenu. In your case, for example, until when you press "clear" and the triggered signal is emitted (note: you have a typo), but at that moment there is no connection, so the clear method will not be called. The solution is to make the connection before invoking the QMenu exec_():
def context_menu(self, pos):
menu = QtWidgets.QMenu(self)
action = menu.addAction("clear")
action.triggered.connect(self.clear)
menu.exec_(self.mapToGlobal(pos))

How to implement a "processing request"-message in TkInter

I'm trying to implement a popup-window, which should display over the main window, while an operation is taking place in the background. Pressing the button from the code below results in freezing the GUI for 10s without displaying any message and eventually making the button green. The freezing is normal, but I would like to have the popup displayed during the 10 seconds.
Any help would be appreciated! Thanks in advance!
import tkinter as tk
import time
class GUI(tk.Tk):
def __init__(self):
super().__init__()
self.button1 = tk.Button(text="Start", command=self.make_green)
self.button1.pack()
def popup(self):
tl = tk.Toplevel(self)
tl.transient()
tk.Label(tl, text="Painting green").pack()
tl.grab_set()
return tl
def make_green(self):
wait_popup = self.popup()
time.sleep(10)
self.button1.config(bg="green")
wait_popup.destroy()
a = GUI()
a.mainloop()
You can use self.update() in your code to make the popup window appear.
import tkinter as tk
import time
class GUI(tk.Tk):
def __init__(self):
super().__init__()
self.button1 = tk.Button(text="Start", command=self.make_green)
self.button1.pack()
def popup(self):
tl = tk.Toplevel(self)
tl.transient()
tk.Label(tl, text="Painting green").pack()
self.update()
tl.grab_set()
return tl
def make_green(self):
wait_popup = self.popup()
time.sleep(10)
self.button1.config(bg="green")
wait_popup.destroy()
a = GUI()
a.mainloop()
Or you can use threading.
Start by importing Thread from threading.
from threading import Thread
Then make a new method
def thread_it(self):
return Thread(target=self.make_green, daemon=True).start()
and update your button's command
self.button1 = tk.Button(text="Start", command=self.thread_it)

PyQt5 - How to return application to initial state after error handling with QMessageBox

Just starting out with Python3 and PyQt5 and I'm kinda stuck here.
My main window takes two ticker codes as input and, after the user presses the Show Me! button, outputs ratio averages for each of them. I created a QMessageBox with an OK button that pops up when the user enters invalid ticker codes.
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import good_morning as gm
import MainUI
class MainWindow(QMainWindow, MainUI.Ui_MyStockratios):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
self.home()
def home(self):
#If Show Me! button is clicked, go grab_user_input()
self.show_me_btn.clicked.connect(self.grab_user_input)
def grab_user_input(self):
#Grab user input for QLineEdits
self.ticker1_value = self.ticker1_label.text()
self.ticker2_value = self.ticker2_label.text()
#Fetch the ratios and place them in a dataframe
self.kr = gm.KeyRatiosDownloader()
try:
self.kr_frame1 = self.kr.download(self.ticker1_value)
self.kr_frame2 = self.kr.download(self.ticker2_value)
#Error handling
except ValueError:
msg = QMessageBox()
msg.setIcon(QMessageBox.Information)
msg.setText("Invalid ticker code")
msg.setInformativeText("Please verify the data you entered and try again.")
msg.setWindowTitle("Error")
msg.setStandardButtons(QMessageBox.Ok)
reply = msg.exec_()
if reply:
self.ticker2_label.clear()
self.ticker1_label.clear()
self.home()
[...]
def main():
app = QApplication(sys.argv)
form = MainWindow()
form.show()
app.exec_()
if __name__ == "__main__":
main()
Here's my problem: I want the application to return to its' initial state after the user presses the QMessageBox's OK button, which means the QLineEdits must be cleared and the application must wait for the user to input new data and press the Show Me! button again. I cleared the QLineEdits with the clear() function, but can't seem to make the application wait for new user input.
Thanks in advance !
For future reference you're posted code is a bit incomplete. I took some liberties to get a working example. You can ignore most of the changes except for the button handler part. You only need to connect the button once. Your home() method is not needed.
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
# import good_morning as gm
# import MainUI
class MainWindow(QMainWindow):#, MainUI.Ui_MyStockratios):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
# self.setupUi(self)
layout = QVBoxLayout()
widget = QWidget(self)
self.setCentralWidget(widget)
widget.setLayout(layout)
self.show_me_btn = QPushButton('Show Me!', self)
layout.addWidget(self.show_me_btn)
self.ticker1_label = QLineEdit(self)
layout.addWidget(self.ticker1_label)
self.ticker2_label = QLineEdit(self)
layout.addWidget(self.ticker2_label)
# self.home()
self.show_me_btn.clicked.connect(self.grab_user_input)
# def home(self):
# #If Show Me! button is clicked, go grab_user_input()
# self.show_me_btn.clicked.connect(self.grab_user_input)
def grab_user_input(self):
#Grab user input for QLineEdits
self.ticker1_value = self.ticker1_label.text()
self.ticker2_value = self.ticker2_label.text()
# #Fetch the ratios and place them in a dataframe
# self.kr = gm.KeyRatiosDownloader()
#
# try:
# self.kr_frame1 = self.kr.download(self.ticker1_value)
# self.kr_frame2 = self.kr.download(self.ticker2_value)
#
# #Error handling
# except ValueError:
if 1:
msg = QMessageBox()
msg.setIcon(QMessageBox.Information)
msg.setText("Invalid ticker code")
msg.setInformativeText("Please verify the data you entered and try again.")
msg.setWindowTitle("Error")
msg.setStandardButtons(QMessageBox.Ok)
reply = msg.exec_()
if reply:
self.ticker2_label.clear()
self.ticker1_label.clear()
# self.home()
# [...]
def main():
app = QApplication(sys.argv)
form = MainWindow()
form.show()
app.exec_()
if __name__ == "__main__":
main()

Pause execution until button press

I have a QStackedWidget. In the logic (not the UI) I am trying to change pages and wait there until a button on that page is pressed (basically an OK/Cancel). I pass the UI to the function in the class.
Something like this:
def func1(self, window):
window.stackedWidget.setCurrentIndex(4)
while True:
window.btn_OK.clicked.connect(self.OK_func)
window.btn_Cancel.clicked.connect(self.Can_func)
def OK_func(self, window):
do_something
window.stackedWidget.setCurrentIndex(3)
break
def Can_func(self, window):
window.stackedWidget.setCurrentIndex(3)
break
for i in range(5):
#stuff
func1(window) #this is where I want to pause
#other stuff
Now I know that I can't break with the function like that or pass the window variable through connect, but I hope that makes my point clearly enough.
A simple way to do this is to process pending events inside the loop (so the UI remains responsive), and set/unset an internal flag to control starting and stopping of the loop.
The following demo script shows a basic implementation of this idea:
import time
from PyQt4 import QtCore, QtGui
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
layout = QtGui.QVBoxLayout(self)
self.label = QtGui.QLabel(self)
layout.addWidget(self.label)
self.buttonStart = QtGui.QPushButton('Start', self)
self.buttonStart.clicked.connect(self.handleStart)
layout.addWidget(self.buttonStart)
self.buttonStop = QtGui.QPushButton('Stop', self)
self.buttonStop.clicked.connect(self.handleStop)
layout.addWidget(self.buttonStop)
self._running = False
def handleStart(self):
self.buttonStart.setDisabled(True)
self._running = True
while self._running:
self.label.setText(str(time.clock()))
QtGui.qApp.processEvents()
time.sleep(0.05)
self.buttonStart.setDisabled(False)
def handleStop(self):
self._running = False
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 300, 200, 100)
window.show()
sys.exit(app.exec_())
Just remove while and break.
def func1(self, window):
window.stackedWidget.setCurrentIndex(4)
window.btn_OK.clicked.connect(self.OK_func)
window.btn_Cancel.clicked.connect(self.Can_func)
def OK_func(self, window):
# do_something
window.stackedWidget.setCurrentIndex(3)
def Can_func(self, window):
window.stackedWidget.setCurrentIndex(3)

Resources