1

I'm trying to make all tkinter Entry objects map Control-C to a copy event (and similar defaults for other commonly used shortcuts). This works for most Entry objects, but fails in a strange way for a file dialog. A minimal example is below. I'm using Python 3.8.6 with Tkinter version 8.6.

import tkinter
from tkinter import filedialog
from tkinter import ttk

def ctrl_c_callback(event):
    # I would like to do something like:
    #   event.widget.event_generate("<<Copy>>")
    # but this results in the error
    #   AttributeError: 'str' object has no attribute 'event_generate'
    # due to the fact 'event.widget' is a 'str' rather than a widget,
    # as will now be demonstrated.
    print(event.widget.__class__)

root = tkinter.Tk()
root.bind_class("TEntry", "<Control-c>", ctrl_c_callback)
save_button = ttk.Button(root, text="Save", command=filedialog.asksaveasfile)
save_button.grid()
root.deiconify()
root.mainloop()

Running this and clicking "Save" produces a dialog box, as expected. Now I select some text in the "File name" field near the bottom...

enter image description here

...and hit Control-C. I get:

<class 'str'>

I find this extremely surprising. Shouldn't event.widget be a widget? Specifically, shouldn't it be an Entry? Clearly, the event was triggered by an Entry, because otherwise the Control-C binding should not have done anything.

If this is relevant, the value of the string is

.__tk_filedialog.contents.f2.ent

which seems like it might be the name of a widget in Tcl. (I do not know much about Tk in general.) As a last-ditch attempt, I tried using the root.nametowidget method on this to see if I could get a widget, but this resulted in a KeyError.

Note that, for other Entry objects that I have manually created, I have no trouble getting the Control-C callback to copy the text as desired.

sasquires
  • 356
  • 3
  • 15
  • when I skip `bind_class` then I can stil use `Control+C` to copy text from `Entry` in `filedialog` - so this object already have binded this combination. And maybe this makes problem. – furas Apr 14 '21 at 02:59
  • It seems it uses `ttk.Entry` instead of `tkinter.Entry` and it behave in different way. – furas Apr 14 '21 at 03:02
  • @furas I think that this depends on the system defaults. But I can reproduce the problem by binding to something else, like Control-A, as well. – sasquires Apr 14 '21 at 03:03
  • Oh, interesting. How can you tell whether it is using `ttk.Entry` or `tkinter.Entry`? – sasquires Apr 14 '21 at 03:04
  • I'm not sure if they use `ttk.Entry` but I created both in main window and I see they look different - at least on Linux - `ttk.Entry` has white background like in `filedialog` but `tkinter.Entry` has gray background. But now I used `print( filedialog.__file__ )` to get path to source code and check it. – furas Apr 14 '21 at 03:09
  • 2
    `root.nametowidget()` would have been the proper solution - except that it only works on widgets explicitly created by your code. You're going to need to do this directly in Tcl - try `root.tk.call("event", "generate", event.widget, "<>")`. – jasonharper Apr 14 '21 at 03:24
  • @jasonharper Works beautifully. So then is my understanding basically correct, that the string is actually the name of a `Tk` widget that I don't have access to in Python? If you post as an answer then I will accept it. – sasquires Apr 14 '21 at 03:33
  • The underlying Tk implementation just uses widget names everywhere - they aren't first-class objects like the Python wrapper makes them appear to be. `event.widget` is the only place I can think of where this is actually visible in Python. – jasonharper Apr 14 '21 at 03:42
  • @jasonharper I agree with @sasquires you should send it as answer. I found it runs `Tk` widget `tk_getSaveFile` I got the same problem with `root.nametowidget()` to access it as widget. – furas Apr 14 '21 at 04:03

0 Answers0