0

This question is an extension to this.

I am creating comboboxes as popup windows when user double-clicks on a treeview. When I scroll my mouse on the treeview, the entry-box with the arrow moves okay. However, the associated dropdown listbox does not move. I can see that the ttk combobox is actually a combination of an Entry and a listbox/PopdownWindow. But I cannot find proper documentation on how to access the listbox portion and move it.

import tkinter as tk
from tkinter import ttk

class ComboPopup(ttk.Combobox):
    def __init__(self, parent, itemId, col, **kw):
        super().__init__(parent, **kw)
        self.tv = parent
        self.iId = itemId
        self.column = col
        choices = ["option1", "option2", "option3"]
        v = tk.StringVar()
        self.config(state="readonly", textvariable=v, values=choices, width=9)

        self.focus_force()

        existingChoice = 1
        self.current(existingChoice)
        #self.set(self.choices[existingChoice])

        self.bind("<Return>", self.onReturn)
        self.bind("<Escape>", self.onEscape)
        #self.bind("<FocusOut>", self.onFocusOut)        

    def saveCombo(self):    
        self.tv.set(self.iId, column=self.column, value=self.get())
        print("EntryPopup::saveEdit---{}".format(self.iId))

    def onEscape(self, event):
        print("ComboPopup::onEscape")
        # give focus back to the treeview.
        self.tv.focus_set()

        self.destroy()
    def onReturn(self, event):
        self.tv.focus_set()
        self.saveCombo()
        self.destroy()

class EditableDataTable(tk.Frame):
    def __init__(self, parent):
        tk.Frame.__init__(self, parent)

        self.parent = parent
        self.tree = None
        self.comboPopup = None
        self.curSelectedRowId = ""
        columns = ("Col1", "Col2")
        # Create a treeview with vertical scrollbar.
        self.tree = ttk.Treeview(self, columns=columns, show="headings")
        self.tree.grid(column=0, row=0, sticky='news')
        self.tree.heading("#1", text="col1")
        self.tree.heading("#2", text="col2")
        self.vsb = ttk.Scrollbar(self, orient="vertical", command=self.tree.yview)
        self.tree.configure(yscrollcommand=self.vsb.set)
        self.vsb.grid(column=1, row=0, sticky='ns')

        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure(0, weight=1)

        col1 = []
        col2 = []
        for r in range(50):
            col1.append("data 1-{}".format(r))
            col2.append("data 2-{}".format(r))

        for i in range(min(len(col1),len(col2))):
            self.tree.insert('', i, values=(col1[i], col2[i]))

        self.tree.bind('<Double-1>', self.onDoubleClick)
        self.tree.bind("<MouseWheel>", self.onMousewheel)

    def onMousewheel(self, event):
        popupWindow = None
        #TODO: Fix the scroll of combobox (the associated listbox)        
        if self.comboPopup != None:
            if ttk.Combobox.winfo_exists(self.comboPopup):
                popupWindow = self.comboPopup

                pd = popupWindow.tk.call('ttk::combobox::PopdownWindow', popupWindow)
                lb = popupWindow.tk.eval('return {}.f.l'.format(pd))
                print("pw: {}".format(popupWindow))
                print("pd: {}".format(pd))
                print("lb: {}".format(lb))

        if popupWindow != None:
            def _move():
                try:
                    iid = popupWindow.iId
                    x, y, width, height = self.tree.bbox(iid, column="#2") #hardcoded to col2
                    popupWindow.place(x=x, y=y+height//2, anchor='w', width=width)
                except ValueError:
                    popupWindow.place_forget()
                except tk.TclError:
                    pass

            popupWindow.after(5, _move)
            if ttk.Combobox.winfo_exists(self.comboPopup):
                #pd.after(5, _move) # does not work
                #lb.after(5, _move) # does not work
                pass

    def createPopup(self, row, column):
        x, y, width, height = self.tree.bbox(row, column)

        # y-axis offset
        pady = height // 2
        self.comboPopup = ComboPopup(self.tree, row, column)
        self.comboPopup.x = x
        self.comboPopup.y = y+pady
        self.comboPopup.place(x=x, y=y+pady, anchor='w', width=width)

    def onDoubleClick(self, event):
        rowid = self.tree.identify_row(event.y)
        column = self.tree.identify_column(event.x)
        self.createPopup(rowid, column)

root = tk.Tk()

for row in range(2):
    root.grid_rowconfigure(row, weight=1)
root.grid_columnconfigure(0, weight=1)

label = tk.Label(root, text="Double-click to edit and press 'Enter'")
label.grid(row=0, column=0, sticky='news', padx=10, pady=5)

dataTable = EditableDataTable(root)
dataTable.grid(row=1, column=0, sticky="news", pady=10, padx=10)

root.geometry("450x300")
root.mainloop()

How can I access the 'listbox' and move it along with the 'entry'? I am using tkinter version 8.6 on Python 3.

Jakaria
  • 197
  • 1
  • 7

1 Answers1

1

As I said before in post How to move popup window when scrolling a tkinter treeview? you will need to move the widgets, that means doing the math and keeping track of it. I'm not sure why you would need more then one active popup widget during editing. My solution to inline editing for treeview is to allow only one cell to be edited at a time. Doing it this way reduces the complexity of the code needed to support such a thing. I have a working demo of this posted on github, I would post the code here but there is a lot of code in the demo. Treeview is a very tough beast to handle if you want to cover it all. Anyways I'll post the link below give it a run it might suit your needs. The code is being worked on currently but is usable as is. The code is in one file and no imports are needed.

https://github.com/unodan/TkInter-Treeview-Example-Demo

Daniel Huckson
  • 1,157
  • 1
  • 13
  • 35
  • I am not trying to edit more than one cell. A ttk.Combobox is, in fact, a combination of an Entry and a Listbox. When you launch a Combobox popup, there are two widgets: a text field and a popdown listbox. When I move the Combobox object on the treeview mouse wheel, only the text field moves, the popdown does not move. It looks like I need to manually access it and try to move it. I found the closest problem here but I can't make it work with the example code that I provided above: https://stackoverflow.com/questions/48172185/how-to-justify-the-characters-in-drop-down-list-of-a-combobox – Jakaria Jun 11 '20 at 05:20
  • @Jakaria, run the treeview demo I provided above I have a combobox in the last column. Just the first and last column are editable in the demo. My combobox works fine. Maybe you are going about this the wrong way. Give that code a run and see if that's what you are looking for. Also to add items right click. – Daniel Huckson Jun 11 '20 at 05:43
  • Thanks a lot for the demo! That's a great project. I am actually working on something similar. However, your popups (both Entry and Combobox) do not (yet) move up and down when you scroll the mousewheel over the treeview. To replicate, make your main window's height smaller so that the scrollbar appears, double-click on the first or last column, the popup appears, move your mouse to hover over the tree and scroll the mousewheel. The popup does not move, only the tree rows and scrollbar move. I want the popup to move, as well. In my code, popup Entry works okay, but Combobox does not. – Jakaria Jun 11 '20 at 17:30
  • @Jakaria, Oh I see that now, I will fix that and tell you my solution. – Daniel Huckson Jun 11 '20 at 18:00
  • @Jakaria, What needs to be done is like I said you need to do the math and keep track of the focused item. When you know what line and column has focus the rest is just math. I have updated the github treeview demo code so that the popup widgets now moves. Look at the popup_widget_place() method that's where the magic happens. – Daniel Huckson Jun 11 '20 at 20:07
  • @Jakaria, If you re-ren the demo remember to delete the two json files "app.json and treeview.json" files before you run. If you don't the old setup will apply and my updates won't work. Every time you download an updated version of the code this should be done. As I have said this project is under development. – Daniel Huckson Jun 11 '20 at 21:10
  • 1
    @Jakaria, as the code shows you can make it work, it's jittery and I prefer to just delete the popup widget when scrolling. Doing this as I said before will make the code much easier to write. The user can always re-click the cell they want to edit so it's no big deal. I have updated the code once again to work the way I think it should. Give it a try and let me know what you think. – Daniel Huckson Jun 11 '20 at 22:21
  • I think you are right that we should remove the popups when the user starts scrolling. In your current code, I had to change the event 'Shift_Tab' to 'Shift-Tab' for your Entry popups to make it work correctly. I am curious, though, about your popup_widget_place() method, which you have removed already. Could you please share it? – Jakaria Jun 12 '20 at 03:49
  • @Jakaria, you can view it here https://github.com/unodan/TkInter-Treeview-Example-Demo/blob/304ae3415a0e7824ea266dff552328330ee38fcd/treeview_demo.py – Daniel Huckson Jun 12 '20 at 03:59
  • @Jakaria, if you run the code from the link in the comment above it does as this question asked. Like I said above I found that when scrolling the popup widget was jittery and doesn't look appealing to me so I delete the popups when scrolling happens. It's up to the user to re-click if they want to start editing aging. That's probably what the user was going to do anyways that's why they were scrolling. Also if you run the code don't forget to delete any old json files from previous runs. – Daniel Huckson Jun 12 '20 at 15:02
  • Daniel, unfortunately, it's not working for me. I noticed that the and events are not working for me. However, even if I comment out the wheel_mouse method and its bindings, the treeview scrolls just fine. What am I missing here? I used event for my mouse wheel event. I am using python 3.7.4 and tkinter version 8.6. I am on Windows 10. – Jakaria Jun 14 '20 at 03:02