首页 > 解决方案 > MultiListbox 上的双击事件

问题描述

我有三个 MultiListbox 项目和两个条目。当我双击 MultiListbox 项目时,MultiListbox 选定的行信息将出现在条目中。出于这个原因,我试图在我的 MultiListbox 类中添加一个函数并在另一个类中调用这个函数,但我不知道该怎么做。这是我的代码:

from tkinter import *
from tkinter.ttk import *
import tkinter as tk
import sqlite3
from tkinter import messagebox


class MultiListbox(Frame):

    def __init__(self, master, lists):
        Frame.__init__(self, master)
        self.grid(sticky="nsew")
        self.select_index = None
        i = 2
        for num, (l, w) in enumerate(lists):
            if num == 0:
                continue
            self.grid_columnconfigure(num, weight=w, uniform='fred')
            i += num
        self.grid_rowconfigure(1, weight=1)
        self.lists = []

        for num, (l, w) in enumerate(lists):
            frame = Frame(self, borderwidth=0)
            frame.grid_columnconfigure(0, weight=1)
            frame.grid_rowconfigure(1, weight=1)
            frame.grid(row=1, column=num, sticky="nsew")
            lbl = Label(frame, text=l, font=("Vrinda (Body CS)", 11), borderwidth=1, relief=SUNKEN, anchor="center", justify="center")
            lbl.grid(row=0, column=0, sticky="nsew")
            lb = Listbox(frame, font=("Vrinda (Body CS)", 9), height=5, borderwidth=1, selectborderwidth=1, exportselection=FALSE)
            lb.grid(row=1, column=0, sticky="nsew")

            self.lists.append(lb)
            lb.bind('<B1-Motion>', lambda e, s=self: s._select(e.y))
            lb.bind('<Button-1>', lambda e, s=self: s._select(e.y))
            lb.bind('<Leave>', lambda e: 'break')
            lb.bind('<B2-Motion>', lambda e, s=self: s._b2motion(e.x, e.y))
            lb.bind('<Button-2>', lambda e, s=self: s._button2(e.x, e.y))
            lb.bind('&lt;Button-4>', lambda e, s=self: s._scroll(SCROLL, 1, PAGES))
            lb.bind('&lt;Button-5>', lambda e, s=self: s._scroll(SCROLL, -1, PAGES))
            lb.bind("<MouseWheel>", self.OnMouseWheel)

        sb_y = Scrollbar(self, orient=VERTICAL, command=self._yscroll)
        sb_y.grid(row=1, rowspan=2, column=i, sticky="nsew")
        self.lists[0]['yscrollcommand'] = sb_y.set

        sb_x = Scrollbar(self, orient=HORIZONTAL, command=self._xscroll)
        sb_x.grid(row=3, column=0, columnspan=i, sticky="ew")
        self.lists[0]['xscrollcommand'] = sb_x.set

    def _button2(self, x, y):
        for l in self.lists: l.scan_mark(x, y)
        return 'break'

    def _b2motion(self, x, y):
        for l in self.lists: l.scan_dragto(x, y)
        return 'break'

    def _yscroll(self, *args):
        for l in self.lists:
            l.yview(*args)

    def _xscroll(self, *args):
        for l in self.lists:
            l.xview(*args)

    def curselection(self):
        return self.lists[0].curselection()

    def delete(self, first, last=None):
        for l in self.lists:
            l.delete(first, last)

    def get(self, first, last=None):
        result = []
        for l in self.lists:
            result.append(l.get(first,last))
        if last: return list(map(*[None] + result))
        return result

    def index(self, index):
        self.lists[0].index(index)

    def insert(self, index, *elements):
        for e in elements:
            i = 0
            for l in self.lists:
                l.insert(index, e[i])
                i = i + 1

    def size(self):
        return self.lists[0].size()

    def see(self, index):
        for l in self.lists:
            l.see(index)

    def _select(self, y):
        row = self.lists[0].nearest(y)
        self.selection_clear(0, END)
        self.selection_set(row)
        return 'break'

    def selection_anchor(self, index):
        for l in self.lists:
            l.selection_anchor(index)

    def selection_clear(self, first, last=None):
        for l in self.lists:
            l.selection_clear(first, last)

    def selection_includes(self, index):
        return self.lists[0].selection_includes(index)

    def selection_set(self, first, last=None):
        self.select_index =[first]
        for l in self.lists:
            l.selection_set(first, last)

    def OnMouseWheel(self, event):
        for l in self.lists:
            l.yview("scroll", event.delta,"units")
        # this prevents default bindings from firing, which
        # would end up scrolling the widget twice
        return "break"


class FormAddProduct:

    def __init__(self, master):
        self.frame = master
        self.frame.configure(padx=5)
        for i in range(0, 1):
            self.frame.grid_columnconfigure(i, weight=1)
        self.frame.grid_rowconfigure (1, weight = 1)
        self._init_widgets()

    def _init_widgets(self):
        self.frame1 = tk.Frame(self.frame, relief=FLAT, borderwidth=1)
        for i in range(0, 2):
            self.frame1.grid_columnconfigure(i, weight=1)
        for i in range(0, 4):
            self.frame1.grid_rowconfigure(i, weight=1)

        lbl = Label(self.frame1, text="Product Name").grid(row=0, column=0)
        self.ent_item_code = Entry(self.frame1)
        self.ent_item_code.grid(row=0, column=1, sticky="nesw")

        lbl = Label(self.frame1, text="Amount").grid(row=1, column=0)
        self.ent_item_code = Entry(self.frame1)
        self.ent_item_code.grid(row=1, column=1, sticky="nesw")

        self.mlb = MultiListbox(self.frame1, (
            ('Product Name', 3), ('Amount $', 1)))
        self.mlb.grid(row=2, column=0, columnspan=2, sticky="nsew")
        a1 = ("Apple", 10)
        a2 = ("Orange", 25)
        a3 = ("Banana", 25)
        self.mlb.insert(END, a1)
        self.mlb.insert(END, a2)
        self.mlb.insert(END, a3)

        self.frame1.grid(row=1, column=0, sticky="nsew", padx=1, pady=5)

def main():
    app = Tk()
    FormAddProduct(app)
    app.mainloop()

if __name__=='__main__':
    main()

标签: tkinterpython-3.7

解决方案


我可以想到两种方法来解决这个问题,尽管可能还有其他方法。

首先,您可以使用虚拟事件。MultiListbox可以生成事件并可以FormAddProduct绑定到它。或者,FormAddProduct可以传递一个回调来MultiListbox告诉它在发生双击时调用什么。

使用虚拟事件

首先添加一个函数MultiListbox来生成事件:

class MultiListbox(Frame):
    ...
    def _double1(self, event):
        self.event_generate("<<DoubleClick>>")
    ...

接下来,在列表框中绑定到此函数:

class MultiListbox(Frame):
    def __init__(self, master, lists):
        ...
        for num, (l, w) in enumerate(lists):
            ...
            lb.bind('<Double-1>', self._double1)

最后,在 中FormAddProduct,添加一个绑定到这个事件来调用你的函数:

class FormAddProduct:
    def _init_widgets(self):
        ...
        self.mlb.bind("<<DoubleClick>>", self.do_something)

    def do_something(self, event):
        print("Double-click!")

使用回调

要使用回调,__init__需要MultiListbox接受回调作为参数,并将其保存到实例变量中:

class MultiListbox(Frame):

    def __init__(self, master, lists, callback=None):
        Frame.__init__(self, master)
        self.callback = callback
        ...

接下来,我们可以创建一个双击调用回调的函数。您需要决定将哪些信息传递给回调。例如,您可以传入选定的产品名称和金额。以下示例传递 的实例,MultiListbox以便回调可以与 的多个实例一起使用MultiListbox

class MultiListbox(Frame):
    ...
    def _double1(self):
        if self.callback is not None:
            self.callback(listbox=self)

接下来,我们需要在FormAddProduct. 请记住,它必须接受 the 的实例MultiListbox作为参数。此示例使用它来获取当前选择并将其打印出来:

class FormAddProduct:  
    ...  
    def do_something(self, listbox):
        curselection = listbox.curselection()
        value = None
        if curselection:
            item = curselection[0]
            value = listbox.get(item)
        print(f"callback was called: {value}")

最后,在创建时将此函数作为回调传递MultiListbox

class FormAddProduct:
    def _init_widgets(self):
        ...
    self.mlb = MultiListbox(self.frame1, (
        ('Product Name', 3), ('Amount $', 1)), callback=self.do_something)

推荐阅读