Kivy countdown Label - python-3.x

I'm trying to perform a countdown with the Label.
By updating the label's text I believe it is possible.
My intention is to start the countdown after the user presses the start button
But it's not working!!!
Here is my code
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.app import App
import time
class WelcomeScreen(Widget):
def __init__(self):
super().__init__()
self.start = Button(text="Start Game", pos=(350, 250))
self.add_widget(self.start)
self.count = Label(text="", pos=(350, 250),font_size=90)
self.start.bind(on_press=self.start_game)
def start_game(self, obj):
num = 0
self.remove_widget(self.start)
self.add_widget(self.count)
for i in range(1, 4):
num += 1
self.count.text = str(num)
time.sleep(1)
class TestApp(App):
def build(self):
return WelcomeScreen()
TestApp().run()
I know this is a stupid problem but I cant't really figure it out how to solve it.
Please Help

Short anwser: Use the Clock
def start_game(self, obj):
num = 0
self.remove_widget(self.start)
self.add_widget(self.count)
def count_it(num):
if num == 4:
return
num += 1
self.count.text = str(num)
Clock.schedule_once(lambda dt: count_it(num), 1)
Clock.schedule_once(lambda dt: count_it(0), 0)
Longer:
When you are running python code the kivy event loop is waiting so nothing gets update in the screen, when you do time.sleep you stall the process...
The Clock schedule_* methods allow you to set callbacks while letting the event loop run when you are idle
You can also use a thread and call Clock.schedule_once just for the updates but for your use case the code above is simpler

Related

PyQt5: count seconds mouse is over QPushButton in tooltip

The following running code gives a window with a button on it . The tooltip displays when the mouse enters the button.
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtTest import QTest
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.layout = QVBoxLayout()
self.button = QPushButton("MyButton")
global i
i = 0
self.button.setToolTip(str(i) + " seconds has passed since you move your mouse onto MyButton")
self.button.leaveEvent = self.clear()
self.layout.addWidget(self.button)
timer = QTimer(self)
timer.timeout.connect(self.start_counting)
timer.start(1000)
self.widget = QWidget()
self.widget.setLayout(self.layout)
self.setCentralWidget(self.widget)
def clear(self):
global i
i = 0
def start_counting(self):
if self.button.underMouse() == True:
global i
i = i + 1
self.button.setToolTip(str(i) + " seconds has passed since you move your mouse onto MyButton")
if __name__ == "__main__":
app = QApplication(sys.argv)
mainWin = MainWindow()
mainWin.show()
sys.exit( app.exec_() )
My goal is to count the number of seconds the mouse is inside the button and live display it using the tooltip. More precisely, I need to make sure all of the following is happening:
Every time the mouse enters the button, the count starts at 0 seconds and the tooltip shows up.
While the mouse stays inside the button (stationary or moving), the tooltip stays shown with the number of seconds (the text displayed inside the tooltip) updated over time.
When the mouse leaves the button, the count is cleared to zero.
As seen in the code, I have attempted to use underMouse to achieve my goals. My attempt is a partial success as the tooltip does update itself when the mouse moves inside the button. However, the tooltip does not update itself when the mouse stays stationary inside the button. Also, the count does not seem to be cleared when the mouse moves outside of the button .
What am I missing ?
One solution is to use an event-filter to monitor the enter and leave events, and also use an elapsed-timer to get an accurate measure of how long the mouse has been over the target widget. Below is a basic demo based on your example that implements the above. It also tries to match the normal behaviour of tooltips, but if necessary you can easily adjust the code to suit your own needs:
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.layout = QVBoxLayout()
self.button = QPushButton("MyButton")
self.layout.addWidget(self.button)
self.widget = QWidget()
self.widget.setLayout(self.layout)
self.setCentralWidget(self.widget)
self.position = QPoint()
self.counter = QElapsedTimer()
self.timer = QTimer()
self.timer.timeout.connect(self.start_counting)
self.button.installEventFilter(self)
def eventFilter(self, source, event):
if event.type() == QEvent.Enter and source is self.button:
self.counter.start()
self.timer.start(1000)
QTimer.singleShot(500, self.start_counting)
elif event.type() == QEvent.Leave and source is self.button:
self.timer.stop()
QToolTip.hideText()
return super().eventFilter(source, event)
def start_counting(self):
if self.button.underMouse():
if not QToolTip.isVisible():
self.position = QCursor.pos()
count = int(self.counter.elapsed() / 1000)
QToolTip.showText(self.position, (
f'{count} seconds have passed since '
'you moved your mouse onto MyButton'
))
if __name__ == "__main__":
app = QApplication(sys.argv)
mainWin = MainWindow()
mainWin.show()
sys.exit( app.exec_() )

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()

wxPython threading - GUI freezes while doing an animation in GLCanvas

What is the proper way of refreshing a GLCanvas (with heavy 3D geometries) without freezing the GUI?
I'm trying to use a set of GUI elements (buttons, checkboxes) to control a GLCanvas which is refreshed to display a 3D animation. My problem is that the GUI elements are not responsive when the animation loop is on.
I've tried to launch the animation loop in three different ways:
Option 1: within a thread
Option 2: using wx.lib.delayedresult (most likely similar to thread)
Option 3: calling Refresh after the onDraw event
It seems I can get the Option 1 to work, but I need to introduce a sleep delay with time.sleep(xxx) in between the call to the Refresh of the canvas. Otherwise the GUI remains poorly responsive: resizing the window, will end up "graying" the GUI elements, clicking on the checkboxes will trigger events but not the "checking" of the box, the "mouseover" effect over buttons does not work, etc.
I enclose a small working example. In my actual application, I animate a 3D geometry using moderngl, and I noticed that the "sleep" time required varies depending on how heavy the geometry is (as opposed to this example which is extremely lightweight and works with a delay as small as 0.00001s). I'm wondering what I'm missing. Thanks!
import wx
from wx import glcanvas
import wx.lib.delayedresult as delayedresult
from OpenGL.GL import *
import OpenGL.GL.shaders
import time
import numpy as np
from threading import Thread
# --- Option 1: Thread
class TestThread(Thread):
def __init__(self, parent, canvas):
Thread.__init__(self)
self.parent=parent
self.canvas=canvas
self.start() # start the thread
def run(self):
print('Thread running... ')
while self.canvas.animate:
#time.sleep(0.01) # <<<<<<<<<<<< This line needed
self.canvas.Refresh()
print('Tread done ')
class OpenGLCanvas(glcanvas.GLCanvas):
def __init__(self, parent):
glcanvas.GLCanvas.__init__(self, parent, -1, size=(400,400))
self.context = glcanvas.GLContext(self)
self.SetCurrent(self.context)
self.init = False
self.animate = False
self.refreshAfter = False
self.t=0
self.Bind(wx.EVT_PAINT, self.OnPaint)
def OnPaint(self, event):
wx.PaintDC(self)
if not self.init:
self.InitGL()
self.init = True
self.OnDraw()
def InitGL(self):
glEnable(GL_DEPTH_TEST)
def OnDraw(self):
""" Called at every frame"""
glClearColor(0.1, 0.0, np.mod(self.t,1), 1.0)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
if self.animate:
self.t+=0.0005 # increment time
# ---- Option 3
if self.refreshAfter:
self.Refresh() # Trigger next frame
self.SwapBuffers()
# --- Option 2: delayed results
def onAnimDelayedEnd(self, thread):
""" Consumer """
print('Delayed result done')
jobID = thread.getJobID()
result = thread.get()
def onAnimDelayedStart(self):
print('Delayed result running... ')
while self.animate:
self.Refresh()
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.canvas = OpenGLCanvas(self)
# GUI
self.rot_btn = wx.Button(self, -1, label="Toggle animation", pos=(430, 10))
self.cbDo = wx.CheckBox (self, -1, label="Do something", pos=(430 ,100))
self.radDo = wx.RadioButton(self, -1, label="Do something", pos=(430,140))
self.radDo2= wx.RadioButton(self, -1, label="Do something", pos=(430,180))
# Binding
self.rot_btn.Bind(wx.EVT_BUTTON, self.toggleAnim)
self.radDo.Bind(wx.EVT_RADIOBUTTON, self.doSomething)
self.radDo2.Bind(wx.EVT_RADIOBUTTON, self.doSomething)
self.cbDo.Bind(wx.EVT_CHECKBOX , self.doSomething)
def toggleAnim(self, event):
if not self.canvas.animate:
self.canvas.animate = True
# --- Option 1: thread
TestThread(self, self.canvas)
# --- Option 2: delayed result
#delayedresult.startWorker(self.canvas.onAnimDelayedEnd, self.canvas.onAnimDelayedStart, jobID=1)
# --- Option 3: refreshloop
#self.canvas.refreshAfter=True
#self.canvas.Refresh() # set the canvas into an "infinite" refresh loop
else:
self.canvas.animate = False
def doSomething(self, event):
print('Do something')
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="My wx frame", size=(600,400))
self.Bind(wx.EVT_CLOSE, self.on_close)
self.panel = MyPanel(self)
def on_close(self, event):
self.Destroy()
if __name__ == "__main__":
app = wx.App()
frame = MyFrame().Show()
app.MainLoop()

Pyqt5 Entry don't appear [duplicate]

This question already has answers here:
QLabel does not display in QWidget
(2 answers)
Closed 2 years ago.
I would like to create a simple GUI that assign to each name another name randomly. But the problem is that, after the user decide how many name create, it should show the QLineEdit tag, but i can't see that. There aren't errors. Obviusly,the application is not over. The problem is only the QLineEdit tag. Here there is the code:
from PyQt5.QtWidgets import *
from PyQt5.QtCore import Qt
from PyQt5 import QtGui
import sys
class Assigner(QWidget):
def __init__(self):
super().__init__()
self.setGeometry(0,0,250,500)
self.setWindowTitle("Assigner")
self.counter = 3
self.tit = QLabel(self)
self.tit.setText("Assigner GUI")
self.tit.move (60,10)
self.tit.setFont(QtGui.QFont("Times", 20, QtGui.QFont.Bold))
self.butup = QPushButton("↑",self)
self.butup.resize(40,40)
self.butup.setToolTip("Increase number")
self.butup.move(100,50)
self.butup.clicked.connect(self.increase)
self.lab = QLabel(self)
self.lab.setText(str(self.counter))
self.lab.resize(40,60)
self.lab.move (100,115)
self.lab.setStyleSheet("background: red;")
self.lab.setFrameShape(QFrame.Panel)
self.lab.setFrameShadow(QFrame.Sunken)
self.lab.setLineWidth(4)
self.lab.setFont(QtGui.QFont("Times", 20, QtGui.QFont.Bold))
self.butdo = QPushButton("↓",self)
self.butdo.resize(40,40)
self.butdo.setToolTip("Decrease number")
self.butdo.move(100,200)
self.butdo.clicked.connect(self.decrease)
self.go = QPushButton("Start assign",self)
self.go.resize(70,40)
self.go.setToolTip("Start")
self.go.move(85,280)
self.go.clicked.connect(self.start)
self.show()
def increase(self):
self.counter += 1
self.lab.setText(str(self.counter))
def decrease(self):
if self.counter > 0:
self.counter -= 1
self.lab.setText(str(self.counter))
def start(self):
self.go.deleteLater()
self.butdo.deleteLater()
self.butup.deleteLater()
self.lab.deleteLater()
self.tit.deleteLater()
self.entry = []
self.y = 20
for i in range(self.counter):
self.entry.append(QLineEdit(self))
self.entry[-1].move(20, self.y)
self.entry[-1].resize(220,40)
self.y += 50
if __name__ == "__main__":
app = QApplication(sys.argv)
ass = Assigner()
sys.exit(app.exec_())
When you click the start button, all is white, but we should see the QLineEdit.
If someone knows where is the issue, please write it to me
Your QLineEdits are simply invisible; just add:
self.entry[-1].show()
# or self.entry[-1].setVisible(True)
There's a (discrete) note about that in Qwidget's doc:
If you add a child widget to an already visible widget you must
explicitly show the child to make it visible.
So an alternative solution could be to self.hide() at the very beginning of start, modify your sub-widgets as before, and finish the function with a self.show().

StatusIcon with threaded loop: loop does not loop

I have created a simple status icon.
Its purpose is to check the existence of files in a given time interval.
The status icon shows, and the thread is started.
However, the loop does not loop: it enters the loop, but on event.wait it halts, and never continues.
I've tried to replace with time.sleep(5), but that had the same result.
I've searched anywhere, but I couldn't find an answer.
What am I missing?
#! /usr/bin/env python3
#-*- coding: utf-8 -*-
from gi.repository import Gtk
import threading
class CheckStatus(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.event = threading.Event()
def run(self):
cnt = 0
while not self.event.is_set():
cnt += 1
print((">>> check %d" % cnt)) # This shows just once
self.event.wait(5)
print(">>> done")
def stop(self):
self.event.set()
class MyStatusIcon(object):
def __init__(self):
self.statusIcon = Gtk.StatusIcon()
self.statusIcon.set_from_stock(Gtk.STOCK_ABOUT)
# Build status icon menu
menu = Gtk.Menu()
menuQuit = Gtk.MenuItem("Quit")
menuQuit.connect('activate', self.quit_tray)
menu.append(menuQuit)
self.statusIcon.connect('popup-menu', self.popup_menu, menu)
# Start thread
self.check = CheckStatus()
self.check.start()
def popup_menu(self, widget, button, time, data):
data.show_all()
data.popup(None, None, None, None, button, time)
def quit_tray(self, widget):
# Stop the thread and quit
self.check.stop()
self.check.join()
Gtk.main_quit()
if __name__ == '__main__':
# Create an instance of our GTK application
try:
MyStatusIcon()
Gtk.main()
except:
pass
[EDIT 1]
I've replaced the while loop with a counter loop (while cnt < 10), although this didn't change the behavior, it showed the printed counter every 5 seconds as soon as I quit the status icon.
It seems that the thread is waiting for an event from the status icon.
[EDIT 2]
I needed to look at this from a different angle, and wondered if GObject.timeout_add could solve this issue...well, it did, but I don't know if it's the most elegant way to do so.
#! /usr/bin/env python3
#-*- coding: utf-8 -*-
from gi.repository import Gtk, GObject
class MyStatusIcon(object):
def __init__(self):
self.quit = False
self.counter = 0
self.service = GObject.timeout_add(5000, self.checkStatus)
self.statusIcon = Gtk.StatusIcon()
self.statusIcon.set_from_stock(Gtk.STOCK_ABOUT)
# Build status icon menu
menu = Gtk.Menu()
menuQuit = Gtk.MenuItem("Quit")
menuQuit.connect('activate', self.quit_tray)
menu.append(menuQuit)
self.statusIcon.connect('popup-menu', self.popup_menu, menu)
def checkStatus(self):
self.counter += 1
if not self.quit:
print((">>> check %d" % self.counter))
return True
print("Done")
return False
def popup_menu(self, widget, button, time, data):
data.show_all()
data.popup(None, None, None, None, button, time)
def quit_tray(self, widget):
# Stop the thread and quit
self.quit = True
Gtk.main_quit()
if __name__ == '__main__':
# Create an instance of our GTK application
try:
MyStatusIcon()
Gtk.main()
except:
pass

Resources