Python tkinter 列表框绑定在 <Button-1>仅适用于第二次点击

2022-01-16 00:00:00 python tkinter bind listbox sqlite

如果有其他讨论已经解决了这个问题,我先道歉,但我找不到任何东西.我是 Python 新手(就此而言,除了 90 年代的一点 Pascal 之外的编程).

I apologize upfront if there is another discussion that already addresses this issue, but I could not find anything. I am new to Python (and for that matter programming other than a little bit of Pascal back in the 90's).

我正在构建一个带有 tk 输入框的 GUI,供用户输入值,然后将其存储在 sqlite 数据库中.我希望用户能够单击列表框中某个字段的值,然后使用所选记录的所有字段的值重新填充 tk 条目框.我已经能够完成这项工作,但它仅在第二次单击时有效,因为我将列表框绑定到函数以填充 Button-1 上的 tk 条目框.第一次点击会产生以下错误,我在其他问题中看到过这些错误,但我无法将这些答案翻译成我的情况:

I am building a GUI with tk Entry boxes for a user to enter values, which is then stored in a sqlite db. I would like the user to be able to click on the values from one of the fields in a listbox, which would then re-populate the tk Entry boxes with the values for all fields for the selected record. I have been able to make this work, but it only works on the second click as I bind the list box to the function to populate the tk Entry boxes on Button-1. The first click generates the following error, which I have seen references to in other questions, but I cannot translate those answers to my situation:

错误:

Traceback(最近一次调用最后一次):调用中的文件...PythonPython38-32lib kinter__init__.py",第 1883 行返回 self.func(*args)文件fe.py",第 108 行,在选择项中selecteditem.append(lb1.get(ndex))文件...PythonPython38-32lib kinter__init__.py",第 3182 行,在获取return self.tk.call(self._w, 'get', first)_tkinter.TclError: bad listbox index "": must be active, anchor, end, @x,y, or a number

Traceback (most recent call last): File "...PythonPython38-32lib kinter__init__.py", line 1883, in call return self.func(*args) File "fe.py", line 108, in selectitem selecteditem.append(lb1.get(ndex)) File "...PythonPython38-32lib kinter__init__.py", line 3182, in get return self.tk.call(self._w, 'get', first) _tkinter.TclError: bad listbox index "": must be active, anchor, end, @x,y, or a number

这里是复制错误的示例代码 - 要使用,首先通过设置"->项目"菜单添加几个值,方法是填写 VAL 和 REF 框并单击添加项目"按钮.然后单击列表框中的一项.第一次单击应生成上述错误.当您再次单击时,应填充条目框:

Here is example code that replicates the error - to use, first add a a couple of values through the Setup -> Items menu by filling in the VAL and REF boxes and clicking the Add Item button. Then click on one of the items in the list box. The first click should generate the error above. When you click a second time the Entry boxes should populate:

```
import sqlite3  
import itertools  
import tkinter as tk  
from tkinter import ttk  

INTRO_FONT = ("Arial", 72)  
LARGE_FONT = ("Arial", 12)  
NORMAL_FONT = ("Arial", 10)  
SMALL_FONT = ("Arial", 8)  

#Function to create database  

def create_db():  
    conn = sqlite3.connect("demo.db")  
    cur = conn.cursor()  
    cur.execute("CREATE TABLE IF NOT EXISTS demotable (id INTEGER PRIMARY KEY, val TEXT, ref TEXT)")  
    conn.commit()  
    conn.close()  

#Create database

create_db()

#Main Class to manage frames

class demo(tk.Tk):

    def __init__(self,*args,**kwargs):

        tk.Tk.__init__(self,*args,**kwargs)

        container = tk.Frame(self)
        container.pack(side="top",fill="both",expand=True)
        container.grid_rowconfigure(0,weight=1)
        container.grid_columnconfigure(0,weight=1)

        menubar = tk.Menu(container)

        filemenu = tk.Menu(menubar,tearoff=0)
        filemenu.add_command(label="Exit", command=quit)
        menubar.add_cascade(label="File", menu=filemenu)

        setupmenu = tk.Menu(menubar, tearoff=0)
        setupmenu.add_command(label="Items",command = lambda: self.show_frame(Itemsetuppage))
        menubar.add_cascade(label="Setup", menu=setupmenu)

        tk.Tk.config(self, menu=menubar)

        self.frames={}

        for F in (Itemsetuppage,Itemsetuppage):

            frame = F(container, self)

            self.frames[F] = frame

            frame.grid(row=0, column=0, sticky="nsew")

        self.show_frame(Itemsetuppage)

    def show_frame(self, cont):

        frame = self.frames[cont]
        frame.tkraise()  

# Frame that inserts and loads values

class Itemsetuppage(tk.Frame):

def __init__(self, parent, controller):

    tk.Frame.__init__(self, parent)

    #Function to populate listbox with values

    def popitemlist(self):
        lb1.delete(0, tk.END)
        for item in itemlist():
            lb1.insert(tk.END, item)

    #Function to add new item to database
    def additem():
        additem_db(val.get(), ref.get())
        val_entry.delete(0, 'end')
        ref_entry.delete(0, 'end')
        popitemlist(self)

    #Function used to populate tk.Entry boxes from database when item in listbox is clicked
    def selectitem(event):
        global selecteditem
        selecteditem = []
        ndex = lb1.curselection()
        selecteditem.append(lb1.get(ndex))
        itemquery = select_item(selecteditem)
        val_entry.delete(0, 'end')
        val_entry.insert(0, itemquery[1])
        ref_entry.delete(0, 'end')
        ref_entry.insert(0, itemquery[2])

    #Function to query database for values to populate lb1
    def itemlist():
        conn = sqlite3.connect("demo.db")
        cur = conn.cursor()
        cur.execute("SELECT DISTINCT val FROM demotable")
        results = cur.fetchall()
        itemlist = list(itertools.chain(*results))
        conn.commit()
        conn.close()
        return itemlist

    #Function to insert values from tk.Entry boxes to database
    def additem_db(val, ref):
        conn = sqlite3.connect("demo.db")
        cur = conn.cursor()
        cur.execute("INSERT OR IGNORE INTO demotable VALUES (NULL, ?, ?)",(val,ref))
        conn.commit()
        conn.close()

    #Function to query database for individual record to populate tk.Entry boxes when item is clicked in lb1
    def select_item(val):
        conn = sqlite3.connect("demo.db")
        cur = conn.cursor()
        cur.execute("SELECT * FROM demotable WHERE val=?",(val))
        results = cur.fetchall()
        itemdetail = list(itertools.chain(*results))
        conn.commit()
        conn.close()
        return itemdetail

    l1 = tk.Label(self, text="Values in database:")
    l1.grid(row=0, column=0, padx=5, pady=5)

    lb1 = tk.Listbox(self, selectmode=tk.SINGLE)
    lb1.grid(row=1, column=0, padx=5, pady=5)
    popitemlist(self)
    lb1.bind("<Button-1>", selectitem)

    l2 = tk.Label(self, text="Type val into entry box to store:")
    l2.grid(row=0, column=1, padx=5, pady=5)

    val = tk.StringVar(self)
    val_entry = tk.Entry(self, textvariable=val)
    val_entry.grid(row=0, column=2, padx=5, pady=5)

    l2 = tk.Label(self, text="Type ref into entry box to store:")
    l2.grid(row=1, column=1, padx=5, pady=5)

    ref = tk.StringVar(self)
    ref_entry = tk.Entry(self, textvariable=ref)
    ref_entry.grid(row=1, column=2, padx=5, pady=5)

    b1 = tk.Button(self, text="Add item",command=additem)
    b1.grid(row=2, column=2, padx=5, pady=5)

app = demo()  
app.geometry("480x240")  
app.mainloop()

``` 

谢谢你&如果我的代码冒犯了任何人,我们深表歉意!

Thank you & apologies if my code offends anyone!

推荐答案

没有运行代码,因为我没有sqlite3数据库i,我可能会关闭,但listbox在选择一个值 <<ListboxSelect>> 可以绑定的.尝试替换:

Not having run your code as I have no sqlite3 database I may be way off, but listbox generates a virtual event when a value is selected <<ListboxSelect>> which you can bind to. Try replacing:

lb1.bind("<Button-1>", selectitem)

lb1.bind("<<ListboxSelect>>", selectitem)

另外,我认为 lb1.curselection() 返回一个元组...,尝试打印它,看看你得到了什么.在我检查的代码中,我总是做 lb1.curselection()[0].

Also, I think lb1.curselection() returns a tuple..., try printing it and see what you get. In the code I checked I always do lb1.curselection()[0].

相关文章