Tkinter / TTK - Prevent string to ButtonPress conversion

余生颓废 提交于 2019-12-10 12:24:13

问题


I'm writing a simple script that creates a ttk Treeview (that acts as a table) and, when you double-click it, it opens a file (with the path saved in the dictionary). However, when you double-click a row you'll get this error:

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Users\Maicol\AppData\Local\Programs\Python\Python36\lib\tkinter\__init__.py",
line 1699, in __call__
    return self.func(*args)
  File "C:\Users\Maicol\Documents\Projects\App_WINDOWS\School_Life_Diary\note.py",
line 195, in <lambda>
    lambda f=nt[x]["URIallegato"]: os.startfile(str(f)))
FileNotFoundError: [WinError 2] Can't find the specified file: '<ButtonPress event state=Mod1 num=1 x=677 y=37>'

The problem is this code:

t.bind("<Double-1>", lambda f=nt[x]["URIallegato"]: os.startfile(str(f)))

that allows the double-clicking and opening of the file.

Here is the full Treeview code:

t=Treeview(w)
t.pack(padx=10,pady=10)
for x in list(nt.keys()):
    t.insert("",x,text=nt[x]["allegati"])
    if nt[x]["allegati"]!="":
        t.bind("<Double-1>",
               lambda f=nt[x]["URIallegato"]: os.startfile(str(f)))

回答1:


When the event fires, tkinter will pass along an event object. You are trying to open that event object as if it were a file.

Why is that? Let's start by rewriting your lambda as a proper function. Your lambda is the equivalent of this function:

def handle_event(f=default_value):
    os.startfile(str(default_value))

When the event fires, it does the equivalent of this:

handle_event(event)

Your script is given a single positional argument, event, which is assigned to the first keyword argument. Thus f is the same as event.

The solution is to make sure your lambda accepts the event, which it can simply ignore:

lambda event, f=nt[x]["URIallegato"]: os.startfile(str(f)))

With the above, the event object will be associated with the event parameter, and your default value for f will be passed as f.




回答2:


The main issue is about creating a binding for the Treeview in the loop.

There is only one double click event that can be declared and triggered for the tree, not one by row, and here you are overriding it in each iteration.


This lambda pattern is known to declare commands for widgets inside a for/loop, and it works fine for this purpose:

lambda f=nt[x]["URIallegato"]: os.startfile(str(f))

But here you declare a default parameter f, and the lambda will be executed with an event argument given by the event binding, that's what you get in the exception : <ButtonPress event state=Mod1...

Anyway, we saw that this won't work in your case even if you fix the lambda with a second parameter to accept the event without replacing your default value f.


What i suggest is to use the values field of each row to store the information URIallegato" without displaying the column in the tree.

And then you can bind a generic event to the Treeview, by using focus() to get the selected item, and extract the value to get URI.

t=Treeview(w)
t.pack(padx=10,pady=10)

def open_item(event):
    item = t.item(t.focus())
    if item['text']:
        os.startfile(item['values'][0])

for x in list(nt.keys()):
    value = ''
    if nt[x]["allegati"]:
        value = str(nt[x]["URIallegato"])
    t.insert("",x,text=nt[x]["allegati"], values=value)

t.bind("<Double-1>", open_item)

A lambda could hardly be used here if you want to check if there is an URI to open.



来源:https://stackoverflow.com/questions/46506048/tkinter-ttk-prevent-string-to-buttonpress-conversion

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!