appJar python: button press action - python

I am trying to get a hand-on experience on appJar with python3 for GUI programming. I am trying to add an action to take place when button is pressed. it works OK but it ends strangely.
Part of the code is below. the function "pressed" is getting called whenever the button is pressed. it works fine. However, when the count reaches 0, the number 0 is not pressed (although, as per the code, the number 0 should be printed on the label named "lb1").
when the button is pressed and the count variable =1, the count variable should get decremented by one, then the label text should be updated with the new count number. then, it checks if the counter =0 and if true, it exits the code.
Now, the form get terminated before updating the label with the new value. with some troubleshooting, I found the label value is only updated upon the termination of the form although the line of code that updates it is executed already.
Anyone can shed some light on this?
from appJar import gui
count=10
def pressed(btnName):
global count
count-=1
win.setLabel("lb1","Count= "+ str(count))
if count==0:
win.stop()

I think what you're seeing is expected behaviour.
When count reaches 0 - the label update is queued to happen, but then the next line of code stops the GUI, this will happen just a few milliseconds later - so the GUI will be gone before there is a chance to update what is being shown on the screen.
If you want to introduce a slight delay, between the label updating and the GUI closing, you could use the .after() function.
For example:
if count == 0:
win.after(500, win.stop)
This will delay 500 milliseconds before calling the stop function.
Alternatively, if you want the GUI to stay open, displaying Count= 0, and only close the next time the user presses the button, change your original code to only call win.stop() when count == -1
So, the full code might look like:
from appJar import gui
count=10
def pressed(btnName):
global count
count-=1
win.setLabel("lb1","Count= "+ str(count))
if count == 0:
win.after(500, win.stop)
win = gui()
win.addLabel("lb1", "empty")
win.addButton("PRESS", pressed)
win.go()

Related

A function is running despite me changing a variable that should stop it

I was hoping someone could help me with this issue. I'm hoping it's fairly simple to fix, but I have been trying for a while to figure this out. I have trimmed my larger code to this, as I believe the issue here is the crux of the problem.
I have a raspberry pi and an external button. This is on python3 on Linux. I am using GPIOZero for the button. The code below I believe is simple to understand, but basically, I want a function to loop at all times. When I press a button I want another function to run, but only if a variable is a certain number. I describe what I ultimately want to happen in a comment below, but my code is unfinished and simplified just for this problem.
I only want button.when_pressed to work when timer = 0. The problem is, once the code naturally gets into the button.when_pressed function, it never "lets go" of the function again. When I successfully redefine the variable to timer = 1, it still prints button is pressed when I press the button. I don't know why. To me, it seems like it should only work once timer = 0.
Any suggestions? I think I have a misunderstanding of global variables I will plan on researching. I'm not sure if that's the issue. I have also tried using break and continue to try to get it "back on its loop" but that hasn't worked either. Also I want to use button.when_pressed instead of btn.is_pressed, because I want the program to only do something when I'm holding the button down once, and not loop when I'm holding it down. In this case, I want button is pressed to print a single time. If I did btn.is_pressed it would print button is pressed every two seconds, which I dont want.
Thanks for any help. I'm happy to learn.
#!/usr/bin/python3
from gpiozero import Button
from time import sleep
import time
button = Button(4)
timer = 0
def press():
print("Button is pressed")
global timer
timer = 1
def loop():
global timer
timer = 1
while True:
if timer == 0:
button.when_pressed = press
else:
loop()
sleep(2)
If you want to disable the callback you had set to button.when_pressed, you need to do another assignment with button.when_pressed = None. This is listed in the documentation:
when_released:
[...] Set this property to None (the default) to disable the event.
It's not exactly clear what behavior you want from your current code. If you want the button to be active for 2 seconds, then be deactivated indefinitely, you can use:
button.when_pressed = press
sleep(2)
button.when_pressed = None
There's no need for a loop, since you don't want to repeat anything.
If you only want the button to be active for a single button press, that needs to happen within 2 seconds, you could instead call button.wait_for_press(2). I hesitate to write a full block of code for that though, as the docs don't specify how a timeout is signaled (it might be by return value, or via an exception). I don't have a Raspberry Pi so I can't test myself, but you could try it out and see what happens.
Treat your whole piece of code as one "black box", ask yourself, what is the input/output? button press or timer mode? (because I don't quite understand what does timer variable mean in your code)
Your code implies timer mode is the top level input to control the flow,
while True:
if timer == 0:
button.when_pressed = press
else:
loop()
sleep(2)
Is it expected?
If you allow user to press the button at any time, suggest you make button press to be your top level input, change the logic to keep when_pressed callback always on, set flag once triggered, and then check if the button has been pressed and still is_pressed in your while loop.
pressed = False
def play_video_1():
pass
def play_video_2():
pass
def press():
print("Button is pressed")
global pressed
pressed = True
button.when_pressed = press
while True:
if pressed and not_playing_video2:
if is_pressed:
play_video_1()
else:
pressed = False
play_video_2()
else:
play_video_2()

changing label color 2 times within a funtion in tkinter python

I have a funtion, that parses the log. I am updating the label color and text at different lines within the same funtion. I see that only the last changed color and text only reflecting in the UI. i do not see the intermediate color and text changes.
Below is my code:
def OnclickLogParser(self):
if LogFileName == '':
messagebox.showinfo("Error", "Please select a valid Log")
if LogPathName == '':
messagebox.showinfo("Error", "Please select a valid Log Path")
self.lb_log_status.configure(bg="#08DFE8", fg="#010101", text='Parsing inProgress...')
m_logParser = CAdpBrrLogParser()
m_logReader = CAdpBrrLogReader('mrr', m_logParser)
status = m_logReader.readFile(LogPathName)
if status == True:
self.lb_log_status.configure(bg="#F6F50B", fg="#010101", text='Log Ready')
self.btn_log_start["state"] = "normal"
global m_injector
m_injector = CAdpUdpDataInjector()
you can see that i am changing the color and text of lb_log_status at two different places. The time gap between those two lines would be around 3 -5 secs. But i can not witness the first color change. '
Tkinter must process events regularly to perform any UI operations. If you want to see the color changes here you have to give Tk a chance to process the configuration and paint events that get generated when you re-configure a widget. Your code just immediately starts processing the log and will not process any events until this function exits and you return to the Tk mainloop() which is the event processing method. You would see the same problem if you used a progress bar widget and tried to update the progress during processing.
One way to resolve this is to use the Tk after() method and schedule your processing in chunks that take a limited amount of time per chunk. After reading and processing a chunk call after() again to schedule the next chunk.
Another method is to put the processing on a worker thread and use event_generate() to post events back to the Tk thread to announce progress and completion.

Problems with update and update_idletasks

I've been learning python for a month now and run into my first brick wall. I have a large art viewer GUI program and at one point want to put an image on screen with a countdown counter-approx every 5 secs. I thought of a code such as the one below The problem is that this uses update and all my reading says that update is bad (starts a new event loop (?)) and that I should use update_idletasks. when I replace update with update_idletasks in the code below the countdown button is not visible until it reaches single figures, update superficially works fine. But also the q bound key calls the subroutine but has no effect
from tkinter import *
import sys
import time
root = Tk()
def q_key(event):
sys.exit()
frame=Frame(root, padx=100, pady=100, bd=10, relief=FLAT)
frame.pack()
button=Button(frame,relief="flat",bg="grey",fg="white",font="-size 18",text="60")
button.pack()
root.bind("q",q_key)
for x in range(30, -1, -5) :
button.configure(text=str(x))
button.update()
print(x)
button.after(5000)
root.mainloop()
In this case you don't need update nor update_idletasks. You also don't need the loop, because tkinter is already running in a loop: mainloop.
Instead, move the body of the loop to a function, and call the function via after. What happens is that you do whatever work you want to do, and then schedule your function to run again after a delay. Since your function exits, tkinter returns to the event loop and is able to process events as normal. When the delay is up, tkinter calls your function and the whole process starts over again.
It looks something like this:
def show(x):
button.configure(text=x)
if x > 0:
button.after(5000, show, x-5)
show(30)

How do I get a variable from a running python function

I am working on some code that will count mouse clicks on a beaglebone board in python.
My question is how to structure code such that I can access the total number of mouse clicks while the mouse click counter function is still running (indefinately), and at the same time not interrupt the mouse click counter function ( i dont want to miss a click!)?
Is there a way to access variables in a running function in python without interrupting it?
Depends on how you're wanting to access them. An infinite loop works well.
_clicks = 0
while True:
if _clicks != clicks:
_clicks = clicks
print(_clicks)
But you'll almost certainly have to put that in another thread.
from threading import Thread
def notifier():
global clicks # whatever variable you're accessing
while True:
if _clicks != clicks:
_clicks = clicks
print(_clicks)
t = Thread(target=notifier)
t.start()

How do widgets update in Tkinter?

Okay, so I'm just trying to get some clarification on why my code is not working like I thought it would.
I am building a GUI, and I want to display text on a Label with a text variable. I have already made a function that updates the Label when the function is called, but of course that is not my problem.
My problem stems from me trying to implement a "print one letter at a time" type of label. While it prints to the terminal in the way I want it to, the label widget only updates after the whole function has finished (visually its the same as just printing the whole string instead of printing a letter at a time).
So what am I missing, what do I not understand? Can you guys help me? Let me post some code so you guys can see where my error is.
I tried both of these independently and they both game me the same result, which was not what I desired.
def feeder(phrase):
"""Takes a string and displays the content like video game dialog."""
message = ""
for letter in phrase:
time.sleep(.15)
message += letter
information.set(message)
#print message
def feeder2(phrase):
"""Same as feeder, but trying out recursion"""
current.index += 1
if current.index <= len(phrase):
information.set(phrase[:current.index])
time.sleep(.1)
feeder2(current.status())
I'm not positive if I need to post more code, so you guys can understand better, but if thats the case, I will do that.
Those 2 functions are used in this function
def get_info():
"""This function sets the textvariable information."""
#information.set(current)
feeder2(current.status())
Which in turn is used in this function
def validate():
""" This function checks our guess and keeps track of our statistics for us. This is the function run when we press the enter button. """
current.turn += 1
if entry.get() == current.name:
if entry.get() == "clearing":
print "Not quite, but lets try again."
current.guesses -= 1
if entry.get() != "clearing":
print "Great Guess!"
current.points += 1
else:
print "Not quite, but lets try again."
current.guesses -= 1
print current
get_info()
entry.delete(0, END)
current.name = "clearing"
The UI will update every time the event loop is entered. This is because painting is done via events (also known as "idle tasks" because they are done when the UI is otherwise idle).
Your problem is this: when you write a loop and do time.sleep, the event loop will not be entered while that loop is running, so no redrawing will occur.
You can solve your problem in at least a couple different ways. For one, you can just call update_idletasks which will refresh the screen. That will solve the repainting, but because you are sleeping the UI will be unresponsive during your loop (since button and key presses aren't "idle tasks").
Another solution is to write a function that takes a string, pulls one character off the string and adds it to the widget. Then it arranges for itself to be called again via the event loop. For example:
import Tkinter as tk
class App(tk.Tk):
def __init__(self,*args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.label = tk.Label(self, text="", width=20, anchor="w")
self.label.pack(side="top",fill="both",expand=True)
self.print_label_slowly("Hello, world!")
def print_label_slowly(self, message):
'''Print a label one character at a time using the event loop'''
t = self.label.cget("text")
t += message[0]
self.label.config(text=t)
if len(message) > 1:
self.after(500, self.print_label_slowly, message[1:])
app = App()
app.mainloop()
This type of solution guarantees that your UI stays responsive while still running your code in a loop. Only, instead of using an explicit loop you add work to the already running event loop.

Categories