Advertisement
Uno-Dan

Treeview with more class

May 8th, 2020
2,268
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 9.93 KB | None | 0 0
  1.  
  2. import tkinter as tk
  3. import tkinter.ttk as ttk
  4. import tkinter.font as tkfont
  5.  
  6.  
  7. class Treeview(ttk.Treeview):
  8.     def __init__(self, parent, **kwargs):
  9.         super().__init__(parent, **kwargs)
  10.  
  11.         self.root = parent.winfo_toplevel()
  12.         self.parent = parent
  13.         self.active_item = None
  14.         self.selected_items = []
  15.  
  16.         sw = self.select_window = tk.Toplevel(self.parent)
  17.         sw.wait_visibility(self.parent)
  18.         sw.withdraw()
  19.         sw.config(bg='#00aaff')
  20.         sw.overrideredirect(True)
  21.         sw.wm_attributes('-alpha', 0.3)
  22.         sw.wm_attributes("-topmost", True)
  23.  
  24.     def fixed_map(self, option):
  25.         style = self.parent.style
  26.  
  27.         return [elm for elm in style.map("Treeview", query_opt=option) if elm[:2] != ("!disabled", "!selected")]
  28.  
  29.     def tag_add(self, tags, item):
  30.         self.tags_update('add', tags, item)
  31.  
  32.     def tag_remove(self, tags, item=None):
  33.         self.tags_update('remove', tags, item)
  34.  
  35.     def tag_replace(self, old, new, item=None):
  36.         for item in (item,) if item else self.tag_has(old):
  37.             self.tags_update('add', new, item)
  38.             self.tags_update('remove', old, item)
  39.  
  40.     def tags_reset(self, _=None):
  41.         self.tag_remove(('selected', '_selected'))
  42.         self.tag_replace('selected_odd', 'odd')
  43.         self.tag_replace('selected_even', 'even')
  44.  
  45.     def tags_update(self, opt, tags, item):
  46.         def get_items(node):
  47.             items.append(node)
  48.             for node in self.get_children(node):
  49.                 get_items(node)
  50.  
  51.         if not tags:
  52.             return
  53.         elif isinstance(tags, str):
  54.             tags = (tags,)
  55.  
  56.         if not item:
  57.             items = []
  58.             for child in self.get_children():
  59.                 get_items(child)
  60.         else:
  61.             items = [item]
  62.  
  63.         for item in items:
  64.             _tags = list(self.item(item, 'tags'))
  65.             for _tag in tags:
  66.                 if opt == 'add':
  67.                     if _tag not in _tags:
  68.                         _tags.append(_tag)
  69.                 elif opt == 'remove':
  70.                     if _tag in _tags:
  71.                         _tags.pop(_tags.index(_tag))
  72.             self.item(item, tags=_tags)
  73.  
  74.     def button_press(self, event):
  75.         self.root.anchor_x, self.root.anchor_y = event.x, event.y
  76.         item = self.root.anchor_item = self.active_item = self.identify('item', event.x, event.y)
  77.         sw = self.select_window
  78.  
  79.         sw.geometry('0x0+0+0')
  80.         sw.deiconify()
  81.  
  82.         self.bind('<Motion>', self.set_selected)
  83.  
  84.         if not item:
  85.             if not event.state & 1 << 2:
  86.                 self.tags_reset()
  87.             return
  88.  
  89.         if event.state & 1 << 2:
  90.             if self.tag_has('odd', item):
  91.                 self.tag_add('selected', item)
  92.                 self.tag_replace('odd', 'selected_odd', item)
  93.             elif self.tag_has('even', item):
  94.                 self.tag_add('selected', item)
  95.                 self.tag_replace('even', 'selected_even', item)
  96.             elif self.tag_has('selected_odd', item):
  97.                 self.tag_replace('selected_odd', 'odd', item)
  98.             elif self.tag_has('selected_even', item):
  99.                 self.tag_replace('selected_even', 'even', item)
  100.         else:
  101.             self.tags_reset()
  102.             self.tag_add('selected', item)
  103.             if self.tag_has('odd', item):
  104.                 self.tag_replace('odd', 'selected_odd', item)
  105.             elif self.tag_has('even', item):
  106.                 self.tag_replace('even', 'selected_even', item)
  107.  
  108.     def button_release(self, _):
  109.         self.select_window.withdraw()
  110.         self.unbind('<Motion>')
  111.  
  112.         for item in self.selected_items:
  113.             if self.tag_has('odd', item) or self.tag_has('even', item):
  114.                 self.tag_remove(('selected', '_selected'), item)
  115.             else:
  116.                 self.tag_replace('_selected', 'selected')
  117.  
  118.     def get_selected(self, event):
  119.         selected_items = []
  120.         window_y = int(self.root.geometry().rsplit('+', 1)[-1])
  121.         titlebar_height = self.root.winfo_rooty() - window_y
  122.         sw = self.select_window
  123.         start = sw.winfo_rooty() - titlebar_height - window_y
  124.         end = start + sw.winfo_height()
  125.  
  126.         while start < end:
  127.             start += 1
  128.             node = self.identify('item', event.x, start)
  129.             if not node or node in selected_items:
  130.                 continue
  131.             selected_items.append(node)
  132.  
  133.         return sorted(selected_items)
  134.  
  135.     def set_selected(self, event):
  136.         def set_row_colors():
  137.             items = self.selected_items = self.get_selected(event)
  138.  
  139.             for item in items:
  140.                 if self.tag_has('selected', item):
  141.                     if item == self.root.anchor_item:
  142.                         continue
  143.  
  144.                     if self.tag_has('selected_odd', item):
  145.                         self.tag_replace('selected_odd', 'odd', item)
  146.                         self.tag_add('_selected', item)
  147.                     elif self.tag_has('selected_even', item):
  148.                         self.tag_replace('selected_even', 'even', item)
  149.                         self.tag_add('_selected', item)
  150.  
  151.                 elif self.tag_has('odd', item):
  152.                     self.tag_replace('odd', 'selected_odd', item)
  153.                     self.tag_add('_selected', item)
  154.                 elif self.tag_has('even', item):
  155.                     self.tag_replace('even', 'selected_even', item)
  156.                     self.tag_add('_selected', item)
  157.  
  158.             for item in self.tag_has('_selected'):
  159.                 if item not in items:
  160.                     self.tag_remove('_selected', item)
  161.                     if self.tag_has('odd', item):
  162.                         self.tag_replace('odd', 'selected_odd', item)
  163.                     elif self.tag_has('even', item):
  164.                         self.tag_replace('even', 'selected_even', item)
  165.                     elif self.tag_has('selected_odd', item):
  166.                         self.tag_replace('selected_odd', 'odd', item)
  167.                     elif self.tag_has('selected_even', item):
  168.                         self.tag_replace('selected_even', 'even', item)
  169.  
  170.         font = tkfont.nametofont('TkTextFont')
  171.         linespace = font.metrics('linespace') + 5
  172.  
  173.         root_x = self.root.winfo_rootx()
  174.         if event.x < self.root.anchor_x:
  175.             width = self.root.anchor_x - event.x
  176.             coord_x = root_x + event.x
  177.         else:
  178.             width = event.x - self.root.anchor_x
  179.             coord_x = root_x + self.root.anchor_x
  180.  
  181.         if coord_x+width > root_x+self.winfo_width():
  182.             width -= (coord_x+width)-(root_x+self.winfo_width())
  183.         elif self.winfo_pointerx() < root_x:
  184.             width -= (root_x - self.winfo_pointerx())
  185.             coord_x = root_x
  186.  
  187.         root_y = self.winfo_rooty()
  188.         if event.y < self.root.anchor_y:
  189.             height = self.root.anchor_y - event.y
  190.             coord_y = root_y + event.y
  191.         else:
  192.             height = event.y - self.root.anchor_y
  193.             coord_y = root_y + self.root.anchor_y
  194.  
  195.         if coord_y+height > root_y+self.winfo_height():
  196.             height -= (coord_y+height)-(root_y+self.winfo_height())
  197.         elif self.winfo_pointery() < root_y + linespace:
  198.             height -= (root_y - self.winfo_pointery() + linespace)
  199.             coord_y = root_y + linespace
  200.             if height < 0:
  201.                 height = self.winfo_rooty() + self.root.anchor_y
  202.  
  203.         self.select_window.geometry(f'{width}x{height}+{coord_x}+{coord_y}')
  204.  
  205.         set_row_colors()
  206.  
  207.  
  208. class App(tk.Tk):
  209.     def __init__(self):
  210.         super().__init__()
  211.  
  212.         def print_selected_items(_):
  213.             print(sorted(tv.tag_has('selected_odd') + tv.tag_has('selected_even')))
  214.             return 'break'
  215.  
  216.         tv = Treeview(self)
  217.  
  218.         style = self.style = ttk.Style()
  219.         style.map("Treeview", foreground=tv.fixed_map("foreground"), background=tv.fixed_map("background"))
  220.  
  221.         tv.heading('#0', text='Name')
  222.         tv.tag_configure('odd', background='#ffffff')
  223.         tv.tag_configure('even', background='#aaaaaa')
  224.         tv.tag_configure('selected_odd', background='#b0eab2')
  225.         tv.tag_configure('selected_even', background='#25a625')
  226.  
  227.         color_tag = 'odd'
  228.         for idx in range(0, 4):
  229.             # Populating the tree with test data.
  230.             color_tag = 'even' if color_tag == 'odd' else 'odd'
  231.             tv.insert('', idx, f'!{idx}', text=f'Item {idx+1}', open=1, tags=(color_tag,))
  232.             color_tag = 'even' if color_tag == 'odd' else 'odd'
  233.             iid = f'!{idx}_!{0}'
  234.             tv.insert(f'!{idx}', '0', iid, text=f'Menu {idx+1}', open=1, tags=(color_tag,))
  235.             for i in range(0, 5):
  236.                 color_tag = 'even' if color_tag == 'odd' else 'odd'
  237.                 tv.insert(iid, i, f'{iid}_!{i}', text=f'Sub item {i+1}', tags=(color_tag,))
  238.  
  239.             color_tag = 'even' if color_tag == 'odd' else 'odd'
  240.             tv.insert(iid, 5, f'{iid}_!{5}', text=f'Another Menu {idx+1}', open=1, tags=(color_tag,))
  241.             iid = f'{iid}_!{5}'
  242.             for i in range(0, 3):
  243.                 color_tag = 'even' if color_tag == 'odd' else 'odd'
  244.                 tv.insert(iid, i, f'{iid}_!{i}', text=f'Sub item {i+1}', tags=(color_tag,))
  245.  
  246.         self.title('Treeview Demo')
  247.         self.geometry('275x650+3000+250')
  248.         self.rowconfigure(0, weight=1)
  249.         self.columnconfigure(0, weight=1)
  250.         self.update_idletasks()
  251.  
  252.         tv.config(selectmode="none")
  253.         tv.grid(sticky='NSEW')
  254.  
  255.         button = ttk.Button(self, text='Get Selected Items')
  256.         button.grid()
  257.  
  258.         button.bind('<Button-1>', print_selected_items)
  259.  
  260.         self.bind('<Escape>', tv.tags_reset)
  261.         self.bind('<Button-1>', tv.button_press)
  262.         self.bind('<ButtonRelease-1>', tv.button_release)
  263.  
  264.  
  265. def main():
  266.     app = App()
  267.     app.mainloop()
  268.  
  269.  
  270. if __name__ == '__main__':
  271.     main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement