I'm trying to build a tool for data analysis. I expect that this kind of application gets big, so I'm following the MVC pattern to organize it as best as possible.
My results are saved in .csv files, so the goal should be to "import" them into the GUI. I want to open these files using "CTRL + O" as keybinding and of course using the corresponding option in the menubar.
Now to the actual problem:
The function which is called when hitting "CTRL + O" is in the Controller class and works as expected, I can open a bunch of files and the list saves each name. But when using the menubar I'm stucked how to implement the "command=" option.
This is my code:
import tkinter as tk
from tkinter.filedialog import askopenfilename
from tkinter.constants import ANCHOR, TRUE
from tkinter import Label, filedialog
from tkinter import ttk
class Model():
# more to come
pass
class View:
def __init__(self, view):
self.view = view
self.view.title("analyzer")
self.view.geometry("640x480")
self.view.resizable(False, False)
# menubar
self.menubar = tk.Menu(self.view)
self.view.config(menu=self.menubar)
self.filemenu = tk.Menu(self.menubar, tearoff=False)
self.menubar.add_cascade(label="File", menu=self.filemenu)
self.filemenu.add_command(label="Open", accelerator="Ctrl+O", command=Controller.get_open_filename())
self.filemenu.add_separator()
self.filemenu.add_command(label="Remove", accelerator="Ctrl+R")
self.filemenu.add_command(label="Information", accelerator="Ctrl+I")
self.filemenu.add_separator()
self.filemenu.add_command(label="Exit", accelerator="Ctrl+E", command=self.view.quit)
class Controller:
def __init__(self):
self.root = tk.Tk()
self.view = View(self.root)
# keybindings / shortcuts
self.root.bind_all("<Control-e>", lambda event: self.root.quit())
self.root.bind_all("<Control-o>", lambda event: self.get_open_filename())
self.list_of_files = []
def run(self):
self.root.mainloop()
def get_open_filename(self):
self.filename = askopenfilename(title="Select data file", filetypes=(("csv files", "*.csv"), ("all files", "*.*")))
self.list_of_files.append(self.filename)
print(self.list_of_files)
if __name__ == "__main__":
c = Controller()
c.run()
I would really appreciate If some could give me a hint, want I'm doing wrong.
Thanks!
You are trying to call a Controller function from a View Object, but View doesn't know that Controller has that function. It doesn't know that Controller exist.
There might be a better way than this, but you can pass a function as a parameter to View's constructor.
By passing the get_open_filename() function as a parameter to View's constructor you can use that as the command.
NOTE: I called the parameter func so you can see what I'm doing. I'd recommend giving it a better name though.
import tkinter as tk
from tkinter.filedialog import askopenfilename
from tkinter.constants import ANCHOR, TRUE
from tkinter import Label, filedialog
from tkinter import ttk
class Model():
# more to come
pass
class View:
def __init__(self, view, func):
self.view = view
self.view.title("analyzer")
self.view.geometry("640x480")
self.view.resizable(False, False)
# menubar
self.menubar = tk.Menu(self.view)
self.view.config(menu=self.menubar)
self.filemenu = tk.Menu(self.menubar, tearoff=False)
self.menubar.add_cascade(label="File", menu=self.filemenu)
self.filemenu.add_command(label="Open", accelerator="Ctrl+O", command=func)
self.filemenu.add_separator()
self.filemenu.add_command(label="Remove", accelerator="Ctrl+R")
self.filemenu.add_command(label="Information", accelerator="Ctrl+I")
self.filemenu.add_separator()
self.filemenu.add_command(label="Exit", accelerator="Ctrl+E", command=self.view.quit)
class Controller:
def __init__(self):
self.root = tk.Tk()
self.view = View(self.root, lambda: self.get_open_filename())
# keybindings / shortcuts
self.root.bind_all("<Control-e>", lambda event: self.root.quit())
self.root.bind_all("<Control-o>", lambda event: self.get_open_filename())
self.list_of_files = []
def run(self):
self.root.mainloop()
def get_open_filename(self):
self.filename = askopenfilename(title="Select data file", filetypes=(("csv files", "*.csv"), ("all files", "*.*")))
self.list_of_files.append(self.filename)
print(self.list_of_files)
if __name__ == "__main__":
c = Controller()
c.run()
Related
I would like to create a pyton script that takes an excel file (provided by the user), run a script, and save this excel file (with a file name provided by the user).
I would like to implement some buttons using Tkinter to enable non-programmers to easily use my algorithm.
I tried to create a ''load data'' and ''display data'' button (I took a code online) that both work.
However I didn't manage to run the script on the database imported (as I don't know how to access it)
I didnt manage to create a button that enables the user to choose the name of the file they want to save it to.
import tkinter as tk
from tkinter import ttk
from tkinter.filedialog import askopenfilename
from tkinter.filedialog import asksaveasfilename
from tkinter.messagebox import showerror
import pandas as pd
import csv
# --- classes ---
class MyWindow:
def __init__(self, parent):
self.parent = parent
self.filename = None
self.df = None
self.text = tk.Text(self.parent)
self.text.pack()
self.button = tk.Button(self.parent, text='LOAD DATA', command=self.load)
self.button.pack()
self.button = tk.Button(self.parent, text='DISPLAY DATA', command=self.display)
self.button.pack()
self.button = tk.Button(self.parent, text='DISPLAY DATA', command=self.display)
self.button.pack()
def load(self):
name = askopenfilename(filetypes=[('CSV', '*.csv',), ('Excel', ('*.xls', '*.xslm', '*.xlsx'))])
if name:
if name.endswith('.csv'):
self.df = pd.read_csv(name)
else:
self.df = pd.read_excel(name)
self.filename = name
display directly
self.text.insert('end', str(self.df.head()) + '\n')
def display(self):
# ask for file if not loaded yet
if self.df is None:
self.load_file()
# display if loaded
if self.df is not None:
self.text.insert('end', self.filename + '\n')
self.text.insert('end', str(self.df.head()) + '\n')
#def script_python(self):
# self.df=self.df.iloc[:, :-1]
#this is not my original script
#def file_save(self):
# savefile = asksaveasfilename(filetypes=(("Excel files", "*.xlsx"),
# ("All files", "*.*") ))
# --- main ---
if __name__ == '__main__':
root = tk.Tk()
top = MyWindow(root)
root.mainloop()
My python script that do analysis on the excel file works, so I just put a very simple python script to make your life easier.
Thank you
I'm new at Tkinter and not used to classes and dictionaries. thanks for your help
I've modified your code, adding a button to run your script and a button to save the data (I've tried to keep the rest as close to your original code as possible):
import tkinter as tk
from tkinter import ttk
from tkinter.filedialog import askopenfilename
from tkinter.filedialog import asksaveasfilename
from tkinter.messagebox import showerror
import pandas as pd
import csv
# --- classes ---
class MyWindow:
def __init__(self, parent):
self.parent = parent
self.filename = None
self.df = None
self.text = tk.Text(self.parent)
self.text.pack()
# load data button
self.load_button = tk.Button(self.parent, text='LOAD DATA', command=self.load)
self.load_button.pack()
# display data button
self.display_button = tk.Button(self.parent, text='DISPLAY DATA', command=self.display)
self.display_button.pack()
# run script button
self.script_button = tk.Button(self.parent, text='RUN SCRIPT', command=self.script_python)
self.script_button.pack()
# save data button
self.save_button = tk.Button(self.parent, text='SAVE DATA', command=self.file_save)
self.save_button.pack()
def load(self):
name = askopenfilename(filetypes=[('CSV', '*.csv',), ('Excel', ('*.xls', '*.xslm', '*.xlsx'))])
if name:
if name.endswith('.csv'):
self.df = pd.read_csv(name)
else:
self.df = pd.read_excel(name)
self.filename = name
# display directly
self.text.insert('end', str(self.df.head()) + '\n')
def display(self):
# ask for file if not loaded yet
if self.df is None:
self.load_file()
# display if loaded
if self.df is not None:
self.text.insert('end', self.filename + '\n')
self.text.insert('end', str(self.df.head()) + '\n')
def script_python(self):
# replace this with the real thing
self.df = self.df.iloc[:, :-1]
def file_save(self):
fname = asksaveasfilename(filetypes=(("Excel files", "*.xlsx"),
("All files", "*.*")))
# note: this will fail unless user ends the fname with ".xlsx"
self.df.to_excel(fname)
# --- main ---
if __name__ == '__main__':
root = tk.Tk()
top = MyWindow(root)
root.mainloop()
I've write a simple app, see below, to redirect help function data of tkinter library on ScrolledText , something like
print (help(tkinter.Label)) on cli.
I' ve use a class written by #Bryan Oakley.
After launch the scipt press 'Load' button and after click on a voice on the left tree.
This cause the writing of help function data of the selected item on ScrolledText using sys.stdout by #Bryan Oakley class
sys.stdout.write(help(s))
All works but I can't refresh data on my ScrolledText even with
self.widget.delete('1.0', tk.END)
than using
sys.stdout.flush()
Basically I'm not able, when you click another item, to delete all data from ScrolledText and write new sys.stdout
Wath is wrong in my approach?
import sys
import tkinter as tk
from tkinter import ttk
from tkinter.scrolledtext import ScrolledText
class TextRedirector(object):
"""Written Bryan Oakley
https://stackoverflow.com/users/7432/bryan-oakley
"""
def __init__(self, widget, tag="stdout"):
self.widget = widget
self.tag = tag
def write(self, str):
#this generate an error
#self.widget.delete('1.0', tk.END)
self.widget.configure(state="normal")
#it works but generete an error
self.widget.insert("end", str, self.tag)
self.widget.configure(state="disabled")
class App(tk.Frame):
def __init__(self,):
super().__init__()
self.master.title("Hello Tkinter ")
self.selected = tk.StringVar()
self.init_ui()
def init_ui(self):
f = tk.Frame()
f1 = tk.Frame(f)
tk.Label(f, textvariable = self.selected).pack()
cols = (["#0",'','w',False,200,200],
["#1",'','w',True,0,0],)
self.Voices = self.get_tree(f1, cols, show="tree")
self.Voices.show="tree"
self.Voices.pack(fill=tk.Y, padx=2, pady=2)
self.Voices.bind("<<TreeviewSelect>>", self.on_selected)
f1.pack(side=tk.LEFT, fill=tk.BOTH, expand=0)
f2 = tk.Frame(f)
self.text = ScrolledText(f2)
self.text.pack(side="top", fill="both", expand=True)
self.text.tag_configure("stderr", foreground="#b22222")
sys.stdout = TextRedirector(self.text, "stdout")
sys.stderr = TextRedirector(self.text, "stderr")
f2.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
w = tk.Frame()
tk.Button(w, text="Load", command=self.set_values).pack()
tk.Button(w, text="Close", command=self.on_close).pack()
w.pack(side=tk.RIGHT, fill=tk.Y, expand=0)
f.pack(side=tk.LEFT, fill=tk.BOTH, expand=0)
def set_values(self,):
rs = []
for i in dir(tk):
rs.append(i)
for i in rs:
tree = self.Voices.insert("", tk.END, text=i, values=(i,'tree'))
def on_selected(self, evt=None):
selected_item = self.Voices.focus()
d = self.Voices.item(selected_item)
if d['values']:
item = (d['values'][0])
self.selected.set(item)
s = "tkinter.{}".format(item)
#this generate an error
#sys.stdout.flush()
sys.stdout.write(help(s))
def get_tree(self,container, cols, size=None, show=None):
headers = []
for col in cols:
headers.append(col[1])
del headers[0]
if show is not None:
w = ttk.Treeview(container,show=show)
else:
w = ttk.Treeview(container,)
w['columns']=headers
for col in cols:
w.heading(col[0], text=col[1], anchor=col[2],)
w.column(col[0], anchor=col[2], stretch=col[3],minwidth=col[4], width=col[5])
sb = ttk.Scrollbar(container)
sb.configure(command=w.yview)
w.configure(yscrollcommand=sb.set)
w.pack(side=tk.LEFT, fill=tk.BOTH, expand =1)
sb.pack(fill=tk.Y, expand=1)
return w
def on_close(self):
self.master.destroy()
if __name__ == '__main__':
app = App()
app.mainloop()
Adding a delete inside the write statement is the wrong solution since you do not always have control over what gets sent to the write statement. For example, help may actually call write more than once each time you call it. If it does, you will only ever see the results of the most recent call to write.
The correct solution is to delete the contents before calling help. For that, you need to enable the widget before deleting the contents since the redirector class as written leaves the widget disabled.
For example, you could add the method clear to the redirector class like so:
class TextRedirector(object):
...
def clear(self):
self.widget.configure(state="normal")
self.widget.delete("1.0", "end")
self.widget.configure(state="disabled")
You can then call it immediately before calling help:
def on_selected(self, evt=None):
...
if d['values']:
...
sys.stdout.clear()
help(s)
Note: you do not need to do sys.stdout.write(help(s)) because help(s) merely returns an empty string. help(s) is already sending its information to stdout.
Hello I'm trying to make a notepad written in python using tkinter. I'm having trouble making the edit menu work. I'm currently trying to implement copy, cut, and paste but I'm having issues. When I try to press the cut button I get a message like this "TypeError: cut() missing 2 required positional arguments: 'self' and 'event'" I'm honestly new to the whole class thing in Python so this is my first attempt at using that. I've shortened my code for convenience. Any help would be appreciated!
class Notepad:
#Functions
def __init__(self, master, **kw):
Text.__init__(self, master, **kw)
self.bind('<Control-c>', self.copy)
self.bind('<Control-x>', self.cut)
self.bind('<Control-v>', self.paste)
def copy(self, event=None):
self.clipboard_clear()
text = self.get("sel.first", "sel.last")
self.clipboard_append(text)
def cut(self, event):
self.copy()
self.delete("sel.first", "sel.last")
def paste(self, event):
text = self.selection_get(selection='CLIPBOARD')
self.insert('insert', text)
root = Tk()
menu = Menu(root)
root.config(menu=menu)
root.title('Written in Python')
root.minsize(width=100, height=100)
root.geometry('800x500+350+150') #Height, Width, X, Y coordinates of the program
#NotePad
textArea = ScrolledText.ScrolledText(root, width=1000, height=100)
#Height and width of notepad
textArea.pack()
root = Tk()
menu = Menu(root)
root.config(menu=menu)
editMenu = Menu(menu)
menu.add_cascade(label="Edit",menu=editMenu)
editMenu.add_separator()
editMenu.add_command(label="Cut", command=cut)
editMenu.add_command(label="Copy", command=copy)
editMenu.add_command(label="Paste", command=paste)
root.mainloop()
The function cut is defined with two arguments: self and event, but the menu command invokes cut() without argument, hence the error message.
In addition, your class structure looks strange to me, especially putting the last block of code directly in the class, not inside a class methods. I suggest you to create instead a Notepad class inheriting from Text (or ScrolledText), with your custom methods and bindings and put the root = Tk() ... outside the class, like that:
import tkinter as tk
from tkinter.scrolledtext import ScrolledText
class Notepad(ScrolledText):
def __init__(self, master, **kw):
ScrolledText.__init__(self, master, **kw)
self.bind('<Control-c>', self.copy)
self.bind('<Control-x>', self.cut)
self.bind('<Control-v>', self.paste)
def copy(self, event=None):
self.clipboard_clear()
text = self.get("sel.first", "sel.last")
self.clipboard_append(text)
def cut(self, event=None):
self.copy()
self.delete("sel.first", "sel.last")
def paste(self, event=None):
text = self.selection_get(selection='CLIPBOARD')
self.insert('insert', text)
if __name__ == '__main__':
root = tk.Tk()
menu = tk.Menu(root)
root.config(menu=menu)
root.title('Written in Python')
root.minsize(width=100, height=100)
root.geometry('800x500+350+150') #Height, Width, X, Y coordinates of the program
#NotePad
notepad = Notepad(root, width=1000, height=100)
#Height and width of notepad
notepad.pack()
editMenu = tk.Menu(menu)
menu.add_cascade(label="Edit", menu=editMenu)
editMenu.add_separator()
editMenu.add_command(label="Cut", command=notepad.cut)
editMenu.add_command(label="Copy", command=notepad.copy)
editMenu.add_command(label="Paste", command=notepad.paste)
root.mainloop()
In the above code, the functions cut, copy and paste are methods of the Notepad class, you can invoke them with notepad.cut(event). Since, you don't use the event argument in the methods, it is just here for the binding, I suggest you to do def cut(self, event=None) so that event becomes optional, with default value None. This way you can directly use notepad.copy as a command in the editMenu.
I have the app with Tkinter, for example:
from Tkinter import *
from ttk import *
class MyMenu(Menu):
....
class MyNotebook(Notebook):
....
tk=Tk()
f1=Frame(master=tk)
f2=Frame(master=tk)
menu=MyMenu(master=f1)
notebook=MyNotebook(master=f2)
I want to add command in menu, which will add new tab in notebook. How can i do this?
P.S. f1 != f2 It's important!
P.P.S. functions, that used as commands in menu may be in another file
One of the frames is not necessary for the menu, since it should be configured with the window and not placed with the geometry manager. Something similar to this can do the job:
# ...
def add_tab():
text = "Tab {}".format(len(notebook.tabs()))
frame = Frame(notebook, width=100, height=100)
notebook.add(frame, text=text)
menu=MyMenu()
menu.add_command(label="Add tab", command=add_tab)
tk.config(menu=menu)
However, I recommend you to: a) Define a class instead of using global variables; and b) Don't use import * since Tkinter an ttk uses the same name for different classes. It will be not only more organized, but also easier to read:
import Tkinter as tk
import ttk
class MyMenu(tk.Menu):
pass
class MyNotebook(ttk.Notebook):
pass
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.frame = ttk.Frame(self)
self.notebook = MyNotebook(self.frame)
self.frame.pack()
self.notebook.pack()
self.add_tab()
menu = MyMenu()
menu.add_command(label="Add tab", command=self.add_tab)
self.config(menu=menu)
def add_tab(self):
text = "Tab {}".format(len(self.notebook.tabs()))
frame = ttk.Frame(self.notebook, width=100, height=100)
self.notebook.add(frame, text=text)
app = App()
app.mainloop()
The solution is simple: for an instance of class A to interact with an instance of class B, class A needs a reference to the instance of class B. That means you need to either pass it in to the constructor, or set if after creation. For example:
class MyMenu(Menu):
def __init__(self, notebook):
...
self.add_command("New page", command=notebook.add(...))
...
notebook = Notebook(...)
menu = MyMenu(notebook)
Another way -- which I think is better -- is to pass what is sometimes called a controller -- a class that knows about all the widgets, or provides an interface to the widgets. For example, you could implement your app as a class and use an instance of that as your controller:
class MyMenu(Menu)
def __init__(self, app=None):
...
self.add_command(..., command=app.add_tab)
class App(Tk):
def __init__(self):
...
self.menu = MyMenu(self, controller=self)
self.notebook = Notebook(...)
...
def add_tab(self, label):
frame = Frame(self)
self.notebook.add(frame, text=label)
app = App()
app.mainloop()
I have a python-tkinter gui app that I've been trying to find some way to add in some functionality. I was hoping there would be a way to right-click on an item in the app's listbox area and bring up a context menu. Is tkinter able to accomplish this? Would I be better off looking into gtk or some other gui-toolkit?
You would create a Menu instance and write a function that calls
its post() or tk_popup() method.
The tkinter documentation doesn't currently have any information about tk_popup().
Read the Tk documentation for a description, or the source:
library/menu.tcl in the Tcl/Tk source:
::tk_popup --
This procedure pops up a menu and sets things up for traversing
the menu and its submenus.
Arguments:
menu - Name of the menu to be popped up.
x, y - Root coordinates at which to pop up the menu.
entry - Index of a menu entry to center over (x,y).
If omitted or specified as {}, then menu's
upper-left corner goes at (x,y).
tkinter/__init__.py in the Python source:
def tk_popup(self, x, y, entry=""):
"""Post the menu at position X,Y with entry ENTRY."""
self.tk.call('tk_popup', self._w, x, y, entry)
You associate your context menu invoking function with right-click via:
the_widget_clicked_on.bind("<Button-3>", your_function).
However, the number associated with right-click is not the same on every platform.
library/tk.tcl in the Tcl/Tk source:
On Darwin/Aqua, buttons from left to right are 1,3,2.
On Darwin/X11 with recent XQuartz as the X server, they are 1,2,3;
other X servers may differ.
Here is an example I wrote that adds a context menu to a Listbox:
import tkinter # Tkinter -> tkinter in Python 3
class FancyListbox(tkinter.Listbox):
def __init__(self, parent, *args, **kwargs):
tkinter.Listbox.__init__(self, parent, *args, **kwargs)
self.popup_menu = tkinter.Menu(self, tearoff=0)
self.popup_menu.add_command(label="Delete",
command=self.delete_selected)
self.popup_menu.add_command(label="Select All",
command=self.select_all)
self.bind("<Button-3>", self.popup) # Button-2 on Aqua
def popup(self, event):
try:
self.popup_menu.tk_popup(event.x_root, event.y_root, 0)
finally:
self.popup_menu.grab_release()
def delete_selected(self):
for i in self.curselection()[::-1]:
self.delete(i)
def select_all(self):
self.selection_set(0, 'end')
root = tkinter.Tk()
flb = FancyListbox(root, selectmode='multiple')
for n in range(10):
flb.insert('end', n)
flb.pack()
root.mainloop()
The use of grab_release() was observed in an example on effbot.
Its effect might not be the same on all systems.
I made some changes to the conext menu code above in order to adjust my demand and I think it would be useful to share:
Version 1:
import tkinter as tk
from tkinter import ttk
class Main(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
master.geometry('500x350')
self.master = master
self.tree = ttk.Treeview(self.master, height=15)
self.tree.pack(fill='x')
self.btn = tk.Button(master, text='click', command=self.clickbtn)
self.btn.pack()
self.aMenu = tk.Menu(master, tearoff=0)
self.aMenu.add_command(label='Delete', command=self.delete)
self.aMenu.add_command(label='Say Hello', command=self.hello)
self.num = 0
# attach popup to treeview widget
self.tree.bind("<Button-3>", self.popup)
def clickbtn(self):
text = 'Hello ' + str(self.num)
self.tree.insert('', 'end', text=text)
self.num += 1
def delete(self):
print(self.tree.focus())
if self.iid:
self.tree.delete(self.iid)
def hello(self):
print ('hello!')
def popup(self, event):
self.iid = self.tree.identify_row(event.y)
if self.iid:
# mouse pointer over item
self.tree.selection_set(self.iid)
self.aMenu.post(event.x_root, event.y_root)
else:
pass
root = tk.Tk()
app=Main(root)
root.mainloop()
Version 2:
import tkinter as tk
from tkinter import ttk
class Main(tk.Frame):
def __init__(self, master):
master.geometry('500x350')
self.master = master
tk.Frame.__init__(self, master)
self.tree = ttk.Treeview(self.master, height=15)
self.tree.pack(fill='x')
self.btn = tk.Button(master, text='click', command=self.clickbtn)
self.btn.pack()
self.rclick = RightClick(self.master)
self.num = 0
# attach popup to treeview widget
self.tree.bind('<Button-3>', self.rclick.popup)
def clickbtn(self):
text = 'Hello ' + str(self.num)
self.tree.insert('', 'end', text=text)
self.num += 1
class RightClick:
def __init__(self, master):
# create a popup menu
self.aMenu = tk.Menu(master, tearoff=0)
self.aMenu.add_command(label='Delete', command=self.delete)
self.aMenu.add_command(label='Say Hello', command=self.hello)
self.tree_item = ''
def delete(self):
if self.tree_item:
app.tree.delete(self.tree_item)
def hello(self):
print ('hello!')
def popup(self, event):
self.aMenu.post(event.x_root, event.y_root)
self.tree_item = app.tree.focus()
root = tk.Tk()
app=Main(root)
root.mainloop()
from tkinter import *
root=Tk()
root.geometry("500x400+200+100")
class Menu_Entry(Entry):
def __init__(self,perant,*args,**kwargs):
Entry.__init__(self,perant,*args,**kwargs)
self.popup_menu=Menu(self,tearoff=0,background='#1c1b1a',fg='white',
activebackground='#534c5c',
activeforeground='Yellow')
self.popup_menu.add_command(label="Cut ",command=self.Cut,
accelerator='Ctrl+V')
self.popup_menu.add_command(label="Copy ",command=self.Copy,compound=LEFT,
accelerator='Ctrl+C')
self.popup_menu.add_command(label="Paste ",command=self.Paste,accelerator='Ctrl+V')
self.popup_menu.add_separator()
self.popup_menu.add_command(label="Select all",command=self.select_all,accelerator="Ctrl+A")
self.popup_menu.add_command(label="Delete",command=self.delete_only,accelerator=" Delete")
self.popup_menu.add_command(label="Delete all",command=self.delete_selected,accelerator="Ctrl+D")
self.bind('<Button-3>',self.popup)
self.bind("<Control-d>",self.delete_selected_with_e1)
self.bind('<App>',self.popup)
self.context_menu = Menu(self, tearoff=0)
self.context_menu.add_command(label="Cut")
self.context_menu.add_command(label="Copy")
self.context_menu.add_command(label="Paste")
def popup(self, event):
try:
self.popup_menu.tk_popup(event.x_root, event.y_root, 0)
finally:
self.popup_menu.grab_release()
def Copy(self):
self.event_generate('<<Copy>>')
def Paste(self):
self.event_generate('<<Paste>>')
def Cut(self):
self.event_generate('<<Cut>>')
def delete_selected_with_e1(self,event):
self.select_range(0, END)
self.focus()
self.event_generate("<Delete>")
def delete_selected(self):
self.select_range(0, END)
self.focus()
self.event_generate("<Delete>")
def delete_only(self):
self.event_generate("<BackSpace>")
def select_all(self):
self.select_range(0, END)
self.focus()
ent=Menu_Entry(root)
ent.pack()
root.mainloop()
Important Caveat:
(Assuming the event argument that contains the coordinates is called "event"): Nothing will happen or be visible when you call tk_popup(...) unless you use "event.x_root" and "event.y_root" as arguments. If you do the obvious of using "event.x" and "event.y", it won't work, even though the names of the coordinates are "x" and "y" and there is no mention of "x_root" and "y_root" anywhere within it.
As for the grab_release(..), it's not necessary, anywhere. "tearoff=0" also isn't necessary, setting it to 1 (which is default), simply adds a dotted line entry to the context menu. If you click on it, it detaches the context menu and makes it its own top-level window with window decorators. tearoff=0 will hide this entry. Moreover, it doesn't matter if you set the menu's master to any specific widget or root, or anything at all.