Resetting button to original text pyqt5 - python

I have a button called "start" and when I click the button, it becomes "Pause", when I click it again, it doesn't revert back to "start" it just stays as is and I need to be able to have it go back to the original text. How do I go about this? Right now I have this
def button_click(self):
self.start_button.setText("Pause")
This changes the start button to pause but I don't know how to go from pause to start.

Once you set a property on an object, it doesn't "remember" its previous state, unless you provide that feature in some way.
A simple solution, at least for boolean-like objects, would be to store the current state, and toggle between them.
self.start_button = QPushButton('Start')
self.start_state = False
# ...
def button_click(self):
self.start_state = not self.start_state
if self.start_state:
self.start_button.setText("Pause")
else:
self.start_button.setText("Start")
An alternative could be to use a checkable button instead, which is also more clear from the UX perspective as the pressed/unpressed state is much more visible to the eye than a string (imagine using "Start"/"Stop", which are very similar).
Since the clicked also sends the checked state automatically, you can add the argument to the function you already have and check that instead.
self.start_button = QPushButton('Start')
self.start_button.setCheckable(True)
# ...
def button_click(self, state):
if state:
self.start_button.setText("Pause")
else:
self.start_button.setText("Start")
Note that for this it's usually preferred to use the toggled signal instead, since it also reacts to setChecked() and toggle() functions.

I don't think there's a nice fancy way to do this. Probably just do the following:
def button_click(self):
if self.start_button.text() == "Pause":
self.start_button.setText("start")
elif self.start_button.text() == "start":
self.start_button.setText("Pause")

Related

Python tkinter: Delete Menu checkbutton

I want to delete a menu checkbutton when i right click on it.
Its usually done with bind("Mouse3", deletefunction) , BUT i need an actual instance of a checkbutton to bind it with, and the only way to add a checkbutton to a menu i know is a add_checkbutton() method (and i have no access to instance this way). Is there any way i could do this?
import tkinter as tk
root = tk.Tk()
menubar = tk.Menu(root)
view_menu = tk.Menu(menubar, tearoff=0)
view_menu.add_checkbutton(label="Right click on me to delete", onvalue=1, offvalue=False)
# I want to do something like this:
# c = Checkbutton(label="Right click on me to delete")
# c.bind("Mouse3", my_delete_function())
# view_menu.add(c)
menubar.add_cascade(label='View', menu=view_menu)
root.config(menu=menubar)
root.mainloop()
To the best of my understanding, there's essentially two parts to your question:
Can the menu bar item be assigned for manipulation after?
Can the referenced item be then bound to an event?
The first answer is, sort of. While you can't exactly assign the object, you can reference it by index like this:
view_menu.delete(0)
Since you added the checkbutton first, it'll have an index of 0. You can either keep track of the indices, or refer to the item by its label. See this related answer from Bryan Oakley. e.g.:
view_menu.delete(view_menu.index("Right click on me to delete"))
The .index() method will locate the index by the menu entry's label, which can be handy unless you have the same label more than once...
The second answer, as far as I'm aware, there doesn't seem to be any effective binding for typical events like mouse clicks. However after some search I did come across a rather hidden <<MenuSelect>> binding that at least triggers an event. That by itself is not useful to your quest, but you can combine the event state with the checkbutton's command argument as well as a boolean flag to trigger an event on click:
# Add a BooleanVar for flagging:
delete_checkbutton = tk.BooleanVar()
# Add a binding to your view_menu
view_menu.bind('<<MenuSelect>>', event_state)
# Define the callback function:
def event_state(e):
if bool(e.state & 0x0400): # watch for the Mouse Right click state
# you might opt to use 0x0004 or 0x0001 instead
# i.e. Ctrl+click or Shift+Click
delete_checkbutton.set(True)
else: # If the state is not right click, reset the flag
delete_checkbutton.set(False)
# Define a self_delete command for the checkbutton
def self_delete():
if delete_checkbutton.get():
view_menu.delete(view_menu.index("Right click on me to delete"))
# Add the command to your checkbutton
view_menu.add_checkbutton(label="Right click on me to delete", onvalue=lambda:print('hey'), offvalue=False, command=self_delete)
Note: You will actually have to hold right click and then left click on the checkbutton to delete it. Obviously the drawback is you have now triggered the on/off value, and you might need to have some additional handling on those.
If right + left click is too awkward, Ctrl/Shift is another mod state you might consider.
Another side note: I'm a proponent of OOP when it comes to tkinter, it makes accessible variables and flags much easier without needing to worry the global and nonlocal namespaces. Here since delete_checkbutton is set in the global namespace I avoied using the global keyword and accessed it via the tk.BooleanVar() object. However if you were to use a python boolean (e.g. flag = True) then it won't be as effective unless you indicate global flag in both functions. If however you took an OOP approach you can reference the flags directly via self.flag without ambiguity.
Finally, here are the comprehensive changes implemented into your code for sampling:
import tkinter as tk
def event_state(e):
if bool(e.state & 0x0400):
# you might opt to use 0x0004 or 0x0001 instead
# i.e. Ctrl+click or Shift+Click
delete_checkbutton.set(True)
else:
delete_checkbutton.set(False)
def self_delete():
if delete_checkbutton.get():
view_menu.delete(view_menu.index("Right click on me to delete"))
root = tk.Tk()
menubar = tk.Menu(root)
delete_checkbutton = tk.BooleanVar()
view_menu = tk.Menu(menubar, tearoff=0)
view_menu.add_command(label='dude', command=lambda: print('dude'))
view_menu.add_checkbutton(label="Right click on me to delete", onvalue=lambda:print('hey'), offvalue=False, command=self_delete)
menubar.add_cascade(label='View', menu=view_menu)
root.config(menu=menubar)
view_menu.bind('<<MenuSelect>>', event_state)
root.mainloop()
All that said, I am of the opinion that this is not a very smooth User Experience and is somewhat confusing. Just the permanent deletion of the menu item alone is questionable at best, combined with the method you are trying to call upon the deletion feels even more contrived. I'd suggest revisiting your UX flow to consider how to streamline this.

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

Pygame: Why do both buttons activate if one hasn't been checked yet?

I've set up a basic Pygame graphical interface, but I'm having trouble with my buttons. I created a Button class, and the function to be executed by the button is determined in the __init__() method. In other words, I input the function when I create an instance of Button via lambda expression. The relevant code of the buttons basically looks like this:
class Button():
def __init__(self, action):
self.command = action
def check(self): # To be called while iterating through pygame.event.get()
if event.type == MOUSEBUTTONUP and self.rect.collidepoint(pygame.mouse.get_pos()):
self.command()
I also created a Window class where each instance is list of the buttons to be seen at a time:
class Window():
def __init__(self, buttons):
self.is_visible = False # This determines whether the buttons in this
# window should be updated, checked, and drawn
self.buttons = list(buttons)
def open(self):
self.is_visible = True
def close(self):
self.is_visible = False
def trans(self, new_window):
self.close()
new_window.open()
Next, I set up two instances of Window, each with a Button to toggle back to the other:
WINDOW_1 = Window([Button(lambda: WINDOW_1.trans(WINDOW_2))])
WINDOW_2 = Window([Button(lambda: WINDOW_2.trans(WINDOW_1))])
And finally:
WINDOW_1.is_visible = True
Here comes the problem.
Each button works exactly how it is supposed to: it closes the open window and opens the closed window. Unfortunately, if I click the mouse in a spot where both buttons overlap (or where they would overlap if they were both visible), the function for WINDOW_2's button is called immediately after the function for WINDOW_1's button is called. Basically, WINDOW_1 -> WINDOW_2 -> WINDOW_1, and it all happens in the same loop.
However, if we start from WINDOW_2, then this happens: WINDOW_2 -> WINDOW_1. It appears that the glitch is only one-way. I just can't figure out what's wrong here, I would really appreciate some help. (Just in case, here's a link to the full code so you can maybe reproduce the problem; I've set the buttons' location so that the bottom half of the first button overlaps the top half of the second: http://pastebin.com/m1zCQLRF). Thank you for reading and thank you in advance for answering!
Consider these lines (from the pastebin):
all_windows=[WINDOW_1,WINDOW_2]
....
for event in pygame.event.get():
for window in all_windows:
if window.is_visible:
for button in window.buttons:
button.update()
button.check()
If WINDOW_1's button is clicked, WINDOW_2.is_visible becomes True.
In the next iteration of the for window in all_windows loop, the check method of WINDOW_2 will be called because its is_visible attribute is now True. Because this is still the same iteration of for event in pygame.event.get(), WINDOW_2.check() sees the same MOUSEBUTTONUP event. The Button objects overlap, so the event causes the windows' visibility to toggle a second time, back to the state where WINDOW_1 is visible, and this is what is drawn.
Incidentally, using event.pos would be more accurate than pygame.mouse.get_pos() in the check() method. The former is the mouse's position at the time the event was posted, while the latter is the current position of the mouse.
Edit
I got the pastebin running with some tweaking and verified that what I described above is the problem by applying a quick and dirty fix.
First, I edited Button.check() so it returns a boolean that shows whether or not its check validated:
def check(self):
if event.type==MOUSEBUTTONUP and self.rect.collidepoint(event.pos):
self.command()
return True
return False
Then altered the code shown above to break out of the all_windows loop if check returns True (i.e. if a window is closed).
for window in all_windows:
if window.is_visible:
for button in window.buttons:
button.update()
_break = button.check()
if _break:
break
Now the windows close and open as expected when a button is clicked. Again, that's not a "real" fix just a confirmation of what caused the problem.

Python Tkinter use to emulate blinking with interaction of multiple buttons

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.

accessing the return value of function that was bound to an event (tkinter)

Basically, what I've done is bound a click event to a function. For example:
self.button1.bind("<Button-1>",self.chooseDice)
What I want now is to access the result of chooseDice() in another function. What is the best way to go about doing that?
class GraphicsInterface:
#we want to initialize the game board here, set up the dice and buttons
def __init__(self):
self.window = Tk()
self.window.geometry("720x500")
#buttons under each die
self.clicked=[] #empty list to collect all the buttons that were clicked (see chooseDice function)
self.button1 = Button(self.window, text="Dice 1", width=13) #create the button object
self.button1.place(x=60, y=160)
#bind button click event to a function (chooseDice())
self.button1.bind("<Button-1>",self.chooseDice)
self.button2 = Button(self.window, text="Dice 2", width=13)
self.button2.place(x=185, y=160)
self.button2.bind("<Button-1>",self.chooseDice)
#using the event as an argument, append the text to the clicked list
def chooseDice(self, event):
self.clicked.append(event.widget.cget('text'))
self.diceList=[] #create a new empty list
for i in range(len(self.clicked)):
self.diceList.append(int(self.clicked[i][5])) #get just the int value of the last character (i.e. the dice number)
self.deactivate(event.widget) #deactivate the button
return self.diceList
You are already doing what you need to do. Your example code sets self.diceList to some value. Anywhere else in your code you can directly use self.diceList.
By the way -- you're writing code that is going to be hard to maintain over time. For example, what if you change the dice label to "Dice One" or simply "One" rather than "Dice 1"? Or, as your app progresses you might want graphical images instead of text on the buttons. You'll have to modify the code that parses the button name. You are essentially encoding information in a button label which is not a good idea.
A simple solution, that also makes your chooseDice method simpler and easier to understand, is to pass in the dice number in the callback. For example:
self.button1.configure(command=lambda btn=self.button1: self.chooseDice(btn, 1))
The above passes two parameters to the chooseDice method: the button instance (so you can disable it) and the button number (so you don't have to parse the button name to get it)
This also allows you to create your dice in a loop rather than hard-coding multiple copies of the same block of code. Here's a complete working example:
from Tkinter import *
class GraphicsInterface:
def __init__(self):
self.window = Tk()
self.window.geometry("720x500")
self.clicked=[]
self.buttons = []
for n in range(1, 3):
btn = Button(text="Button " + str(n))
btn.configure(command=lambda btn=btn, n=n: self.chooseDice(btn, n))
btn.pack()
self.buttons.append(btn)
btn = Button(text="Go!", command=self.go)
btn.pack()
self.window.mainloop()
def go(self):
print "buttons:", self.clicked
self.reset()
def reset(self):
'''Reset all the buttons'''
self.clicked = []
for button in self.buttons:
button.configure(state="normal")
def chooseDice(self, widget, number):
self.clicked.append(number)
widget.configure(state="disabled")
app = GraphicsInterface()
Finally, some last bits of advice:
Don't use place, it makes your GUIs harder to create, and they won't react well to changes in window size, changes in font, changes in platform, etc. Use pack and grid instead. Also, don't create fixed-width buttons. Again, this is to better handle changes in fonts. There are times when you want fixed width buttons, but it doesn't look like your code has any reason to use them.
Finally, I don't know what you're actually trying to accomplish, but usually if you're using buttons to track state (is this thing pressed or not?) you want to use checkboxes (pick N of N) or radiobuttons (pick 1 of N). You might want to consider switching to those instead of to buttons.
Refactor. Split this into two functions.
One returns the proper result, usable by other objects.
The other is bound to a GUI control, and uses the proper result to activate and deactivate GUI objects.
Indeed, you should always do this. You should always have functions that do normal Python stuff, work correctly without the GUI and can be unit tested without the GUI. Then you connect this working "model" to the GUI.
just add a self.result attribute to your class and set it at chooseDice()

Categories