Python Tkinter use to emulate blinking with interaction of multiple buttons - python

I am looking for a solution to emulate the behavior of the UI of an electronic component and the user interaction (which should be pushing buttons) with LEDs reporting an internal state of the electronic component.
I am using python and the tKinter module to do so.
My code runs and my GUI window displays correctly. However, when I push several times on buttons the behavior is not as expected.
I have 4 possible state for each LED (OFF, ON, (Blinking) SLOW, (Blinking) FAST).
I have 4 buttons which can have an impact on the state. Each button has an interaction function defined in the widget class I have defined, and each of this function, once called, redefines the internal state of the widget.
In order to control the blinking of the LED, I use a single loop and the self.after( ..) function. This function is the following:
def toggleLeds(self):
for led in [self.ledTxIP, self.ledRxIP, self.ledTxRS, self.ledRxRS, self.ledPower, self.ledRun, self.ledStatus, self.ledConfig]:
if (((led[1] == "SLOW") and (self._FastBlinking == 0)) or (led[1] =="FAST")):
bg = led[0].cget("background")
bg = "green" if bg == "black" else "black"
led[0].configure(background=bg)
elif((led[1] == "OFF") and (self._update == 1)):
led[0].configure(background="black")
self._update = 0
elif (self._update == 1):
led[0].configure(background="green")
self._update = 0
self._FastBlinking = (self._FastBlinking + 1)%2
self.update_idletasks()
self.after(self._FastBlinkTime, self.toggleLeds)
This one is called recursively through the self.after function, and at the end of the interaction function I have defined for each button.
Here is how I have defined a single LED:
self.ledTxIP = [tk.Label(self, width=1, borderwidth=2, relief="groove"),"OFF"]
And here is an example of the button interaction function:
def pushMode(self):
if (re.search("Reset",self.state) == None):
if (self.clickModCnt == 0):
self.state = "Status"
self._stateTimer = int(time.gmtime()[5])
elif (self.clickModCnt == 1):
if(int(time.gmtime()[5]) - self._stateTimer < 3):
self.state = "Config"
else:
self.state = "RunMode"
else:
self.state = "RunMode"
self.clickModCnt = (self.clickModCnt + 1)%3
self._update = 1
self.updateLedState()
If anybody has an advice on this, it would be more than welcome.

I don't know why this didn't jump out at me sooner, but I think the problem is listed in your own question text, referring to the toggleLeds method:
This one is called recursively through the self.after function, and at the end of the interaction function I have defined for each button.
When the program initially runs, I'm assuming that you call toggleLeds somewhere to kick off the initial pattern for the LEDs. That sets up a single recursive loop via the self.after call at the end of the method. However, if you also call that same method every time you click a button to change state, you're setting up a new loop with every button click, and each new loop may or may not be in sync with your initial loop.
There are a couple ways that I can think of to handle this possible conflict. One is to avoid making new calls to toggleLeds, but that way there could be a delay between the button click and the new LED pattern. If you don't mind that delay, that's probably the best solution.
If you want the light/blink pattern to change immediately, you need to interrupt the current loop and start a new one with the new light/blink states. According to the Tkinter reference produced by New Mexico Tech, the after method:
...returns an integer “after identifier” that can be passed to the .after_cancel() method if you want to cancel the callback.
Here's how you could take advantage of that. First make sure that you're storing that identifier when calling the after method:
self.after_id = self.after(self._FastBlinkTime, self.toggleLeds)
Then change your toggleLeds method definition to accept an optional "interrupt" argument, and to cancel the existing after loop if that argument is True:
def toggleLeds(self, interrupt=False):
if interrupt:
self.after_cancel(self.after_id)
# Existing code follows
Finally, pass True to that argument when calling the method after a button has been clicked:
# Existing button processing code here
self.toggleLeds(interrupt=True)
With these changes in place, each button click would cancel the current after cycle and start a new one, preventing more than one cycle from running at once, which should keep the LEDs in sync.

Related

Controlling flow of application using GUI interface

(Thanks in advance if you decide to answer. Sorry If I am not able to describe the content of the Application clearly.)
I am currently making Desktop Application using Tkinter Python for my gui interface. I am having a problem related to the flow of the Application.
My Application
Main Page of my application has form designed with some textboxes and checkboxes. Check button work as True or False Condition, where each check button refers to whether or not a specific function has to be executed or not.
To store the values of the check button, the dictionary is maintained with keys as LabelName and values as True/False value.
Checkbox code
f1=tk.BoolVar() #People tend to use IntVar() but i prefer BoolVar()
ttk.Label(text="func1")
ttk.Checkbutton(parent, variable=f1)
f2=tk.BoolVar()
ttk.Label(text="func2")
ttk.Checkbutton(parent, variable=f2)
-------other such CheckButtons------------------
There's a submit button in the form on pressing which all the data entered into textbox along with these check buttons. Based on true-false values, functions are called which is handled by if-else conditions.
#submit button
ttk.Button(parent,text="Submit",command=onsubmit)
###########
def onsubmit():
----------statements to read data--------------
dict['func1']=f1
dict['func2']=f2
#other statements
-----------------------------------------------
if dict['func1']:
func1()
if dict['func2']:
func2()
---other if-else conditions---
Each function is individual module which consists of either form, or frame with data or matplotlib graphs for visualization of data and to do other operations on data and graphs which are placed on root window frame.
My problem
I want the user to control the flow by giving them the next button after every function is executed and then move onto the execution of the next function based on his input of the check button. The program should wait until the User presses the next button and after they press the next button, it should execute the next function and then wait again for the next button.
One of the solution:
Using fig.waitforbuttonpress() was the solution. But I didn't find it reliable. Because even mouse click could skip the function execution. But I need to specifically assign a button through which the user can select when to proceed to the next function.
I am not sure if I understood what your code does, but you could do it something like that I guess:
next_button = ttk.Button(parent,text="Next",command=func1)
...
def func1():
#do your stuff here
next_button.configure(command=func2)
Then you would have to add the last line of code to all the functions to always reassign the button.
Another way could be:
process = 0
next_button = ttk.Button(parent,text="Next",command=next)
def next():
global process
process += 1
if process == 1:
func1()
elif process == 2:
func2()
...
elif *last_function_reached*:
process = 0

tkinter left clicks on a TAB in your GUI. How to detect when user has done this

I have a GUI written in tkinter and all works fine. I want to enhance it so that when a user left clicks on a certain tab with the mouse, a method is executed. I thought this would be straight forward but I can't get it working. My code is
def f_x():
print('entered this method')
tab4e = ttk.Frame(notebook2,width=C_WIDTH,height=C_TAB_HEIGHT)
tab4e.bind("<Button-1>",f_x())
When the tab changes, it emits the event "<<NotebookTabChanged>>", which you can bind to:
def handle_tab_changed(event):
selection = event.widget.select()
tab = event.widget.tab(selection, "text")
print("text:", tab)
notebook = ttk.Notebook(...)
...
notebook.bind("<<NotebookTabChanged>>", handle_tab_changed)
The benefit of using this event rather than binding to a mouse click is that the binding will fire no matter what causes the tab to change. For example, if you define shortcut keys to switch tabs, binding to the mouse won't cause your handler to fire if the user uses one of those shortcut keys.
You were right, this is pretty straight forward, what you did was almost correct, you need to pass the function not the return value of the function to bind. So you will need to get rid of the parentheses after f_x. Another thing is, the bind also automatically pass an argument to the callback called event, so you will need to let f_x accept an argument.
def f_x(event): # accept the event arg
print('entered this method')
tab4e = ttk.Frame(notebook2,width=C_WIDTH,height=C_TAB_HEIGHT)
tab4e.bind("<Button-1>",f_x) # not f_x()
This works now detecting when a TAB Canvas has been brought to focus through a LEFT mouse click
def fTabSwitched(event):
global notebook2
l_tabText = notebook2.tab(notebook2.select(), "text")
if (l_tabText == 'eee'):
print('lets roll')
tab4a = ttk.Frame(notebook2,width=C_WIDTH,height=C_TAB_HEIGHT)
tab4b = ttk.Frame(notebook2,width=C_WIDTH,height=C_TAB_HEIGHT)
tab4c = ttk.Frame(notebook2,width=C_WIDTH,height=C_TAB_HEIGHT)
tab4d = ttk.Frame(notebook2,width=C_WIDTH,height=C_TAB_HEIGHT)
tab4e = ttk.Frame(notebook2,width=C_WIDTH,height=C_TAB_HEIGHT)
notebook2.add(tab4a,text='aaa')
notebook2.add(tab4b,text='bbb')
notebook2.add(tab4c,text='ccc')
notebook2.add(tab4d,text='ddd')
notebook2.add(tab4e,text='eee')
notebook2.bind("<ButtonRelease-1>",fTabSwitched) # must be release

Issue with selectively undoing functions in Python

I have created a program in the turtle canvas in which the user can press any letter key on the keyboard and the turtle draws the corresponding letter in the canvas. I have also implemented an undo function that undoes the last function called by clearing the canvas then redrawing everything up until the point before the undone action. This undo function works by the user pressing a tkinter button at the bottom of the canvas labeled "Undo" or pressing the "left" key on the keyboard.
However, I have also decided to create a dynamic (a.k.a. selective, nonlinear, etc.) undo method in which there is a tkinter drop down menu and each drawing function, as it is called, is written to that menu. Then, from the menu, the user can select the function she previously called that he/she wants to undo and the program will undo that specific function instance. The whole process is described below:
The tkinter drop down menu is created through the following code block:
# global variables because next code block located ABOVE this one
global fav
fav = Menubutton(text = "Selective redo", state = DISABLED)
fav.pack(side = "left")
fav.menu = Menu(fav, tearoff = 0)
fav["menu"] = fav.menu
global redo1
redo1 = fav.menu
fav.pack()
When letter drawn, that letter's point object written to menu using code block below:
po = Point(v,y,c,w,isdown(),x,ph,pw)
undo1.add_command(label = Point.__str__(po), command = lambda: selectundo(undo1.index(po)))
# 'po' is a Point object for each function, and the string of that is the label of the menu item
Point.__str__() is this Point object method:
def __str__(self):
return "({})".format(self.function)
Finally, user supposed to be able to select –from menu– which instance of letter to undo, and program undoes that letter instance using the user-defined function below:
def selectundo(x):
for ty in range(x, len(function)):
undoHandler()
update()
listen()
The issue here is that when the user chooses to draw two or more of the same letters, they both get written to the menu with the same exact index values and thus, if the user wants to undo say, 1 of those, it will instead undo ALL instances of the letter from the canvas, not just the one selected by the user! However, I want the program to ONLY undo the instance of the letter that the user selects from the menu. Any ideas on how I would fix this issue? Any help is much appreciated! :)

Waiting for a button click during the execution of a function in Pyside

I have multiple buttons. When I click one of them, I call a function that iterates over rows of a table (say, changes the background colour of the row). During the execution of this function, in the middle of the iteration, I'd like to "pause" and wait for another button to be clicked in order to resume the execution of the function. During this "pause" I'd like all other buttons (that is, the ones I'm not waiting to be clicked) to be disabled or ignored.
I've looked into QTimer and QThread, but I think that I'm unnecessarily over-complicating things. Looking for suggestions...
First of all you need to call QtGui.qApp.processEvents() method at the end or beginning of the iteration in order to make the gui responsive during the execution of the loop. Then you need to maintain a list of rows (say, self.allRows), in each iteration pop one row from the list change the background color and append it to a second list (say, self.doneRows). Also declare a boolean variable as False (say, self.pause = False) inside __init__ and add a check statement at the beginning of the loop, if the self.pause is True, break the loop. Then you need to add a slot to update the value of self.pause to True.
def pause_execution(self):
self.pause = True
# loop through all the buttons to make them disabled excluding the pause and resume buttons
for btn in self.buttons:
btn.setEnabled(False)
QtGui.qApp.processEvents()
Connect Pause button's clicked signal to this slot. Now the loop will be broken and self.allRows contains only the rows with no background color changed yet. Create another slot to resume the loop again. This time the loop will change the background color of only remaining rows.
def resume(self):
self.stop = False
# loop through all the buttons that were disabled, to make them enabled (if you need to)
for btn in self.buttons:
btn.setEnabled(True)
QtGui.qApp.processEvents()
# call the method which contains the background color changing loop.
I think you are looking for this to execute on each method depending on the mouse button click:
myWidget.setDisabled(true)
Then, you could find all the children of the main window or the root widget whatever that is, this way:
widgets = form.findChildren(QtGui.QWidget)
Then, you could connect your push button click to the desired method as follows:
button.clicked.connect(setDisableWidgets)
So, here is a simple example:
import sys
from PySide.QtCore import *
from PySide.QtGui import *
g_disable = true
g_mainWidget = ...
def setDisableWidgets():
''' This will get more exciting when you have other widgets, too, for sure '''
widgets = mainWidget.findChildren(QtGui.QWidget)
for widget in widgets:
# filter out push button though based objectName for instance
widget.setDisabled(disable)
# Toggle for the next run
disable = not disable
app = QApplication(sys.argv)
button = QPushButton("Click me")
button.clicked.connect(setDisableWidgets)
button.show()
app.exec_()
In your QThread run, you could extend the code that runs that function the way that, for instance is checks against the disable variable after processing each row or even small units if you wish and if that is false, it runs QThread.sleep(100) or so, and then wakes up for a check against disable. If it is false, then it continues the execution, otherwise sleeps back.
Here is the pseudo code:
class MyThread (QThread):
def run():
isEnd = false
while not isEnd:
while disable:
self.msleep(200)
# Process next row
# if we are done, isEnd = true

Odd function behaviour with Tkinter

We have some code in an event callback that looks like:
...
self.position.side = -self.position.side
self.update_with_board() # displays self.position graphically
ai_move = self.brain.get_move(self.position)
...
update() is called immediately but does not affect the GUI until the ai_move line.
However, when I do:
...
self.position.side = -self.position.side
self.update_with_board() # displays self.position graphically
raw_input()
ai_move = self.brain.get_move(self.position)
...
it updates graphically immediately as it asks for the input. I don't know what to make of this: maybe funky lazy evaluation or a tkinter scheduling thing I don't know about? How do I get the GUI to update in the order specified instead of delayed?
EDIT: Sorry, I wasn't using the built-in update() method, but rather one I defined to draw. I renamed it update_with_board(), and see the same behavior.
def update_with_board(self):
for i in range(8):
for j in range(8):
color = "gray" if (i+j) % 2 else "white"
self.canvas.create_rectangle(self.square * i, self.square * j, self.square * (i+1), self.square * (j+1), fill=color)
if self.position.board[8 * j + i] in self.ims.keys():
self.canvas.create_image(self.square * i + self.square/2,
self.square * j + self.square/2, image = self.ims[self.position.board[8 * j + i]])
Doing a bit more research, I'm pretty sure my comment is correct.
The UI updates all widgets as needed every time through the event loop. Calling update basically just forces it to run the event loop now. So, if you're in the middle of an event callback, you're recursively entering the event loop, which is a very bad thing, and can lead to infinite recursion.
As The TkInter Book says, update:
Processes all pending events, calls event callbacks, completes any pending geometry management, redraws widgets as necessary, and calls all pending idle tasks. This method should be used with care, since it may lead to really nasty race conditions if called from the wrong place (from within an event callback, for example, or from a function that can in any way be called from an event callback, etc.). When in doubt, use update_idletasks instead.
Similarly, the TkInter reference says:
This method forces the updating of the display. It should be used only if you know what you're doing, since it can lead to unpredictable behavior or looping. It should never be called from an event callback or a function that is called from an event callback.
Testing things out, it seems like at least in some cases, TkInter just ignores you when you call update from inside the event loop. Presumably to protect you from infinite recursion, but I haven't looked at the code to verify that.
At any rate, this isn't the function you want, so it doesn't really matter why exactly it isn't doing what it wasn't documented to do.
If you need to trigger an update from within an event callback, call update_idletasks. This actually calls all pending "idle" tasks, including redraws, without calling any event callbacks. (Not "update next time we're in the event loop and have nothing else to do".)
Meanwhile, calling raw_input inside an event callback is even worse. You're blocking the main event loop on terminal input. Unless this was just something you did for debugging purposes, it's a very bad idea. And even for debugging purposes, it's a very odd thing to test, and it's entirely plausible that whatever happens will have no relevance to normal behavior.
For more background, see the question TkInter: How do widgets update, or search update_idletasks on this site, or look at some of the Related links on the right side (at least two of which are relevant).
Based on your edit, as it turns out, it sounds like you had kind of the opposite problem—you were just changing things without telling TkInter to do anything about it, so the changes wouldn't show up until the next time through the event loop (which means not until after you return from this function). But the answer is basically the same—one way it's "replace your call to update with update_idletasks", the other way it's "add a call to update_idletasks".

Categories