Mohammad Yasin
Mohammad Yasin

Reputation: 43

Double Click Event on MultiListbox

I have three MultiListbox items and two entries. When I will double click on the MultiListbox item then MultiListbox selected row information will come to the entries. For this reason, I'm trying to add a function in my MultiListbox Class and called this function in another Class but I don't know how can I do this. Heare is My code:

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()

Upvotes: 0

Views: 616

Answers (1)

Bryan Oakley
Bryan Oakley

Reputation: 385970

I can think of two ways to solve this problem, though there are probably others.

First, you can use a virtual event. The MultiListbox can generate the event and FormAddProduct can bind to it. Or, FormAddProduct can pass a callback to MultiListbox to tell it what to call when a double-click occurs.

Using a Virtual Event

Start by adding a function in MultiListbox to generate the event:

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

Next, bind to this function in your listboxes:

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

Finally, in FormAddProduct, add a binding to this event to call your function:

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

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

Using a callback

To use a callback, the __init__ of MultiListbox needs to accept the callback as an argument, and save it to an instance variable:

class MultiListbox(Frame):

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

Next, we can create a function that calls the callback on a double click. You need to decide on what information to pass to the callback. For example, you could pass in the selected product name and amount. The following example passes the instance of MultiListbox so that the callback can be used with multiple instances of MultiListbox:

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

Next, we need to define the callback in FormAddProduct. Remember, it must accept the instance of the MultiListbox as an argument. This example uses that to get the current selection and print it out:

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}")

Finally, pass this function as the callback when creating the MultiListbox:

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

Upvotes: 1

Related Questions