Answer a question

I'm trying to use the mouse to select and deselect multiple items. I have it working sort of but there is a problem when the user moves the mouse to fast. When the mouse is moved fast some items are skipped and are not selected at all. I must be going about this the wrong way.

Update 1: I decided to use my own selecting system, but I get the same results as above. Some items are skipped when the mouse is moved to fast and therefore they don't get the correct color tag added and remain unchanged. If the mouse is moved slowly all items get selected correctly. Below is the new code and another Image of the problem.

Update 2: I have solved this issue and posted the working code just for completeness and to help others in the future. I ended up using my own selection selecting system instead of the built in one.

Sample Image

import tkinter as tk
import tkinter.ttk as ttk


class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title('Treeview Demo')
        self.geometry('300x650')
        self.rowconfigure(0, weight=1)
        self.columnconfigure(0, weight=1)

        tv = self.tv = ttk.Treeview(self)
        tv.heading('#0', text='Name')
        # Populate tree with test data.
        for idx in range(0, 4):
            tv.insert('', idx, f'!{idx}', text=f'Item {idx+1}', tags='TkTextFont', open=1)
            iid = f'!{idx}_!{idx}'
            tv.insert(f'!{idx}', '0', iid, text=f'Python {idx+1}', tags='TkTextFont', open=1)
            for i in range(0, 5):
                tv.insert(iid, f'{i}', f'{iid}_!{i}', text=f'Sub item {i+1}', tags='TkTextFont')

        tv.grid(sticky='NSEW')
        self.active_item = None

        def motion(_):
            x, y = tv.winfo_pointerxy()
            item = tv.identify('item', x - tv.winfo_rootx(), y - tv.winfo_rooty())
            if not item or item == self.active_item:
                return

            if not self.active_item:
                self.active_item = item

            tv.selection_toggle(item)
            self.active_item = item

        def escape(_):
            tv.selection_remove(tv.selection())

        def button_press(_):
            self.bind('<Motion>', motion)

        def button_release(_):
            self.unbind('<Motion>')
            self.active_item = None

        self.bind('<Escape>', escape)
        self.bind('<Button-1>', button_press)
        self.bind('<ButtonRelease-1>', button_release)


def main():
    app = App()
    app.mainloop()


if __name__ == '__main__':
    main()

Second Try:

Sample Image 2

import tkinter as tk
import tkinter.ttk as ttk
import tkinter.font as tkfont


class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title('Treeview Demo')
        self.geometry('700x650')
        self.rowconfigure(0, weight=1)
        self.columnconfigure(0, weight=1)

        tv = self.tv = ttk.Treeview(self)
        tv.heading('#0', text='Name')
        tv.tag_configure('odd', background='#aaaaaa')
        tv.tag_configure('even', background='#ffffff')
        tv.tag_configure('selected_odd', background='#25a625')
        tv.tag_configure('selected_even', background='#b0eab2')

        tag = 'odd'
        # Populate tree with test data.
        for idx in range(0, 4):
            tag = 'even' if tag == 'odd' else 'odd'
            tv.insert('', idx, f'!{idx}', text=f'Item {idx+1}', open=1, tags=(tag,))
            tag = 'even' if tag == 'odd' else 'odd'
            iid = f'!{idx}_!{idx}'
            tv.insert(f'!{idx}', '0', iid, text=f'Python {idx+1}', open=1, tags=(tag,))
            for i in range(0, 5):
                tag = 'even' if tag == 'odd' else 'odd'
                tv.insert(iid, i, f'{iid}_!{i}', text=f'Sub item {i+1}', tags=(tag,))

        tv.config(selectmode="none")
        tv.grid(sticky='NSEW')

        dw = tk.Toplevel()
        dw.overrideredirect(True)
        dw.wait_visibility(self)
        dw.wm_attributes('-alpha', 0.2)
        dw.wm_attributes("-topmost", True)
        dw.config(bg='#00aaff')
        dw.withdraw()
        self.selected = False
        self.active_item = None

        def motion(event):
            x, y = self.winfo_pointerxy()
            width = event.x-self.anchor_x
            height = event.y-self.anchor_y
    
            if width < 0:
                coord_x = event.x+self.winfo_rootx()
                width = self.anchor_x - event.x
            else:
                coord_x = self.anchor_x+self.winfo_rootx()
    
            if coord_x+width > self.winfo_rootx()+self.winfo_width():
                width -= (coord_x+width)-(self.winfo_rootx()+self.winfo_width())
            elif x < self.winfo_rootx():
                width -= (self.winfo_rootx() - x)
                coord_x = self.winfo_rootx()
    
            if height < 0:
                coord_y = event.y+self.winfo_rooty()
                height = self.anchor_y - event.y
            else:
                coord_y = self.anchor_y+self.winfo_rooty()
    
            if coord_y+height > self.winfo_rooty()+self.winfo_height():
                height -= (coord_y+height)-(self.winfo_rooty()+self.winfo_height())
            elif y < self.winfo_rooty():
                height -= (self.winfo_rooty() - y)
                coord_y = self.winfo_rooty()

            dw.geometry(f'{width}x{height}+{coord_x}+{coord_y}')

            item = tv.identify('item', coord_x, coord_y-40)
            if not item or item == self.active_item:
                self.active_item = None
                return

            self.active_item = item
            tags = list(tv.item(item, 'tags'))
            if 'odd' in tags:
                tags.pop(tags.index('odd'))
                tags.append('selected_odd')

            if 'even' in tags:
                tags.pop(tags.index('even'))
                tags.append('selected_even')

            tv .item(item, tags=tags)

        def escape(_=None):
            for item in tv.tag_has('selected_odd'):
                tags = list(tv.item(item, 'tags'))
                tags.pop(tags.index('selected_odd'))
                tags.append('odd')
                tv.item(item, tags=tags)

            for item in tv.tag_has('selected_even'):
                tags = list(tv.item(item, 'tags'))
                tags.pop(tags.index('selected_even'))
                tags.append('even')
                tv.item(item, tags=tags)

        def button_press(event):
            if self.selected and not event.state & 1 << 2:
                escape()
                self.selected = False

            dw.deiconify()
            self.anchor_item = tv.identify('item', event.x, event.y-40)
            self.anchor_x, self.anchor_y = event.x, event.y
            self.bind('<Motion>', motion)
            self.selected = True

        def button_release(event):
            dw.withdraw()
            dw.geometry('0x0+0+0')
            self.unbind('<Motion>')

        self.bind('<Escape>', escape)
        self.bind('<Button-1>', button_press)
        self.bind('<ButtonRelease-1>', button_release)


def main():
    app = App()
    app.mainloop()


if __name__ == '__main__':
    main()

Answers

Working Example:

Tested and works in Windows and Linux

UPDATE: I have updated the code and everything works in Windows and Linux, although in Windows the blue transparent window is jittery when sizing from right to left, left to right is fine. In Linux the jitters don't happen anyone know why? If someone could let me know if the code below works in MacOS that would be great also.

import tkinter as tk
import tkinter.ttk as ttk
import tkinter.font as tkfont


class Treeview(ttk.Treeview):
    def __init__(self, parent, **kwargs):
        super().__init__(parent, **kwargs)

        self.root = parent.winfo_toplevel()
        self.selected_items = []
        self.origin_x = \
            self.origin_y = \
            self.active_item = \
            self.origin_item = None

        sw = self.select_window = tk.Toplevel(self.root)
        sw.wait_visibility(self.root)
        sw.withdraw()
        sw.config(bg='#00aaff')
        sw.overrideredirect(True)
        sw.wm_attributes('-alpha', 0.3)
        sw.wm_attributes("-topmost", True)

        self.font = tkfont.nametofont('TkTextFont')
        self.style = parent.style
        self.linespace = self.font.metrics('linespace') + 5

        self.bind('<Escape>', self.tags_reset)
        self.bind('<Button-1>', self.button_press)
        self.bind('<ButtonRelease-1>', self.button_release)

    def fixed_map(self, option):
        return [elm for elm in self.style.map("Treeview", query_opt=option) if elm[:2] != ("!disabled", "!selected")]

    def tag_add(self, tags, item):
        self.tags_update('add', tags, item)

    def tag_remove(self, tags, item=None):
        self.tags_update('remove', tags, item)

    def tag_replace(self, old, new, item=None):
        for item in (item,) if item else self.tag_has(old):
            self.tags_update('add', new, item)
            self.tags_update('remove', old, item)

    def tags_reset(self, _=None):
        self.tag_remove(('selected', '_selected'))
        self.tag_replace('selected_odd', 'odd')
        self.tag_replace('selected_even', 'even')

    def tags_update(self, opt, tags, item):
        def get_items(node):
            items.append(node)
            for node in self.get_children(node):
                get_items(node)

        if not tags:
            return
        elif isinstance(tags, str):
            tags = (tags,)

        if not item:
            items = []
            for child in self.get_children():
                get_items(child)
        else:
            items = (item,)

        for item in items:
            _tags = list(self.item(item, 'tags'))
            for _tag in tags:
                if opt == 'add':
                    if _tag not in _tags:
                        _tags.append(_tag)
                elif opt == 'remove':
                    if _tag in _tags:
                        _tags.pop(_tags.index(_tag))
            self.item(item, tags=_tags)

    def button_press(self, event):
        self.origin_x, self.origin_y = event.x, event.y
        item = self.origin_item = self.active_item = self.identify('item', event.x, event.y)

        sw = self.select_window
        sw.geometry('0x0+0+0')
        sw.deiconify()

        self.bind('<Motion>', self.set_selected)

        if not item:
            if not event.state & 1 << 2:
                self.tags_reset()
            return

        if event.state & 1 << 2:
            if self.tag_has('odd', item):
                self.tag_add('selected', item)
                self.tag_replace('odd', 'selected_odd', item)
            elif self.tag_has('even', item):
                self.tag_add('selected', item)
                self.tag_replace('even', 'selected_even', item)
            elif self.tag_has('selected_odd', item):
                self.tag_replace('selected_odd', 'odd', item)
            elif self.tag_has('selected_even', item):
                self.tag_replace('selected_even', 'even', item)
        else:
            self.tags_reset()
            self.tag_add('selected', item)
            if self.tag_has('odd', item):
                self.tag_replace('odd', 'selected_odd', item)
            elif self.tag_has('even', item):
                self.tag_replace('even', 'selected_even', item)

    def button_release(self, _):
        self.select_window.withdraw()
        self.unbind('<Motion>')

        for item in self.selected_items:
            if self.tag_has('odd', item) or self.tag_has('even', item):
                self.tag_remove(('selected', '_selected'), item)
            else:
                self.tag_replace('_selected', 'selected', item)

    def get_selected(self):
        return sorted(self.tag_has('selected_odd') + self.tag_has('selected_even'))

    def set_selected(self, event):
        def selected_items():
            items = []
            window_y = int(self.root.geometry().rsplit('+', 1)[-1])
            titlebar_height = self.root.winfo_rooty() - window_y
            sw = self.select_window
            start = sw.winfo_rooty() - titlebar_height - window_y
            end = start + sw.winfo_height()

            while start < end:
                start += 1
                node = self.identify('item', event.x, start)
                if not node or node in items:
                    continue
                items.append(node)

            return sorted(items)

        def set_row_colors():
            items = self.selected_items = selected_items()

            for item in items:
                if self.tag_has('selected', item):
                    if item == self.origin_item:
                        continue

                    if self.tag_has('selected_odd', item):
                        self.tag_replace('selected_odd', 'odd', item)
                    elif self.tag_has('selected_even', item):
                        self.tag_replace('selected_even', 'even', item)

                elif self.tag_has('odd', item):
                    self.tag_replace('odd', 'selected_odd', item)
                elif self.tag_has('even', item):
                    self.tag_replace('even', 'selected_even', item)

                self.tag_add('_selected', item)

            for item in self.tag_has('_selected'):
                if item not in items:
                    self.tag_remove('_selected', item)
                    if self.tag_has('odd', item):
                        self.tag_replace('odd', 'selected_odd', item)
                    elif self.tag_has('even', item):
                        self.tag_replace('even', 'selected_even', item)
                    elif self.tag_has('selected_odd', item):
                        self.tag_replace('selected_odd', 'odd', item)
                    elif self.tag_has('selected_even', item):
                        self.tag_replace('selected_even', 'even', item)

        root_x = self.root.winfo_rootx()
        if event.x < self.origin_x:
            width = self.origin_x - event.x
            coord_x = root_x + event.x
        else:
            width = event.x - self.origin_x
            coord_x = root_x + self.origin_x

        if coord_x+width > root_x+self.winfo_width():
            width -= (coord_x+width)-(root_x+self.winfo_width())
        elif self.winfo_pointerx() < root_x:
            width -= (root_x - self.winfo_pointerx())
            coord_x = root_x

        root_y = self.winfo_rooty()
        if event.y < self.origin_y:
            height = self.origin_y - event.y
            coord_y = root_y + event.y
        else:
            height = event.y - self.origin_y
            coord_y = root_y + self.origin_y

        if coord_y+height > root_y+self.winfo_height():
            height -= (coord_y+height)-(root_y+self.winfo_height())
        elif self.winfo_pointery() < root_y + self.linespace:
            height -= (root_y - self.winfo_pointery() + self.linespace)
            coord_y = root_y + self.linespace
            if height < 0:
                height = self.winfo_rooty() + self.origin_y

        set_row_colors()
        self.select_window.geometry(f'{width}x{height}+{coord_x}+{coord_y}')


class App(tk.Tk):
    def __init__(self):
        super().__init__()

        def print_selected_items(_):
            print(tv.get_selected())
            return 'break'

        style = self.style = ttk.Style()

        tv = Treeview(self)
        style.map("Treeview", foreground=tv.fixed_map("foreground"), background=tv.fixed_map("background"))

        tv.heading('#0', text='Name')
        tv.tag_configure('odd', background='#ffffff')
        tv.tag_configure('even', background='#aaaaaa')
        tv.tag_configure('selected_odd', background='#b0eab2')
        tv.tag_configure('selected_even', background='#25a625')

        color_tag = 'odd'
        for idx in range(0, 4):
            # Populating the tree with test data.
            color_tag = 'even' if color_tag == 'odd' else 'odd'
            tv.insert('', idx, f'{idx}', text=f'Item {idx+1}', open=1, tags=(color_tag,))
            color_tag = 'even' if color_tag == 'odd' else 'odd'
            iid = f'{idx}_{0}'
            tv.insert(f'{idx}', '0', iid, text=f'Menu {idx+1}', open=1, tags=(color_tag,))
            for i in range(0, 5):
                color_tag = 'even' if color_tag == 'odd' else 'odd'
                tv.insert(iid, i, f'{iid}_{i}', text=f'Sub item {i+1}', tags=(color_tag,))

            color_tag = 'even' if color_tag == 'odd' else 'odd'
            tv.insert(iid, 5, f'{iid}_{5}', text=f'Another Menu {idx+1}', open=1, tags=(color_tag,))
            iid = f'{iid}_{5}'
            for i in range(0, 3):
                color_tag = 'even' if color_tag == 'odd' else 'odd'
                tv.insert(iid, i, f'{iid}_{i}', text=f'Sub item {i+1}', tags=(color_tag,))

        self.title('Treeview Demo')
        self.geometry('275x650+3000+250')
        self.rowconfigure(0, weight=1)
        self.columnconfigure(0, weight=1)

        tv.config(selectmode="none")
        tv.grid(sticky='NSEW')

        button = ttk.Button(self, text='Get Selected Items')
        button.grid()
        button.bind('<Button-1>', print_selected_items)


def main():
    app = App()
    app.mainloop()


if __name__ == '__main__':
    main()
Logo

学AI,认准AI Studio!GPU算力,限时免费领,邀请好友解锁更多惊喜福利 >>>

更多推荐