How can I collect all Qtreeview items so i can then iterate over them and apply necessary changes like display text updates, or color changes?
Is there an easy way to collect all of them using the 'match' method?
def get_checked(self):
model = self.treeview.model()
checked = model.match(
model.index(0, 0), QtCore.Qt.CheckStateRole,
QtCore.Qt.Checked, -1,
QtCore.Qt.MatchExactly | QtCore.Qt.MatchRecursive)
for index in checked:
item = model.itemFromIndex(index)
print(item.text())
test code:
from PySide import QtGui, QtCore
from PySide import QtSvg, QtXml
import sys
class Person:
def __init__(self, name="", children=None):
self.name = name
self.children = children if children else []
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.resize(300, 400)
self.init_ui()
def init_ui(self):
# Setup Tabs Widget
# self.treeview = QtGui.QTreeView()
self.treeview = QtGui.QTreeView()
self.treeview.setHeaderHidden(True)
self.treeview.setUniformRowHeights(True)
self.treeview.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.model = QtGui.QStandardItemModel()
self.treeview.setModel(self.model)
self.action = QtGui.QAction('Print', self)
self.action.setShortcut('F5')
self.action.triggered.connect(self.get_checked)
fileMenu = QtGui.QMenu("&File", self)
fileMenu.addAction(self.action)
self.menuBar().addMenu(fileMenu)
# Setup central widget
self.setCentralWidget(self.treeview)
# populate data
self.populate_people()
self.treeview.expandAll()
def populate_people(self):
parent = Person("Kevin", [
Person("Tom", [Person("Sally"), Person("Susan")]),
Person("Snappy", [Person("John"), Person("Kimmy"),
Person("Joe")]),
Person("Chester", [Person("Danny"), Person("Colleen")])
]
)
self.create_nodes(parent, self.model)
def create_nodes(self, node, parent):
tnode = QtGui.QStandardItem()
tnode.setCheckable(True)
tnode.setData(QtCore.Qt.Unchecked, role=QtCore.Qt.CheckStateRole)
tnode.setData(node.name , role=QtCore.Qt.DisplayRole)
tnode.setData(node, role=QtCore.Qt.UserRole) # store object on item
parent.appendRow(tnode)
for x in node.children:
self.create_nodes(x, tnode)
def get_checked(self):
print "collecting..."
def main():
app = QtGui.QApplication(sys.argv)
ex = MainWindow()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Here is a recursive solution (requires Python >= 3.3):
def iterItems(self, root):
def recurse(parent):
for row in range(parent.rowCount()):
for column in range(parent.columnCount()):
child = parent.child(row, column)
yield child
if child.hasChildren():
yield from recurse(child)
if root is not None:
yield from recurse(root)
Alternative recursive solution (for Python2/Python3):
def iterItems(self, root):
def recurse(parent):
if root is not None:
for row in range(parent.rowCount()):
for column in range(parent.columnCount()):
child = parent.child(row, column)
yield child
if child.hasChildren():
for item in recurse(child):
yield item
return recurse(root)
And here is an iterative solution (for Python2/Python3):
def iterItems(self, root):
if root is not None:
stack = [root]
while stack:
parent = stack.pop(0)
for row in range(parent.rowCount()):
for column in range(parent.columnCount()):
child = parent.child(row, column)
yield child
if child.hasChildren():
stack.append(child)
(NB: this solution gives a more predictable ordering, and is a little faster)
Usage:
root = self.treeview.model().invisibleRootItem()
for item in self.iterItems(root):
print(item.text())
The root argument can be any item in a QStandardItemModel.
I have created 2 trees with idlelib.TreeWidget in Canvas, left and right.
I am able to print out the name of a tree node if double-clicked, but what I need is double-clicking tree node ONLY from the left will print out messages.
Please run the following code (left tree is self.canvas, right is self.canvas2):
from Tkinter import Tk, Frame, BOTH, Canvas
from xml.dom.minidom import parseString
from idlelib.TreeWidget import TreeItem, TreeNode
class DomTreeItem(TreeItem):
def __init__(self, node):
self.node = node
def GetText(self):
node = self.node
if node.nodeType == node.ELEMENT_NODE:
return node.nodeName
elif node.nodeType == node.TEXT_NODE:
return node.nodeValue
def IsExpandable(self):
node = self.node
return node.hasChildNodes()
def GetSubList(self):
parent = self.node
children = parent.childNodes
prelist = [DomTreeItem(node) for node in children]
itemlist = [item for item in prelist if item.GetText().strip()]
return itemlist
def OnDoubleClick(self):
print self.node.nodeName
content = '''
<level0>
<level1/>
</level0>
'''
class Application(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.parent.geometry('%dx%d+%d+%d' % (800, 300, 0, 0))
self.parent.resizable(0, 0)
dom = parseString(content)
item = DomTreeItem(dom.documentElement)
self.canvas = Canvas(self, bg = "cyan")
self.canvas.grid(column = 0, row = 0, sticky = 'NSWE')
node = TreeNode(self.canvas, None, item)
node.update()
dom2 = parseString(content)
item2 = DomTreeItem(dom2.documentElement)
self.canvas2 = Canvas(self, bg = "yellow")
self.canvas2.grid(column = 1, row = 0, sticky = 'NSWE')
node2 = TreeNode(self.canvas2, None, item2)
node2.update()
self.pack(fill = BOTH, expand = True)
def main():
root = Tk()
Application(root)
root.mainloop()
if __name__ == '__main__':
main()
You just need to modify the DomTreeItem class to take an argument that determines if it should act on double-click or not:
class DomTreeItem(TreeItem):
def __init__(self, node, doubleclick=True): # set the value of double-click
self.node = node
self.doubleclick = doubleclick # make the value an instance variable
def GetText(self):
node = self.node
if node.nodeType == node.ELEMENT_NODE:
return node.nodeName
elif node.nodeType == node.TEXT_NODE:
return node.nodeValue
def IsExpandable(self):
node = self.node
return node.hasChildNodes()
def GetSubList(self):
parent = self.node
children = parent.childNodes
prelist = [DomTreeItem(node, self.doubleclick) for node in children] # pass it to the nodes
itemlist = [item for item in prelist if item.GetText().strip()]
return itemlist
def OnDoubleClick(self):
if self.doubleclick: # check if it's set to True
print self.node.nodeName # and only print it then
Then, when you make a new instance of the class, just set doubleclick to True or False. If you don't want a double-click to trigger on the second tree, instantiate it like this:
item2 = DomTreeItem(dom2.documentElement, doubleclick=False)
Python 2.7 Linux
I am only using idlelib.TreeWidget to create a tree in Tkinter.Canvas, nothing else.
How do I go about getting the information of the selected tree node (eg. name)? I need to access this information later.
I am able to call a function when the Canvas is selected / double-clicked, but not sure how with the tree nodes:
self.canvas.bind('<Double-Button-1>', self.onSave)
Please run the following code (Note that there are 2 trees, 1 tree in each Canvas):
from Tkinter import Tk, Frame, BOTH, Canvas
from xml.dom.minidom import parseString
from idlelib.TreeWidget import TreeItem, TreeNode
class DomTreeItem(TreeItem):
def __init__(self, node):
self.node = node
def GetText(self):
node = self.node
if node.nodeType == node.ELEMENT_NODE:
return node.nodeName
elif node.nodeType == node.TEXT_NODE:
return node.nodeValue
def IsExpandable(self):
node = self.node
return node.hasChildNodes()
def GetSubList(self):
parent = self.node
children = parent.childNodes
prelist = [DomTreeItem(node) for node in children]
itemlist = [item for item in prelist if item.GetText().strip()]
return itemlist
data = '''
<angel>
<digital_core>
<radio_top>d</radio_top>
<uart_top>e</uart_top>
</digital_core>
<digital_core>
<c>f</c>
</digital_core>
</angel>
'''
class Application(Frame):
def __init__(self, parent):
Frame.__init__(self, parent, background = "white")
self.parent = parent
# Maximize window
self.screenWidth = self.parent.winfo_screenwidth() - 5
self.screenHeight = self.parent.winfo_screenheight() - 110
self.parent.geometry('%dx%d+%d+%d' % (self.screenWidth, self.screenHeight, 0, 0))
self.parent.resizable(0, 0)
dom = parseString(data)
item = DomTreeItem(dom.documentElement)
self.canvas = Canvas(self, bg = "cyan")
self.canvas.grid(column = 0, row = 0, rowspan = 2, sticky = 'NSWE', padx = 5, pady = 5)
node = TreeNode(self.canvas, None, item)
node.update()
self.canvas2 = Canvas(self, bg = "yellow")
self.canvas2.grid(column = 1, row = 0, rowspan = 2, sticky = 'NSWE', padx = 5, pady = 5)
node2 = TreeNode(self.canvas2, None, item)
node2.update()
parent.configure(bg = "black")
self.pack(fill = BOTH, expand = True, padx = 20, pady = 20)
def main():
root = Tk()
Application(root)
root.mainloop()
if __name__ == '__main__':
main()
The most direct approach I have seen is accessing the fields of the TreeNode to retrieve content passed into the TreeNode constructor.
For instance,
node.parent
node.canvas
node.item
To get the text from the DomTreeItem, you can do
node.item.GetText()
You can also get additional information by looking at the methods in
dir(node.item)
DanGar's answer was helpful, but not exactly what I want.
TreeItem already has built-in functions GetSelectedIconName and OnDoubleClick, so I used it in my modified class to get the name of selection:
class DomTreeItem(TreeItem):
def __init__(self, node):
self.node = node
def GetText(self):
node = self.node
if node.nodeType == node.ELEMENT_NODE:
return node.nodeName
elif node.nodeType == node.TEXT_NODE:
return node.nodeValue
def IsExpandable(self):
node = self.node
return node.hasChildNodes()
def GetSubList(self):
parent = self.node
children = parent.childNodes
prelist = [DomTreeItem(node) for node in children]
itemlist = [item for item in prelist if item.GetText().strip()]
return itemlist
def GetSelectedIconName(self):
print self.node.nodeName
def OnDoubleClick(self):
print "double-clicked"
these are first few tries with python and I want to add 2 widgets to a gtk.
I know and saw that you can only add one at a time and already know how to do that.
can someone give me the loophole to be able to have also a "tree" and a right mouse click menu?
This is my code:
import gtk
class treeNode():
def __init__(self, father, name, link):
self.father = father
self.name = name
self.link = link
class PyApp(gtk.Window):
def __init__(self):
super(PyApp, self).__init__()
self.set_size_request(700, 500)
self.set_position(gtk.WIN_POS_CENTER)
self.connect("destroy", gtk.main_quit)
self.set_title("Assignment1")
eventbox = gtk.EventBox()
tree = gtk.TreeView()
trying = gtk.TreeViewColumn()
trying.set_title("Get Busy")
cell = gtk.CellRendererText()
trying.pack_start(cell, True)
trying.add_attribute(cell, "text", 0)
treestore = gtk.TreeStore(str)
father = None
name = ["default"]
node = treestore.append(father, name)
node = treeNode(father, name, node)
lst = [node]
father = lst[0].link
name = ["cluster1"]
node = treestore.append(father, name)
node = treeNode(father, name, node)
lst.append(node)
father = lst[1].link
name = ["clusterA"]
node = treestore.append(father, name)
node = treeNode(father, name, node)
lst.append(node)
father = lst[0].link
name = ["cluster2"]
node = treestore.append(father, name)
node = treeNode(father, name, node)
lst.append(node)
father = lst[3].link
name = ["clusterA"]
node = treestore.append(father, name)
node = treeNode(father, name, node)
lst.append(node)
tree.append_column(trying)
tree.set_model(treestore)
self.add(tree)
self.show_all()
self.menu = gtk.Menu()
addMenu = gtk.MenuItem("Add")
renManu = gtk.MenuItem("Rename")
remMenu = gtk.MenuItem("Remove")
self.menu.append(addMenu)
self.menu.append(renManu)
self.menu.append(remMenu)
eventbox.connect("button-release-event", self.menu_display)
self.add(eventbox)
self.show_all()
def menu_display(self, widget, event):
if event.button == 3:
self.menu.popup(None, None, None, event.button, event.time, None)
self.menu.show_all()
PyApp()
gtk.main()
Thanks a lot
Please try to write clear questions: "I want to add 2 widgets to a gtk" is not meaningful.
I'm going to assume you want to add multiple widgets into a Window (or some other Bin), is that right? You can't do that since a Bin can only have one child (as the runtime error message will tell you): instead you should add a suitable container widget -- like a VBox -- into the Window, and then add your widgets into the container widget.
I've been using Tkinter and Tix to write a small program.
I'm at a point where I need a tree view with checkboxes (checkbuttons) so I can select items from the tree view.
Is there an easy way to do this?
I've been looking at ttk.Treeview () and it looks easy to get the tree view but is there a way to insert a checkbutton to the view?
A simple code snippet would be really appreciated.
I'm not limited to ttk. Anything will do; as long as I have an example or good docs I can make it work
import Tix
class View(object):
def __init__(self, root):
self.root = root
self.makeCheckList()
def makeCheckList(self):
self.cl = Tix.CheckList(self.root, browsecmd=self.selectItem)
self.cl.pack()
self.cl.hlist.add("CL1", text="checklist1")
self.cl.hlist.add("CL1.Item1", text="subitem1")
self.cl.hlist.add("CL2", text="checklist2")
self.cl.hlist.add("CL2.Item1", text="subitem1")
self.cl.setstatus("CL2", "on")
self.cl.setstatus("CL2.Item1", "on")
self.cl.setstatus("CL1", "off")
self.cl.setstatus("CL1.Item1", "off")
self.cl.autosetmode()
def selectItem(self, item):
print item, self.cl.getstatus(item)
def main():
root = Tix.Tk()
view = View(root)
root.update()
root.mainloop()
if __name__ == '__main__':
main()
I made a treeview class with checkboxes inheriting ttk.Treeview, but the checkboxes are not ttk.Checkbutton but images of checked, unchecked and tristate checkboxes.
import tkinter as tk
import tkinter.ttk as ttk
class CheckboxTreeview(ttk.Treeview):
"""
Treeview widget with checkboxes left of each item.
The checkboxes are done via the image attribute of the item, so to keep
the checkbox, you cannot add an image to the item.
"""
def __init__(self, master=None, **kw):
ttk.Treeview.__init__(self, master, **kw)
# checkboxes are implemented with pictures
self.im_checked = tk.PhotoImage(file='checked.png')
self.im_unchecked = tk.PhotoImage(file='unchecked.png')
self.im_tristate = tk.PhotoImage(file='tristate.png')
self.tag_configure("unchecked", image=self.im_unchecked)
self.tag_configure("tristate", image=self.im_tristate)
self.tag_configure("checked", image=self.im_checked)
# check / uncheck boxes on click
self.bind("<Button-1>", self.box_click, True)
def insert(self, parent, index, iid=None, **kw):
""" same method as for standard treeview but add the tag 'unchecked'
automatically if no tag among ('checked', 'unchecked', 'tristate')
is given """
if not "tags" in kw:
kw["tags"] = ("unchecked",)
elif not ("unchecked" in kw["tags"] or "checked" in kw["tags"]
or "tristate" in kw["tags"]):
kw["tags"] = ("unchecked",)
ttk.Treeview.insert(self, parent, index, iid, **kw)
def check_descendant(self, item):
""" check the boxes of item's descendants """
children = self.get_children(item)
for iid in children:
self.item(iid, tags=("checked",))
self.check_descendant(iid)
def check_ancestor(self, item):
""" check the box of item and change the state of the boxes of item's
ancestors accordingly """
self.item(item, tags=("checked",))
parent = self.parent(item)
if parent:
children = self.get_children(parent)
b = ["checked" in self.item(c, "tags") for c in children]
if False in b:
# at least one box is not checked and item's box is checked
self.tristate_parent(parent)
else:
# all boxes of the children are checked
self.check_ancestor(parent)
def tristate_parent(self, item):
""" put the box of item in tristate and change the state of the boxes of
item's ancestors accordingly """
self.item(item, tags=("tristate",))
parent = self.parent(item)
if parent:
self.tristate_parent(parent)
def uncheck_descendant(self, item):
""" uncheck the boxes of item's descendant """
children = self.get_children(item)
for iid in children:
self.item(iid, tags=("unchecked",))
self.uncheck_descendant(iid)
def uncheck_ancestor(self, item):
""" uncheck the box of item and change the state of the boxes of item's
ancestors accordingly """
self.item(item, tags=("unchecked",))
parent = self.parent(item)
if parent:
children = self.get_children(parent)
b = ["unchecked" in self.item(c, "tags") for c in children]
if False in b:
# at least one box is checked and item's box is unchecked
self.tristate_parent(parent)
else:
# no box is checked
self.uncheck_ancestor(parent)
def box_click(self, event):
""" check or uncheck box when clicked """
x, y, widget = event.x, event.y, event.widget
elem = widget.identify("element", x, y)
if "image" in elem:
# a box was clicked
item = self.identify_row(y)
tags = self.item(item, "tags")
if ("unchecked" in tags) or ("tristate" in tags):
self.check_ancestor(item)
self.check_descendant(item)
else:
self.uncheck_descendant(item)
self.uncheck_ancestor(item)
if __name__ == '__main__':
root = tk.Tk()
t = CheckboxTreeview(root, show="tree")
t.pack()
t.insert("", 0, "1", text="1")
t.insert("1", "end", "11", text="1")
t.insert("1", "end", "12", text="2")
t.insert("12", "end", "121", text="1")
t.insert("12", "end", "122", text="2")
t.insert("122", "end", "1221", text="1")
t.insert("1", "end", "13", text="3")
t.insert("13", "end", "131", text="1")
root.mainloop()
An improved version of CheckboxTreeview is available in the ttkwidgets module.
If you can use Tix, go with #Brandon's solution. If you are stuck with Ttk (as I am), here is an solution based on #j_4231's idea. Rather than using an image to represent the checkbox, we can use two characters provided by Unicode:
'BALLOT BOX' (U+2610) : ☐
'BALLOT BOX WITH X (U+2612)' : ☒.
Those character are located after the item name and are used to check the current state: treeview.item(iid, "text")[-1] is either ☐ or ☒. We can update the item name when the text is clicked.
The class TtkCheckList inherits ttk.Treeview, hence the usual parameters/methods of Treeview can be used.
import tkinter as tk
from tkinter import ttk
BALLOT_BOX = "\u2610"
BALLOT_BOX_WITH_X = "\u2612"
class TtkCheckList(ttk.Treeview):
def __init__(self, master=None, width=200, clicked=None, separator='.',
unchecked=BALLOT_BOX, checked=BALLOT_BOX_WITH_X, **kwargs):
"""
:param width: the width of the check list
:param clicked: the optional function if a checkbox is clicked. Takes a
`iid` parameter.
:param separator: the item separator (default is `'.'`)
:param unchecked: the character for an unchecked box (default is
"\u2610")
:param unchecked: the character for a checked box (default is "\u2612")
Other parameters are passed to the `TreeView`.
"""
if "selectmode" not in kwargs:
kwargs["selectmode"] = "none"
if "show" not in kwargs:
kwargs["show"] = "tree"
ttk.Treeview.__init__(self, master, **kwargs)
self._separator = separator
self._unchecked = unchecked
self._checked = checked
self._clicked = self.toggle if clicked is None else clicked
self.column('#0', width=width, stretch=tk.YES)
self.bind("<Button-1>", self._item_click, True)
def _item_click(self, event):
assert event.widget == self
x, y = event.x, event.y
element = self.identify("element", x, y)
if element == "text":
iid = self.identify_row(y)
self._clicked(iid)
def add_item(self, item):
"""
Add an item to the checklist. The item is the list of nodes separated
by dots: `Item.SubItem.SubSubItem`. **This item is used as `iid` at
the underlying `Treeview` level.**
"""
try:
parent_iid, text = item.rsplit(self._separator, maxsplit=1)
except ValueError:
parent_iid, text = "", item
self.insert(parent_iid, index='end', iid=item,
text=text+" "+self._unchecked, open=True)
def toggle(self, iid):
"""
Toggle the checkbox `iid`
"""
text = self.item(iid, "text")
checked = text[-1] == self._checked
status = self._unchecked if checked else self._checked
self.item(iid, text=text[:-1] + status)
def checked(self, iid):
"""
Return True if checkbox `iid` is checked
"""
text = self.item(iid, "text")
return text[-1] == self._checked
def check(self, iid):
"""
Check the checkbox `iid`
"""
text = self.item(iid, "text")
if text[-1] == self._unchecked:
self.item(iid, text=text[:-1] + self._checked)
def uncheck(self, iid):
"""
Uncheck the checkbox `iid`
"""
text = self.item(iid, "text")
if text[-1] == self._checked:
self.item(iid, text=text[:-1] + self._unchecked)
Here is an example:
items = [
'Item',
'Item.SubItem1',
'Item.SubItem2',
'Item.SubItem2.SubSubItem1',
'Item.SubItem2.SubSubItem2',
'Item.SubItem2.SubSubItem3',
'Item.SubItem3',
'Item.SubItem3.SubSubItem1',
'Item.SubItem4'
]
root = tk.Tk()
root.title('Test')
root.geometry('400x300')
check_list = TtkCheckList(root, height=len(items))
for item in items:
check_list.add_item(item)
check_list.pack()
root.mainloop()
You can use the clicked parameter to define a new behavior when an item is
clicked. For instance:
def obey_ancestor(iid):
"""
If the status of an item is toggled, the status of all its descendants
is also set to the new status.
"""
set_status = check_list.uncheck if check_list.checked(iid) else check_list.check
stack = [iid]
while stack:
iid = stack.pop()
set_status(iid)
stack.extend(check_list.get_children(iid))
And:
check_list = TtkCheckList(root, height=len(items),
clicked=obey_ancestor)
I would add to jferard's great answer that if you want to have a table of values rather than a tree structure change the following:
In the init add:
self.column('#1', width=width, stretch=tk.YES)
for each column you want.
add_item should be:
def add_item(self, item):
"""
Add an item to the checklist. The item is the list of nodes separated
by dots: `Item.SubItem.SubSubItem`. **This item is used as `iid` at
the underlying `Treeview` level.**
"""
# try:
# parent_iid, text = item.rsplit(self._separator, maxsplit=1)
# except ValueError:
# parent_iid, text = "", item
# self.insert(parent_iid, index='end', iid=item, text=text+" "+self._unchecked, open=True)
self.insert('', index='end', iid=item, values = item, text=self._unchecked, open=True)
Change the example as such:
cols = ['One', 'Two']
items = [('A', '1',),('B','2')]
check_list = TtkCheckList(root, columns = cols, height=len(items))