I'm brand new to python (started today). I'm looking to automate something I do a lot of, so far I have 40% of what I need from googling and trying things out that I have found.
I'm trying to produce a counter, when I click the button, I want the counter to increase - I have this bit working...
from tkinter import *
root = Tk()
#Definitions of the fruit, links in with buttons with e1/2/3
def Appleadd_1(event):
value = int(e1.get())
value += 1
e1.delete(0, 'end')
e1.insert(0, value)
def Pearadd_1():
value = int(e2.get())
value += 1
e2.delete(0, 'end')
e2.insert(0, value)
def Grapeadd_1():
value = int(e3.get())
value += 1
e3.delete(0, 'end')
e3.insert(0, value)
#text boxes for counts
e1 = tk.Entry(root)
e1.insert(0, 0)
e1.pack()
e2 = tk.Entry(root)
e2.insert(0, 0)
e2.pack()
e3 = tk.Entry(root)
e3.insert(0, 0)
e3.pack()
#buttons
bt1 = tk.Button(root, text="Apples", command=Appleadd_1)
bt1.bind("<q>" , )
bt1.pack()
bt2 = tk.Button(root, text="Pears", command=Pearadd_1)
bt2.pack()
bt2.bind("1", bt2)
bt3 = tk.Button(root, text="Grapes", command=Grapeadd_1)
bt3.pack()
root.mainloop()
Although it looks ugly, it works and I have just found how to place things instead of using pack()
One thing I can't get to work, is binding a keyboard key to the buttons I have created. Is it possible?
Any help would be greatly appreciated!
Thank you
First off, you'll gain a lot by rewriting your code to use OOP. Tkinter like many other toolkits work best when using inheritance and classes to group widgets together.
As for your actual question, yes you can bind functions to keystrokes in tkinter, and it's relatively easy.
import tkinter as tk
def on_button_click(self, event=None): # command= takes a function with no arguments while .bind takes a function with one argument
print("Clicked the button!")
root = tk.Tk()
button = tk.Button(root, text="Click me!", command=on_button_click)
root.bind("<Control-a>", on_button_click)
Note that you can bind more than keypresses to functions. You can also bind mouse events like scrolling, clicking, or dragging the mouse, various keybind combinations like Shift+Tab, or Ctrl+F, and other events like the <Configure> event which is triggered when the window changes size, or the <Enter> and <Leave> event which are fired when you hover over the bound widget.
You must be wary though, because by default, a new binding will replace the existing binding (unless you pass in '+' as the bind method's third argument), and will trigger the callback for the widget that is currently focused (when applicable). For general purpose bindings, you should bind them to the root widget if possible.
Related
I have a ttk.Combobox that my users can select from a dropdown list of options, or manually type something in. I have it bound to Return, so that if my user presses return after making a change it will update, but if my user clicks in the box and accidentally types something else in, it will cause an error down the road. To be clear, I already have an event bound to a new selection, as well as pressing return.
I am asking if it is possible to check if the box value has been changed when focus leaves the box, and if so, then call a function? When I tried a FocusOut bind, everytime I click on one of the dropdowns it calls my function and doesn't let me select anything from the dropdown, so that isn't working.
selection.bind('<Return>', lambda event, entry=selection, row=row: update(
updated_entry=entry.get(), row=row, entry=entry))
selection.bind('<<ComboboxSelected>>', lambda event, entry=selection, row=row: update(
updated_entry=entry.get(), row=row, entry=entry))
edit: Here is a sample code. The way this is written, if the user selects an item from the dropdown, it updates the label. If the users types something in and presses Return, it updates the label. But if the user types something in, and clicks on the other dropdown, it does not update the label.
import tkinter as tk
from tkinter import ttk
def update(updated_entry, row, entry):
label = tk.Text(root, height=1, width=10)
label.insert(tk.END, updated_entry)
label.grid(row=row, column=2)
return 'break'
def gui(root):
root.geometry('300x150')
root.config(background='snow3')
for row in range(2):
options = ['test', 'test1', 'test2']
selection = tk.ttk.Combobox(root, value=options)
selection.bind('<Return>', lambda event, entry=selection, row=row: update(
updated_entry=entry.get(), row=row, entry=entry))
selection.bind('<<ComboboxSelected>>', lambda event, entry=selection, row=row: update(
updated_entry=entry.get(), row=row, entry=entry))
selection.grid(row=row, column=1)
label = tk.Text(root, height=1, width=10)
label.grid(row=row, column=2)
if __name__ == '__main__':
root = tk.Tk()
gui(root)
tk.mainloop()
ttk.Comboboxes are a subclass of Entry widgets, which means that you can add validation to them in the same manner as you would to their base class. Namely by using the validate= and validatecommand= options Entrys support.
The reason to do this is because "validation" will allow the contents of the associated Combobox to be checked when it loses focus—i.e. your stated goal. This should work fine in conjunction with the bound event-handling you already have. The following code, which is similar to your minimal reproducible example, illustrates how do to something like that.
Note: This approach would also allow doing some real validation of the values the user has entered to prevent problems later on if they're invalid.
import tkinter as tk
from tkinter import ttk
def update(updated_entry, entry):
''' Combobox change Callback. '''
entry.delete('1.0', tk.END)
entry.insert(tk.END, updated_entry)
def gui(root):
root.geometry('300x150')
root.config(background='snow3')
for row in range(2):
text = tk.Text(root, height=1, width=10) # Widget to be updated.
text.grid(row=row, column=2)
def check_okay(new_value, text=text):
update(new_value, text)
return True # Note: accepts anything.
combobox = ttk.Combobox(root, value=('test', 'test1', 'test2'),
validate='focusout',
validatecommand=(root.register(check_okay), '%P'))
combobox.grid(row=row, column=1)
combobox.bind('<Return>', lambda event, entry=combobox, text=text:
update(entry.get(), entry=text))
combobox.bind('<<ComboboxSelected>>', lambda event, entry=combobox, text=text:
update(entry.get(), entry=text))
if __name__ == '__main__':
root = tk.Tk()
gui(root)
tk.mainloop()
In python tkinter, I've got a program that generates multiple buttons with a default fg of red
from tkinter import *
root = Tk()
def dothis(i):
print(i)
button.config(fg='green')
for i in range(5):
button = Button(root, width=30, text="button{}".format(i), command=lambda i=i: dothis(i))
button.config(fg='red')
button.pack()
This creates this window:
In this program, I have attempted to make it so that once the button is pressed, the colour of the text (fg) turns green. Instead, when dothis(i) is called, it changes the colour of the last button generated to green. This is not what I want.
To summarise, when I click button3, I want to see this:
But instead, I see this (the last generated button is modified, not the one I want):
How can I work around this, while still keeping the buttons generated in a loop?
Note: The buttons must also be modifiable after changing the colour e.g. Once changed to green, it can be turned back to red.
You got the correct lambda expression, but the parameter you passed isn't related to the buttons you created. You should pass the Button widget as a parameter instead:
from tkinter import *
root = Tk()
def dothis(button):
button.config(fg='green')
for i in range(5):
button = Button(root, width=30, text="button{}".format(i))
button.config(fg='red', command=lambda i=button: dothis(i))
button.pack()
root.mainloop()
To achieve toggling between red and green, you can use ternary operator:
def dothis(button):
button.config(fg='green' if button["foreground"]=="red" else "red")
If you insist on all buttons except the last one being anonymous, and using command instead of binding events, you can use partials:
from tkinter import *
from functools import partial
root = Tk()
button_callbacks = {}
def on_click(button):
button.config(fg="green")
for i in range(5):
button = Button(root, width=30, text=f"button{i}", fg="red")
callback_name = f"on_click_{i}"
button_callbacks.update({callback_name: partial(on_click, button=button)})
button.config(command=button_callbacks[callback_name])
button.pack()
Using event binding would be a bit more straight-forward, but the behavior is not exactly the same as triggering a callback using command. Here's what that might look like:
from tkinter import *
root = Tk()
def on_click(event):
button = event.widget
button.config(fg="green")
for i in range(5):
button = Button(root, width=30, text=f"button{i}", fg="red")
button.bind("<Button-1>", on_click)
button.pack()
How to get the value from tkinter toplevel in side a 0 attribute function?
def Update():
Up = Toplevel()
Up.title("Update")
taskLabel = Label(Up, text ="Update", font=('Times', 20)).grid(row=0)
var = IntVar()
radio = Radiobutton(Up, text="Fully", variable=var, value=1, command = taskUpdate).grid(row=2, sticky=W)
radio = Radiobutton(Up, text="Partly", variable=var, value=2, command = taskUpdate).grid(row=3, sticky=W)
radio = Radiobutton(Up, text="Un", variable=var, value=3, command = taskUpdate).grid(row=4, sticky=W)
note = EntryaUp, width=30, font=('Arial', 12,)).grid(row=6)
Button = Button(Up, text ="Task Complete and Send Invoice", command = taskUpdate).grid(row =7)
return (var, note)
def updateB ():
var, noteBox = Update()
a = (var.get())
b = (note.get())
This is a top level window in tkinter and I want to get the value 1,2,3 when I click the radio button and get the string value when I enter text in the entry and click the command button, but I just have no idea how can I do that. By the way, I don't think I can return the value at all, every time when its return it only return it initial value which is 0 and " ". I know how it works and can get the value if the radiobutton is not in the top level (or in a function). But I could not figure it out how can I do it if the struture is like this.
The short answer to your question is that you can use wait_window() to wait for the window to be dismissed, and then you can return whatever you want from your function. You would then hook up your "Task complete" button to simply destroy the window.
You have to be very careful here -- wait_window creates a new, nested event loop. Most often this is done in conjunction with a window grab to prevent events in other windows from being processed. When you do this, you are creating a modal dialog.
Here's a working example; I had to tweak your code a little, and I took the liberty of removing some unnecessary variables (such as references to the radiobuttons, since you never do anything with the references)
import Tkinter as tk # python 2.7
class Example(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
button = tk.Button(self, text="Open window", command=self.on_button)
button.pack()
def on_button(self):
var, note = Update()
a = var.get()
b = note.get()
print "a: '%s' b: '%s'" % (a,b)
def Update():
Up = tk.Toplevel()
Up.title("Update")
tk.Label(Up, text ="Update", font=('Times', 20)).grid(row=0)
var = tk.IntVar()
tk.Radiobutton(Up, text="Fully", variable=var, value=1).grid(row=2, sticky="w")
tk.Radiobutton(Up, text="Partly", variable=var, value=2).grid(row=3, sticky="w")
tk.Radiobutton(Up, text="Un", variable=var, value=3).grid(row=4, sticky="w")
evar = tk.StringVar()
note = tk.Entry(Up, width=30, font=('Arial', 12,), textvariable=evar)
note.grid(row=6)
Button = tk.Button(Up, text ="Task Complete and Send Invoice", command = Up.destroy).grid(row =7)
Up.wait_window()
print "done waiting..."
return (var, evar)
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(side="top", fill="both", expand=True)
root.mainloop()
To see why you must be careful when using this function, try the following scenarios:
click on the "Open Window" button, fill in the form and click on the "Task Complete" button. Notice that it probably works the way you expect (it prints to stdout, so make sure you're running from a console window)
click on the "Open Window" button, then click on it again. You now have two windows. Fill in the second window, click on "Task Complete" and notice it works like you expect. Now fill in the first window and click on "Task Complete" and it still works just fine. In both cases it prints the correct values to the screen when you click the button.
click on the "Open Window" button, then click on it again. You now have two windows. This time, fill in the first window and click "Task Complete". Notice that it does not print the results to the screen. This is because the nested event loop of the first window can't complete until the nested event loop of the third window completes. Fill in the form of this second window and click on "Task Complete" and you'll see that the two nested event loops unwind, but not in the order that you clicked on "Task Complete" but rather in the order that the windows were created.
This is why you need to be careful when using wait_window - you should prevent this recursive nesting of event loops or your GUI may not behave the way you expect.
Yesterday I asked this question Creating elements by loop Tkinter to find out how to dynamically create some bullet points. Now I'm looking to add a clear button so when pressed, will reset the entire form. I have tried setting the list back to [] but it didn't work.
edit - So basically when I press reset I'd like it to look exactly like it did when the form was loaded.
The buttons are removed with the destroy method:
for button in self.button:
button.destroy()
import Tkinter as tk
class ButtonBlock(object):
def __init__(self, master):
self.master = master
self.button = []
self.button_val = tk.IntVar()
entry = tk.Entry()
entry.grid(row=0, column=0)
entry.bind('<Return>', self.onEnter)
entry.focus()
clear_button = tk.Button(master, text='Clear', command=self.onClear)
clear_button.grid(row=0, column=1)
def onClear(self):
for button in self.button:
button.destroy()
def onEnter(self, event):
entry = event.widget
num = int(entry.get())
self.onClear()
for i in range(1, num+1):
self.button.append(tk.Radiobutton(
self.master, text=str(i), variable=self.button_val, value=i,
command=self.onSelect))
self.button[-1].grid(sticky='WENS', row=i, column=0, padx=1, pady=1)
def onSelect(self):
print(self.button_val.get())
if __name__ == '__main__':
root = tk.Tk()
ButtonBlock(root)
root.mainloop()
Setting the list back (i.e. using self.button = []) just clears the data stored in the button variable. That action alone is not connected to the user interface (UI). You have to explicitly remove the widget objects which were created (by the onEnter method).
So the clearing feature you are looking for should be feasible by extending the answer from your previous question. Add an onClear method to the ButtonBlock class so that when your "Clear" control (i.e. using a button widget) is selected its callback function calls ButtonBlock.onClear(), similar to how your Entry widget invokes the onEnter method.
EDIT: See unutbu's answer to this question. When selected, the clear_button control calls ButtonBlock.onClear(). The for loop in onClear gets a reference to each button ojbect from the button list and calls the object's destroy method, which removes it from the UI.
I have a list of tkinter widgets that I want to change dynamically.
How to delete the widgets from the window?
You can call pack_forget to remove a widget (if you use pack to add it to the window).
Example:
from tkinter import *
root = Tk()
b = Button(root, text="Delete me", command=lambda: b.pack_forget())
b.pack()
root.mainloop()
If you use pack_forget, you can later show the widget again calling pack again. If you want to permanently delete it, call destroy on the widget (then you won't be able to re-add it).
If you use the grid method, you can use grid_forget or grid_remove to hide the widget.
One way you can do it, is to get the slaves list from the frame that needs to be cleared and destroy or "hide" them according to your needs. To get a clear frame you can do it like this:
from tkinter import *
root = Tk()
def clear():
list = root.grid_slaves()
for l in list:
l.destroy()
Label(root,text='Hello World!').grid(row=0)
Button(root,text='Clear',command=clear).grid(row=1)
root.mainloop()
You should call grid_slaves(), pack_slaves() or slaves() depending on the method you used to add the widget to the frame.
You simply use the destroy() method to delete the specified widgets like this:
lbl = tk.Label(....)
btn = tk.Button(....., command=lambda: lbl.destroy())
Using this you can completely destroy the specific widgets.
You say that you have a list of widgets to change dynamically. Do you want to reuse and reconfigure existing widgets, or create all new widgets and delete the old ones? It affects the answer.
If you want to reuse the existing widgets, just reconfigure them. Or, if you just want to hide some of them temporarily, use the corresponding "forget" method to hide them. If you mapped them with pack() calls, you would hide with pack_forget() (or just forget()) calls. Accordingly, grid_forget() to hide gridded widgets, and place_forget() for placed widgets.
If you do not intend to reuse the widgets, you can destroy them with a straight destroy() call, like widget.destroy(), to free up resources.
clear_btm=Button(master,text="Clear") #this button will delete the widgets
clear_btm["command"] = lambda one = button1, two = text1, three = entry1: clear(one,two,three) #pass the widgets
clear_btm.pack()
def clear(*widgets):
for widget in widgets:
widget.destroy() #finally we are deleting the widgets.
Today I learn some simple and good click event handling using tkinter gui library in python3, which I would like to share inside this thread.
from tkinter import *
cnt = 0
def MsgClick(event):
children = root.winfo_children()
for child in children:
# print("type of widget is : " + str(type(child)))
if str(type(child)) == "<class 'tkinter.Message'>":
# print("Here Message widget will destroy")
child.destroy()
return
def MsgMotion(event):
print("Mouse position: (%s %s)" % (event.x, event.y))
return
def ButtonClick(event):
global cnt, msg
cnt += 1
msg = Message(root, text="you just clicked the button..." + str(cnt) + "...time...")
msg.config(bg='lightgreen', font=('times', 24, 'italic'))
msg.bind("<Button-1>", MsgClick)
msg.bind("<Motion>", MsgMotion)
msg.pack()
#print(type(msg)) tkinter.Message
def ButtonDoubleClick(event):
import sys; sys.exit()
root = Tk()
root.title("My First GUI App in Python")
root.minsize(width=300, height=300)
root.maxsize(width=400, height=350)
button = Button(
root, text="Click Me!", width=40, height=3
)
button.pack()
button.bind("<Button-1>", ButtonClick)
button.bind("<Double-1>", ButtonDoubleClick)
root.mainloop()
Hope it will help someone...
You can use forget method on the widget
from tkinter import *
root = Tk()
b = Button(root, text="Delete me", command=b.forget)
b.pack()
b['command'] = b.forget
root.mainloop()
I found that when the widget is part of a function and the grid_remove is part of another function it does not remove the label. In this example...
def somefunction(self):
Label(self, text=" ").grid(row = 0, column = 0)
self.text_ent = Entry(self)
self.text_ent.grid(row = 1, column = 0)
def someotherfunction(self):
somefunction.text_ent.grid_remove()
...there is no valid way of removing the Label.
The only solution I could find is to give the label a name and make it global:
def somefunction(self):
global label
label = Label(self, text=" ")
label.grid(row = 0, column = 0)
self.text_ent = Entry(self)
self.text_ent.grid(row = 1, column = 0)
def someotherfunction(self):
global label
somefunction.text_ent.grid_remove()
label.grid_remove()
When I ran into this problem there was a class involved, one function being in the class and one not, so I'm not sure the global label lines are really needed in the above.