Python Tkinter: Tree double-click node - python

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)

Related

collect all items in QTreeview recursively

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.

Python Tkinter: Canvas scrolling with MouseWheel

I have created a tree inside a Canvas, and I have also allowed MouseWheel to scroll up and down.
However, how do I prevent scrolling if tree content has not exceed canvas size? (Tree content can possibly exceed canvas size by expanding)
Please run the following code:
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 = '''
<top>
<b>
<c>d</c>
<c>e</c>
</b>
<b>
<c><c><c><c><c>f</c></c></c></c></c>
<c><c><c><c><c>f</c></c></c></c></c>
<c><c><c><c><c>f</c></c></c></c></c>
</b>
</top>
'''
class Application(Frame):
def __init__(self, parent):
Frame.__init__(self, parent, background = "white")
parent.configure(bg = "black")
self.pack(fill = BOTH, expand = True, padx = 20, pady = 20)
self.parent = parent
self.parent.geometry('%dx%d+%d+%d' % (700, 700, 0, 0))
self.canvas = Canvas(self, bg = "white", bd = 10, highlightbackground = "black")
self.canvas.grid(column = 0, row = 0, rowspan = 2)
dom = parseString(data)
item = DomTreeItem(dom.documentElement)
node = TreeNode(self.canvas, None, item)
node.update()
node.expand()
self.parent.bind("<MouseWheel>", self.mouse_wheel) # Windows mouse wheel event
self.parent.bind("<Button-4>", self.mouse_wheel) # Linux mouse wheel event (Up)
self.parent.bind("<Button-5>", self.mouse_wheel) # Linux mouse wheel event (Down)
def mouse_wheel(self, event):
""" Mouse wheel as scroll bar """
direction = 0
# respond to Linux or Windows wheel event
if event.num == 5 or event.delta == -120:
direction = 1
if event.num == 4 or event.delta == 120:
direction = -1
self.canvas.yview_scroll(direction, "units")
def main():
root = Tk()
Application(root)
root.mainloop()
if __name__ == '__main__':
main()
You can use the bbox method of canvas to retrieve the actual height of drawn items on canvas. bbox return a tuple defining a rectangle. You can compare it with the height of your canvas widget.
height = self.canvas.winfo_height()
_,_,_,items_height = self.canvas.bbox(Tkinter.ALL)
if (items_height < height):
direction = 0
I'm pretty unfamiliar with Trees, but it looks like it's just a bunch of Labels. You can measure their heights and compare them with the height of the Canvas to determine if you need to scroll. This is a little sloppy, but it worked for me:
if self.canvas.winfo_reqheight() < len(self.canvas.winfo_children()) * self.canvas.winfo_children()[0].winfo_reqheight():
self.canvas.yview_scroll(direction, "units")
else:
pass
EDIT: in case that's too messy, here's the pseudo code:
if CANVAS_HEIGHT < NUMBER_OF_LABELS * LABEL_HEIGHT:
scroll

Python Tkinter: obtain tree node information

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"

Python Tkinter: Tree selection

I have created 2 trees with idlelib.TreeWidget in Canvas, left and right.
I am also able to print out the name of a tree node if double-clicked, but what I need is double-clicking one tree node will make a certain tree node visible and selected.
I have a simple example here. If you double click "level1" on the left hand side, "ccc" on the right hand side should be visible and automatically selected. How do you do that?
Please run the following code:
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
left = '''
<level0>
<level1/>
</level0>
'''
right = '''
<aaa>
<bbb> <ccc/> </bbb>
</aaa>
'''
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(left)
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(right)
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()
First, your double click callback must be aware of your TreeNode node2 (I can think of global variable, attribute in DomTreeItem or bounce to another component).
Then you can rely on expand() method of TreeNode, read the children attribute and expand sequentially until the element you want. Note that children attribute is only populated after the node has been expanded.
1. Quick answer
Quick and dirty solution for the example you have provided
class DomTreeItem(TreeItem):
def OnDoubleClick(self):
if self.GetText() == "level1":
node2.expand()
node2.children[0].expand()
node2.children[0].children[0].select()
[...]
class Application(Frame):
def __init__(self, parent):
global node2
2. Generic solution
Here is a more general method to display an arbitrary item in a tree.
def reach(node_tree, path):
tokens = path.split("/")
current_node = node_tree
for name in tokens:
if len(current_node.children) == 0 and current_node.state != "expanded":
current_node.expand()
candidates = [child for child in current_node.children if child.item.GetText() == name]
if len(candidates) == 0:
print("did not find '{}'".format(name))
return
current_node = candidates[0]
current_node.select()
You might use it this way
if self.GetText() == "level1":
reach(node2, "bbb/ccc")
3. Architecture proposal
Besides the expansion an selection of an item, I propose you a cleaner architecture through a DIY observer.
DIY Observer
(mimic the Tkinter bind call but does not rely on tkinter machinery since generating event with user data is not properly handled)
class DomTreeItem(TreeItem):
def __init__(self, node, dbl_click_bindings = None):
self.node = node
self.dbl_click_bindings = [] if (dbl_click_bindings == None) else dbl_click_bindings
[...]
def OnDoubleClick(self):
self.fireDblClick()
def bind(self, event, callback):
'''mimic tkinter bind
'''
if (event != "<<TreeDoubleClick>>"):
print("err...")
self.dbl_click_bindings.append(callback)
def fireDblClick(self):
for callback in self.dbl_click_bindings:
callback.double_click(self.GetText())
Dedicated component
Rely on the reach method presented above.
class TreeExpander:
def __init__(self, node_tree, matching_items):
self.node_tree = node_tree
self.matching_items = matching_items
def double_click(self, item_name):
print("double_click ({0})".format(item_name))
if (item_name in self.matching_items):
reach(self.node_tree, self.matching_items[item_name])
Subscription
class Application(Frame):
def __init__(self, parent):
[...]
expander = TreeExpander(node2, {
"level1": "bbb/ccc"
})
item.bind("<<TreeDoubleClick>>", expander)
I did not find docs for idlelib, in this case, you can try to look at the code. The following snippet allows you to find which file host this module.
import idlelib.TreeWidget
print(idlelib.TreeWidget.__file__)

Adding 2 widgets into gtk with python

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.

Categories