I'm writing a Tkinter program where I have an option menu with other UI elements. I have a requirement that if optionmenu drop down remains active for a particular time (say 1 minute), I need to close drop down menu and cancel the selection. I was wondering if there is a way to know if an optionmenu dropdown is active. I've looked at this particular link but couldn't locate any specific method for what I need.
Keeping track of whether the menu is closed or open is easily done with a command and click binding. The hard part is forcing the menu to close. Either a physical mouse click off the widget or press of Escape will do so. But generating events does not do. The #commented_out bits below are failed experiments.
import tkinter as tk
root = tk.Tk()
n = 2
after_id = None
menu_open = False
omvar = tk.StringVar(root)
def timeout():
print('timeout called')
omvar.set('')
#root.event_generate('<Key-Escape>', keysym='Escape', keycode=9)
#om.destroy()
#root.event_generate('<FocusIn>')
#root.focus_get()
#root.event_generate('<Button-1>', x=10, y=20)
root.update()
def open(event):
print('open called')
global after_id, menu_open
after_id = root.after(n*1000, timeout)
menu_open = True
def close(val):
print('closed by', val)
global after_id, menu_open
root.after_cancel(after_id)
after_id = None
menu_open = False
om = tk.OptionMenu(root, omvar, 'a', 'b', 'c', command=close)
om.bind('<Button-1>', open)
om.pack()
root.mainloop()
A MenuButton + Menu would look nearly the same and might be easier. Would you consider that?
First define a variable,(for example: ddo = 0)
Then Trigger a click on your dropdownlist, when clicked, toggle ddo = 1(if ddo == 1 then ddo = 0)
and when ddo == 1 a timer start to count for 1second, after that 1second if ddo == 1, then close selectbox(for example by focusing on another element) and do what ever you want. an example pseudo code is here:
def OnClick(event):
if(ddo == 0):
ddo = 1
startTimerForOneSec(whenFinished=finishTimer())
else:
ddo = 0
def finishTimer():
if(ddo == 1):
focusOneSomeOtherElements()
doSomeThingElse()
Related
I actually don't know why it does this but something is wrong with root = Label(master,text =(click))
Label.pack()
but basically what this program does is simple its just a clicker game and thing is everything else works its just when im trying to add a counter to count how many clicks a user has clicked it doesnt work
from tkinter import *
import time
from tkinter import messagebox
master = Tk()
def uiPrint():
info()
print("")
print(click)
blankLine()
Label_1 = Label(text = "Double click purchases need 50 clicks!")
Label_1.pack()
click = 0
mult = 1
dcp1 = 0
def blankLine():
for i in range(20):
print("")
def purchaseDoubleClicksCommand():
global click
global mult
if click < 50:
messagebox.showinfo("showinfo", "Not enough clicks!")
elif click >= 5:
mult = mult+1
click = click - 50
messagebox.showinfo("showinfo", "Double Clicks Purchased!")
def buttonCommand():
global click
global mult
click += 1*(mult)
root = Label(master,text =(click))
Label.pack()
mainClickButton = Button(master, text="Click!", command = buttonCommand)
mainClickButton.pack()
purchaseDoubleClickButton = Button(master, text="Purchase Double Clicks", command = purchaseDoubleClicksCommand)
purchaseDoubleClickButton.pack()
master.title("Clicker!")
master.geometry("%sx%s+%s+%s" % (400,100,512,512))
mainloop()
It seems that you are calling an instance method without an active instance:
Label.pack()
It is only possible for methods that are callable from the class itself and doesn't require an active instance/object e.g. methods decorated with #classmethod and #staticmethod. Thus change it to:
root.pack()
With the instance root, the self argument would automatically be passed as first argument, which references the root object itself.
Use root.pack() instead of Label.pack()
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this question
Is there a way to switch the color of a button every time it's clicked (for example, it starts off white, the first time it's clicked it turns blue, the next time it's clicked it turns back to white, if it's clicked a third time it goes to blue again... And on and on in the same pattern).
If this is possible, is there a way for python to remember what color the button was the previous session, and stay that color the next time it's launched?
Yes, it is possible.
Here, the text color toggles from red to blue when the button is pressed.
If you change the keyword argument of the button option from fg to bg, you will, on certain systems (not on OSX) change the background color instead of the text color. Try it if you wish.
import tkinter as tk # <-- avoid star imports
def toggle_color(last=[0]): # <-- creates a closure to memoize the last value of the colors index used to toggle
colors = ['red', 'blue']
color = colors[last[0]]
last[0] = (last[0] + 1) % 2 # <-- ensure the index is 0 or 1 alternatively
btn.config(fg=color)
if __name__ == '__main__':
root = tk.Tk()
btn = tk.Button(root, text='toggle', fg='blue', command=toggle_color)
btn.pack()
root.mainloop()
Unlike the other answers, I'd suggest an Object Oriented approach.
The below example uses the tkinter Button class as a basis but adds the on_color and off_color parameters to specify what color your want the button to be when it is on/off.
This way you can have as many buttons as you want that have similar behavior but without having to have a function to change the state of every button. This behavior is defined in the class instead.
on_color and off_color are optional parameters that will default to 'green' and 'red' respectively if not specified.
import tkinter as tk
def pressed():
print("Pressed")
class ToggleButton(tk.Button):
def __init__(self, master, **kw):
self.onColor = kw.pop('on_color','green')
self.offColor = kw.pop('off_color','red')
tk.Button.__init__(self,master=master,**kw)
self['bg'] = self.offColor
self.bind('<Button-1>',self.clicked)
self.state = 'off'
def clicked(self,event):
if self.state == 'off':
self.state = 'on'
self['bg'] = self.onColor
else:
self.state = 'off'
self['bg'] = self.offColor
root = tk.Tk()
btn = ToggleButton(root,text="Press Me", on_color='blue',off_color='yellow',command=pressed)
btn.grid()
btn2 = ToggleButton(root,text="Press Me",command=pressed)
btn2.grid()
root.mainloop()
In order to remember what state each button was in, you'll need to save a file on close and open it again on start up. In the past, I've done this by writing a JSON file containing a dictionary of all my settings. To do something when the application closes you can bind a function call to the window manager's delete window method
root.protocol("WM_DELETE_WINDOW", app.onClose)
This will call the onClose method of the app when the window is closed. Inside this method, you just collect up the various states of the buttons and write them to a file. For example
settings = {}
settings['btn2_state'] = btn2.state
with open('settings.json','w') as settings_file:
settings.write(json.dumps(settings))
This can then be read back in again when you open the program up again with something like
with open('settings.json','r') as settings_file:
settings = json.load(settings_file)
btn2.state = settings['btn2_state']
You do can change the color of the button as the button is pressed. There are several ways to do it. I have posted some snippets.
Here is a code:
from tkinter import * #for python2 Tkinter
global x
x = 0
root = Tk()
def b1c(): #this function will run on button press
global x
if x == 0:
b1["bg"] = "blue"
x += 1
elif x == 1:
b1["bg"] = "white"
x = 0
b1 = Button(root,text="Press me to change the color",command = b1c) #making button
if x == 1:b1["bg"] = "white";x =0
b1.place(x=1,y=1) #placing the button
mainloop()
The code above is a bit complex so if you want an easy way to do it then I have made another code. You can also change the colors of the button by changing the values of color1 and color2 (currently white and blue):
from tkinter import * #for python2 Tkinter
root = Tk()
color1 = "white" #the default color
color2 = "blue" #the next color
def b1c(): #this function will run on button press
if b1.cget("bg") == color2:b1["bg"] =color1 #getting current button color
elif b1.cget("bg") == color1:b1["bg"] =color2
b1 = Button(root,text="Press me to change the color",bg=color1,command = b1c)
b1.place(x=1,y=1)
mainloop()
If you have a list of colors which you want to change one by one when the button is pressed then you can also do it. In the following code , the list is colors:
from tkinter import * #for python2 Tkinter
root = Tk()
colors = ["red","blue","green","sky blue"] #place your colors in it which you want to change 1-by-1 (from left to right)
def b1c():
for colo in colors:
if b1.cget("bg") == colo:
try:
color = colors[colors.index(colo)+1]
except:
color = colors[0]
b1["bg"] = color
break
b1 = Button(root,text="Press me to change the color",bg=colors[0],command = b1c)
b1.place(x=1,y=1)
mainloop()
Below is a popup menu command I would like to send event data through. I want the event for its x and y data so I know what cell of the ttk styled treeview (being used as a table) to operate on. Currently it calls the "self.toggle_sort_bool" method but I want it to call the function at the end "self.sort_children(event, cur_tree_children)," but do not because I need to figure out passing/receiving the event here. Note: I know that sending is automatic but receiving is not. Am I overlooking something?
self.heading_popup_menu = tk.Menu(self.treeview, tearoff=0)
self.heading_popup_menu.add_command(label="Reverse Sort", command=self.toggle_sort_bool)
Here is where the journey of the event begins with a right click on the ttk styled treeview.
self.treeview.bind('<Button-3>', self.pop_up_right_click_detail)
The event's x_root and y_root are sent to the tk_popup. Should/can I overload this to send the whole event? It seems the x and y of the event in the root are sent to tell the popup where to...pop up.
def pop_up_right_click(self, event):
try:
self.heading_popup_menu.tk_popup(event.x_root, event.y_root, 0)
finally:
self.heading_popup_menu.grab_release()
Here is the function I want to call from the menu command.
def sort_children(self, event, cur_tree_children):
region = self.treeview.identify("region", event.x, event.y)
if region == "heading":
#get column number
col = self.treeview.identify_column(event.x)
col = int(re.sub('\D', '', col))
col_names = cur_tree_children.pop(0)
cur_tree_children.sort(reverse=self.reverse_sort_bool.get(), key=lambda tup: self.sort_disparate_types(tup[col-1])) #toggle reverse somehow
cur_tree_children.insert(0, col_names)
self.depopulate_tree()
self.populate_tree()
Is it possible to send an event through a menu? I am confused because of how disjointed the--for lack of better terminology--events are in calling a function through a right click pop up menu. While this is all part of one big GUI class, I do not want to use class instance variables to communicate the target cell data because I believe that is messy and bad practice and thus should be avoided wherever possible.
P.S. If I had enough reputation I would make the tag BryanOakley and post this under it.
A common way to do what you want is to modify the menu command immediately before displaying the menu. You can either define postcommand which defines a function that is run before the menu is displayed, or you can do the modification in the code that causes the menu to pop up.
Since you want to pass the event to the function, the best solution is to modify the menu right before popping it up since that function already has the event object.
Another other option would be to have your function set some instance variables, rather than modifying the menu. You can then reference those instance variables in the function called from the menu.
Since you said you don't want to use instance variables, here's an example showing how to modify the menu:
def show_popup(self, event):
self.popup.entryconfig("Do Something", command=lambda: self.something(event))
self.popup.tk_popup(event.x_root, event.y_root)
Example
Here's a complete working example. The code displays a window with a treeview widget which has some dummy data. If you right-click over the treeview you will see a menu with one item. When you click that item it will display information in a label about where the click occurred.
import tkinter as tk
from tkinter import ttk
class Example(object):
def __init__(self):
self.root = tk.Tk()
self.treeview = ttk.Treeview(self.root, columns=("one", "two", "three"))
self.label = tk.Label(self.root, width=40)
self.label.pack(side="bottom", fill="x")
self.treeview.pack(fill="both", expand=True)
self.popup = tk.Menu(self.root, tearoff=False)
self.popup.add_command(label="Do something")
self.treeview.bind('<Button-3>', self.show_popup)
for column in self.treeview.cget("columns"):
self.treeview.column(column, width=50)
for i in range(10):
values = ("a%s" % i, "b%s" %i, "c%s" %i)
self.treeview.insert('', 'end', text="Item %s" % i, values=values)
def start(self):
self.root.mainloop()
def show_popup(self, event):
self.popup.entryconfig("Do something", command=lambda: self.do_something(event))
self.popup.tk_popup(event.x_root, event.y_root)
def do_something(self, event):
region = self.treeview.identify("region", event.x, event.y)
col = self.treeview.identify_column(event.x)
message = "you clicked %s,%s region=%s column=%s" % (event.x, event.y, region, col)
self.label.configure(text=message)
if __name__ == "__main__":
Example().start()
I am trying to create a Tkinter app, where when you press a button, a new Window opens which has continually updating text about certain parts of the program. My problem is the section of code where I am trying to add the text to the screen. This is what I have written:
import tkinter as tk
import time
class TextWindow(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.textArea = tk.Text(self, height = 10, width = 30)
self.textArea.pack(side = "left", fill = "y")
bar = tk.Scrollbar(self)
bar.pack(side = "right", fill = "y")
bar.config(command = self.textArea.yview)
def output(self, value):
outputVal = str(value)
self.textArea.inser('end', "{0}\n".format(outputVal))
self.textArea.see('end')
def openWindow():
textWindow = tk.Toplevel(root)
textFrame = TextWindow(textWindow)
textFrame.pack()
value = 0.0
alive = True
while alive:
if textWindow.winfo_exists:
value = value + 0.1
textFrame.output(value)
time.sleep(0.1)
else:
alive = False
root = tk.Tk
btn = tk.Button(root, text = "Click", command = openWindow)
btn.pack()
root.mainloop()
When I comment out the while loop in the openWindow method, the window opens and closes, and reopens, no problem. However when the code is there, I never see the window when I press the button.
I tried running it through the IDLE debugger, and I am not getting any errors, and everything runs through the loop fine, however the Window still never appears. What is my problem?
The answer that Jason S gave is not a good example. You can avoid any issues with sleep by just using after() instead. Don't settle for "Kinda works".
Here is a break down of how you could accomplish what you need without having the problems associated with sleep() and tkinter.
First you are importing Tk() wrong. Don't do tk.Tk do tk.Tk()
Now lets move the entire program into a single class. This will provide us with the ability to use class attributes and make things a bit easier to work with.
Here we create a class called guiapp(tk.Frame): you can name it what you want but this is just my example. Then make sure you are passing root using guiapp(root) so we can work in this class on the tk.Tk() instance. This will be shown at the bottom of the program where the class is instantiated.
Because we have passed root to the class we can place the button that opens the Toplevel window on our self.master attribute.
UPDATE: Changed how data is sent to the Textbox in Toplevel so we can retain the information in case you want to reopen top level. per your comment.
import tkinter as tk
class guiapp(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.master = master
self.value = 0.0
self.alive = True
self.list_for_toplevel = [] # added list to retain values for Toplevel
btn = tk.Button(self.master, text = "Click", command = self.TextWindow)
btn.pack()
Here we add the method to define the Topelevel we are going to create.
Because everything is inside this one class we can create this Topelevel as a Toplevel of self.master. At the end of this method we call the self.timed_loop() method I added that manages the timed portion of your program. UPDATE: added a call to a new function.
def TextWindow(self):
self.textWindow = tk.Toplevel(self.master)
self.textFrame = tk.Frame(self.textWindow)
self.textFrame.pack()
self.textArea = tk.Text(self.textWindow, height = 10, width = 30)
self.textArea.pack(side = "left", fill = "y")
bar = tk.Scrollbar(self.textWindow)
bar.pack(side = "right", fill = "y")
bar.config(command = self.textArea.yview)
self.alive = True
self.add_list_first()
UPDATE: Added a new function called add_list_first(self):. This will allow us to first add any values that are stored in the list then we can call timed_loop() to continue appending the list and counting.
def add_list_first(self):
for item in self.list_for_toplevel:
self.textArea.insert('end', "{}\n".format(item))
self.textArea.see('end')
self.timed_loop()
Here we have created a method to perform the task you have in you code for the Toplevel that uses the after() function from tkinter. ever 1000 is equal to 1 second, so play with that timer if you want. The first part of after() is for the time in milliseconds and the 2nd part is the function being called. In this case it calls itself to continue the loop until either the Toplevel window self.textWindow is closed or the self.alive variable is no longer True.
UPDATE: I have added a for loop to insert the list instead of directly imputing each value. This way we can retain the data if we want to reopen the Toplevel.
def timed_loop(self):
if self.alive == True and tk.Toplevel.winfo_exists(self.textWindow):
self.master.after(1000, self.timed_loop)
self.value += 1
self.list_for_toplevel.append(self.value)
self.textArea.delete(1.0, "end-1c")
for item in self.list_for_toplevel:
self.textArea.insert('end', "{}\n".format(item))
self.textArea.see('end')
else:
self.alive = False
This is the preferred way to start your class going in tkinter. As you can see we have created root as tk.Tk() and passed root into the the class guiapp(). Also note that I assigned this instance of the class to the variable name myapp. This will allow us to interact with the class from outside of the class if you ever need to. It does not make a difference in this case but I thought I would add it just the same.
if __name__ == "__main__":
root = tk.Tk()
myapp = guiapp(root)
root.mainloop()
Here is the copy paste version for you to use.
import tkinter as tk
class guiapp(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.master = master
self.value = 0.0
self.alive = True
self.list_for_toplevel = []
btn = tk.Button(self.master, text = "Click", command = self.TextWindow)
btn.pack()
def TextWindow(self):
self.textWindow = tk.Toplevel(self.master)
self.textFrame = tk.Frame(self.textWindow)
self.textFrame.pack()
self.textArea = tk.Text(self.textWindow, height = 10, width = 30)
self.textArea.pack(side = "left", fill = "y")
bar = tk.Scrollbar(self.textWindow)
bar.pack(side = "right", fill = "y")
bar.config(command = self.textArea.yview)
self.alive = True
self.add_list_first()
def add_list_first(self):
for item in self.list_for_toplevel:
self.textArea.insert('end', "{}\n".format(item))
self.textArea.see('end')
self.timed_loop()
def timed_loop(self):
if self.alive == True and tk.Toplevel.winfo_exists(self.textWindow):
self.master.after(1000, self.timed_loop)
self.value += 1
self.list_for_toplevel.append(self.value)
outputVal = str(self.value)
self.textArea.insert('end', "{0}\n".format(outputVal))
self.textArea.see('end')
else:
self.alive = False
if __name__ == "__main__":
root = tk.Tk()
myapp = guiapp(root)
root.mainloop()
The problem is that you are never giving control back to the Tkinter main loop. The code gets stuck executing in the while loop, meaning Tkinter never gets to refresh the display or process any other events. You could force update by calling root.update() right before time.sleep(0.1), but this is not really optimal and the display will be unresponsive while sleeping. Depending on what you are doing, it may be good enough.
See here for additional explanation
Im trying to make a simple GUI with Tkinker that, when you press a button it adds 1 to the text on a label. However, the label simply remains at 0. Is there a way I can refresh it so it stays up-to-date?
heres what i have so far:
from Tkinter import *
clicks = 0
def click():
global clicks
global Window
clicks += 1
print 'ok', clicks
def close_window():
global Window
Window.destroy()
Window = Tk()
Number = Label(text = clicks)
close = Button(Window , text='exit' , command = close_window)
button = Button(Window,text = 'clickme' ,command = click)
button.pack()
close.pack()
Number.pack()
Window.mainloop()
clicks += 1 only changes the variable clicks.
Use Label.config(text=...) or Label['text'] = ... to change the label text.
def click():
global clicks
clicks += 1
Number.config(text=clicks) # <------
print 'ok', clicks
You almost have it, but for your label you don't want to use "text", you want "textvariable". However, this takes a StringVar as a variable, which forces a little bit of busywork:
Window = Tk()
strclicks = StringVar()
Number = Label(textvariable=clicks)
and within click():
clicks += 1
strclicks.set(clicks)
Using "text" evaluates the variable at creation; "textvariable" updates the label when the variable updates.