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()
Related
I have four radio buttons. Underneath these four button is an Entry widget. I am trying to make this Entry widget only become available to type into when the last radio button is selected. The gui is in a class, as you can see in the code below:
class Gui:
def __init__(self):
pass
def draw(self):
global root
if not root:
root = tk.Tk()
root.geometry('280x350')
self.type = tk.StringVar()
self.type_label = tk.Label(text="Game Mode")
self.name_entry = tk.Entry()
self.name_entry.configure(state="disabled")
self.name_entry.update()
self.type_entry_one = tk.Radiobutton(text="Garage", value="garage", variable=self.type, command=self.disable_entry(self.name_entry))
self.type_entry_two = tk.Radiobutton(text="Festival", value="festival", variable=self.type, command=self.disable_entry(self.name_entry))
self.type_entry_three = tk.Radiobutton(text="Studio", value="studio", variable=self.type, command=self.disable_entry(self.name_entry))
self.type_entry_four = tk.Radiobutton(text="Rockslam", value="rockslam", variable=self.type, command=self.enable_entry(self.name_entry))
self.type_label.pack()
self.type_entry_one.pack()
self.type_entry_two.pack()
self.type_entry_three.pack()
self.type_entry_four.pack()
self.name_entry.pack()
root.mainloop()
def enable_entry(self, entry):
entry.configure(state="normal")
entry.update()
def disable_entry(self, entry):
entry.configure(state="disabled")
entry.update()
if __name__ == '__main__':
root = None
gui = Gui()
gui.draw()
However, the the self.name_entry is always available to type into. What am I doing wrong. If you still don't understand what is happening then please run this code yourself and you will see.
Thank you very much for your time and I look forward to responses.
You have the right idea about using the RadioButton to enable/disable the entry widget. Mostly it is your class design that is flawed - it is halfway between OO code, and procedural code...
I fixed the class structure and made it a subclass of tk.Tk so it completely encapsulate your GUI. The name_entry is now enabled only when type_entry_four radio button is selected, and disabled otherwise. I've set that last button to be selected at launch, but you can easily change that; it results in the entry being enabled at launch.
Superfluous variable passing through methods was removed, as was the draw method and the calls to it; all widget creation is now conveniently found in GUI.__init__
import tkinter as tk
class Gui(tk.Tk):
def __init__(self):
super().__init__()
self.geometry('280x350')
self.select_type = tk.StringVar()
self.type_label = tk.Label(self, text='Game Mode')
self.name_entry = tk.Entry(self)
self.type_entry_one = tk.Radiobutton(self, text='Garage', value='garage', variable=self.select_type, command=self.disable_entry)
self.type_entry_two = tk.Radiobutton(self, text='Festival', value='festival', variable=self.select_type, command=self.disable_entry)
self.type_entry_three = tk.Radiobutton(self, text='Studio', value='studio', variable=self.select_type, command=self.disable_entry)
self.type_entry_four = tk.Radiobutton(self, text='Rockslam', value='rockslam', variable=self.select_type, command=self.enable_entry)
self.select_type.set('rockslam') # select the last radiobutton; also enables name_entry
self.type_label.pack()
self.type_entry_one.pack()
self.type_entry_two.pack()
self.type_entry_three.pack()
self.type_entry_four.pack()
self.name_entry.pack()
def enable_entry(self):
self.name_entry.configure(state='normal')
def disable_entry(self):
self.name_entry.configure(state='disabled')
if __name__ == '__main__':
Gui().mainloop()
The only problemS, I see, your facing here is because your not passing in the value "properly" into the function, when you use (..), your calling the function, so to get rid of that use lambda, like:
self.type_entry_one = tk.Radiobutton(text="Garage", value="garage", variable=self.type, command=lambda: self.disable_entry(self.name_entry))
self.type_entry_two = tk.Radiobutton(text="Festival", value="festival", variable=self.type, command=lambda:self.disable_entry(self.name_entry))
self.type_entry_three = tk.Radiobutton(text="Studio", value="studio", variable=self.type, command=lambda:self.disable_entry(self.name_entry))
self.type_entry_four = tk.Radiobutton(text="Rockslam", value="rockslam", variable=self.type, command=lambda:self.enable_entry(self.name_entry))
When using command=lambda:func(arg), this will get executed only when selecting a radiobutton. That is the point of using a radiobutton, right?
Also notice that when the initial code is run, the entire radiobuttons are selected, I think its probably because of tristate values, to get rid of that there are 2 ways I'm aware of:
Changing the declaration of self.type to:
self.type = tk.StringVar(value=' ')
Or, you could also go on adding an extra option to each radiobutton, tristatevalue=' ', like:
self.type_entry_one = tk.Radiobutton(text="Garage",..,tristatevalue=' ')
But make sure to do just one of the above solution. Take a read here about more on tristate values.
Also keep a note that your not passing in any master window to the widgets, its fine as long as your having just one window, when working with multiple windows, it may get confusing for where the widgets should appear.
Also side-note, if this is the complete code, then if nothing is being done on __init__(), its definition can be removed.
I have a treeview in tkinter using python 3.
The problem is that when I bind a right-click to get the row ID of the right-clicked row, I end up getting the actual row ID of the previous event. For example, I might right-click "Project 1", this will return me "" and then I right-click "Project 2" and it returns "Project 1" as rowID.
def initTreeView(self):
self.treeView = ttk.Treeview(self.treeSectionFrame)
self.treeView.heading('#0', text='Projects')
self.treeView.grid(row=0, column=0, sticky=("N", "S", "E", "W"))
self.treeView.bind('<3>', self.rightClickMenu)
def rightClickMenu(self, event):
def hello():
print("hello!")
# create a popup menu
print(event.x, event.y)
rowID = self.treeView.identify('item', event.x, event.y)
if rowID:
menu = Menu(self.root, tearoff=0)
menu.add_command(label="Undo", command=hello)
menu.add_command(label="Redo", command=hello)
menu.post(event.x_root, event.y_root)
self.treeView.selection_set(rowID)
self.treeView.focus_set()
self.treeView.focus(rowID)
print(rowID)
else:
pass
Thanks,
[EDIT]
I've found a dirty hack which consists of making the tags of each item the same as its id so you can then fetch the actual rowID. This can also be done using the value option.
self.treeView.insert("", "end", "id-1, tags="id-1", text="Project 1")
...
rowID = self.treeView.identify('item', event.x, event.y)
rowID = self.treeView.item(rowID)["tags"] # gives you actual ID
First of all, if you want to print the actual rowID, then just print it right away:
...
rowID = self.treeView.identify('item', event.x, event.y)
print(rowID)
...
... but of course, this is not what you're expecting from the code. To overcome this problem — let's invert logic a little bit:
def rightClickMenu(self, event):
def hello():
print("hello!")
# create a popup menu
print(event.x, event.y)
rowID = self.treeView.identify('item', event.x, event.y)
if rowID:
self.treeView.selection_set(rowID)
self.treeView.focus_set()
self.treeView.focus(rowID)
print(rowID)
menu = Menu(self.root, tearoff=0)
menu.add_command(label="Undo", command=hello)
menu.add_command(label="Redo", command=hello)
menu.post(event.x_root, event.y_root)
else:
pass
As you can see, now changing selections aren't blocked by the Menu widget.
The reason why it's works like that is because post method displays Menu right away and this event needs to be handled by tk somehow. Thus, we have the main problem: the positioning of that post.
Another approach example:
...
menu = Menu(self.root, tearoff=0)
menu.add_command(label="Undo", command=hello)
menu.add_command(label="Redo", command=hello)
self.treeView.selection_set(rowID)
self.treeView.focus_set()
self.treeView.focus(rowID)
print(rowID)
menu.post(event.x_root, event.y_root)
...
But in my opinion, I think that the most logically correct option here is to extract selection handling to another function, and use it as a postcommand argument of Menu. So that after that you aren't trying to sit on two chairs at the same time with one callback function.
In Python tkinter treeview I am trying to make a display that will show certain things based on the iid of the selected treeview item, it takes place on a selection event (mouse click) but I cannot get this working:
def tree_click_event (event):
iid = treedisplay.identify(event.x,event.y)
treedisplay = ttk.Treeview(root,selectmode='browse')
treedisplay.bind('<<TreeviewSelect>>', tree_click_event)
treedisplay.pack(side='top', fill='both', expand=1)
error:
TypeError: tree_click_event() missing 1 required positional argument: 'y'
this is condensed down to just creating the tree, packing it in a tkinter window, looking for people familiar with this module to know exactly what I've done wrong
Thank you for your example #BryanOakley, it works to get the text of the item. Is there no way to get the below code working though?
import tkinter as tk
from tkinter import ttk
class App:
def __init__(self):
self.root = tk.Tk()
self.tree = ttk.Treeview()
self.tree.pack(side="top", fill="both")
self.tree.bind("<<TreeviewSelect>>", self.tree_click_event)
for i in range(10):
self.tree.insert("", "end", text="Item %s" % i)
self.root.mainloop()
def tree_click_event(self, event):
iid = self.tree.identify(event.x,event.y)
print (iid)
if __name__ == "__main__":
app = App()
identify requires three arguments and you are only passing it two. The first argument represents a component that you want to identify, and needs to be one of the following: region, item, column, row, or element.
For example:
iid = treedisplay.identify("item", event.x,event.y)
Note: while the above is syntactically correct, it won't quite do what you think it does. In the case of the <<TreeviewSelect>> event, you won't get an x and y coordinate. That is because the event can be fired by both keyboard and mouse events. The identify method should be used for explicit bindings to mouse events, and is mostly only used for low level bindings.
If you want the selected item, use the selection method which will return a list of item ids:
for item in treedisplay.selection():
item_text = self.tree.item(item,"text")
I have a Tkinter GUI having 2 entry fields, 2 buttons ( initialization of these not shown in code). There is one more button (initialized in code) which performs the main task of performing change detection on two images. Also there is a progress bar.
Now, when the task of change detection has been completed, I want to display the 4 images(pre, post, aligned, chng) returned by wave.changedetection() in a separate Tkinter window. I want the new window to come only after changedetection() has completed.(wave.py is my own file, not some module)
Unfortunately, if I try to add code to make new window, Tk.Toplevel() ,after the wave.changedetection() call, nothing happens and the main GUI window becomes unresponsive and has to be killed.
There is no way to know when the new created thread (start_thread)completes it's work, so that I can do Tk.Toplevel() there.
How can I do what I require?
class GUI(Tkinter.Tk):
def __init__(self, parent)
Tkinter.Tk.__init__(self, parent)
self.parent = parent
self.initialize()
def initialize(self):
self.button = Tkinter.Button(text = "Start")
self.button.bind('<Button-1>', self.OnButtonClick)
self.button.pack()
self.int = Tkinter.IntVar()
self.pgbar = Tkinter.ProgressBar(variable = self.int, mode = determinate)
def OnButtonClick(self,event):
#this func has been made since I have more buttons.It may seem redundant here
self.button['command'] = self.start_thread()
self.update_idletasks()
def start_thread(self):
self.int_var.set(1)
q = queue.Queue()
self.secondary_thread = threading.Thread(target = self.change)
self.secondary_thread.start()
self.after(50, self.check_queue, q)
def check_queue(self, q):
while True:
try:
x = wave.q.get_nowait()
except queue.Empty :
self.after(50,self.check_queue,q)
break
else:
self.int_var.set(x)
if x == 6:
self.button3['state'] = 'normal'
break
def change(self):
'''The 6 functions of wave.changedetection() change the value of self.int
due to which progress bar progresses.'''
pre, post, aligned, chng = wave.changedetection(self.entry_1.get(),
self.entry_2.get())
if __name__ == '__main__':
gui = GUI(None)
gui.mainloop()
code to update progress bar taken from here (2nd answer,Honest Abe's answer)
You have to be able to differentiate name spaces, i.e. this is in the main window and this is in the Toplevel. I would suggest that you get the Toplevels working first and then decide if you want to add threading or not. The code below is a simple example of creating Toplevels and shows how to place widgets in a specific name space (window in this case). You may or may not want a separate "create a Toplevel" class if there are functions you want to associate with each Toplevel's namespace. Also there are examples on the web on using Tkinter's "after" to update a progressbar. That is a different question so start another thread if you have questions about the progressbar.
try:
import Tkinter as tk ## Python 2.x
except ImportError:
import tkinter as tk ## Python 3.x
from functools import partial
class OpenToplevels():
""" open and close additional Toplevels with a button
"""
def __init__(self):
self.root = tk.Tk()
self.button_ctr=0
## in the "root" namespace *********************
but=tk.Button(self.root, text="Open a Toplevel",
command=self.open_another)
but.grid(row=0, column=0)
tk.Button(self.root, text="Exit Tkinter", bg="red",
command=self.root.quit).grid(row=1, column=0, sticky="we")
self.root.mainloop()
def close_it(self, id):
## destroy the window in this id's namespace ***********
id.destroy()
## id.withdraw()
## id.iconify()
def open_another(self):
self.button_ctr += 1
id = tk.Toplevel(self.root)
id.title("Toplevel #%d" % (self.button_ctr))
## in the "id for this Toplevel" namespace ***********
tk.Button(id, text="Close Toplevel #%d" % (self.button_ctr),
command=partial(self.close_it, id),
bg="orange", width=20).grid(row=1, column=0)
Ot=OpenToplevels()
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.