How to copy text from popup menu in Tkinter/Python using 1 function for multiple widgets?

977 Views Asked by At

I am trying to create a popup menu that opens only when right-clicked inside of certain widgets (Text and Entry, in this case) but nowhere else inside the root window. When a user right-clicks inside one of the widgets and selects "copy", the text selection inside that widget should be copied to the clipboard.

As is, the code below only works when explicitly referring to a certain widget but I want to generalize the copyToClipboard function to copy the text selection from the widget that the user right-clicked inside.

Instead, running the commented out lines from the code below gives the following error:

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Users\...\...\lib\tkinter\__init__.py", line 1702, in __call__
    return self.func(*args)
TypeError: <lambda>() missing 1 required positional argument: 'e'

How do I access the appropriate (right-clicked) widget within the copyToClipboard function?

def copyToClipboard():
#def copyToClipboard(event):
    #value = event.widget.get(SEL_FIRST,SEL_LAST)
    value = inputText.get(SEL_FIRST,SEL_LAST)
    pyperclip.copy(value)
    print(value)

def showMenu(event):
    popup.post(event.x_root, event.y_root)

inputEntry = ttk.Entry(root)
inputText = Text(root)
popup = Menu(root)
popup.add_command(label="Copy", command=copyToClipboard)
#popup.add_command(label="Copy", command=lambda e: copyToClipboard(e))
inputText.bind("<Button-3>", showMenu)
inputEntry.bind("<Button-3>", showMenu)

inputText.pack()
inputEntry.pack()
mainloop()
1

There are 1 best solutions below

0
On BEST ANSWER

I've added a solution below. Storing event.widget as a global variable seemed to help as per acw's suggestion. I got rid of pyperclip because it kept giving me chinese chars and other random chars when mixing clicking-to-copy with Ctrl-V.

EDIT: It is worth noting that the Entry widget doesn't seem to handle line breaks well when they are pasted with Ctrl-V into the entry widget. Unfortunately, I haven't found an effective way to override the hotkey's default paste command to remove the line breaks prior to pasting.

from tkinter import *
from tkinter import ttk

root = Tk()

def copyToClipboard():
    val = clickedWidget.selection_get()
    root.clipboard_clear()
    root.clipboard_append(val)

def showMenu(event):
    global clickedWidget
    clickedWidget = event.widget
    popup.post(event.x_root, event.y_root)

inputEntry = ttk.Entry(root)
inputText = Text(root)
popup = Menu(root)
popup.add_command(label="Copy", command=copyToClipboard)

inputText.bind("<Button-3>", showMenu)
inputEntry.bind("<Button-3>", showMenu)

inputText.pack()
inputEntry.pack()
mainloop()