Python Tkinter: Canvas scrolling with MouseWheel - python

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

Related

Tkinter button highlight feature stops working after command is called

I have been working on my first GUI in tkinter - I am using Windows. My goal right now is to have buttons that accomplish these goals:
The buttons are highlighted when moused over.
The button remains highlighted if clicked.
Only one button can be "selected" (click-highlighted) at a time.
I initially thought that I had accomplished this! But I realize now that my work is not complete.
Here is what I am seeing:
I mouse over button A. It becomes highlighted! (GOOD)
I click on button A. It stays highlighted! (GOOD)
I mouse over button B. It becomes highlighted! (GOOD)
I click on button B. It stays highlighted! The highlight from A is removed! (GOOD)
I mouse over button A. It does not highlight. (BAD)
I am calling the default_coloring class function on button A when I click on button B. However, this appears to turn off the highlighting functions of button A, and the button no longer functions correctly according to the three rules I listed at the top.
How do I ensure that the buttons continue to function normally, even after the command is called? Am I approaching this the wrong way?
import tkinter as tk
blue = '#0000BB'
white = '#FFFFFF'
class HoverButton(tk.Button):
def __init__(self, master, position = None, **kw):
tk.Button.__init__(self,master=master,**kw)
self.defaultBackground = self["background"]
self.defaultForeground = self["foreground"]
self.bind("<Enter>", self.on_enter)
self.bind("<Leave>", self.on_leave)
self.bind("<Button-1>", self.hover_click)
self.state = 0
self.position = position
def on_enter(self, e):
if self.state == 0:
self['background'] = self['activebackground']
self['foreground'] = self['activeforeground']
def on_leave(self, e):
if self.state == 2:
self.state = 0
if self.state == 0:
self['background'] = self.defaultBackground
self['foreground'] = self.defaultForeground
def hover_click(self, e):
self.state += 1
self.state = self.state % 3
if self.state == 2:
self['background'] = self.defaultBackground
self['foreground'] = self.defaultForeground
def default_coloring(self):
self['background'] = self.defaultBackground
self['foreground'] = self.defaultForeground
class AddOnFrame(tk.Frame):
def __init__(self, master):
self.selectedbutton = None
super().__init__(master)
games = ['A','B','C']
self.objs = list()
self['bg'] = blue
for i in range(3):
self.objs.append(HoverButton(self,position = i, text = games[i].upper(), activebackground = white,activeforeground = blue,fg = white, bg = blue, borderwidth=0, relief = 'flat', highlightbackground = white))
self.objs[i]['command'] = lambda c=i: self._hover_button_clicked(self.objs[c])
self.objs[i].grid(row = i, column = 0, sticky = tk.W + tk.E)
self.blanklabel = tk.Label(self, text = '', background = white)
self.blanklabel.grid(row = 0, column = 1,rowspan = 10, sticky = tk.N + tk.E + tk.W + tk.S)
self.grid_columnconfigure(1, weight=1, minsize=10)
self.grid_columnconfigure(2, weight=1, minsize=500)
self.grid_columnconfigure(3, weight=1, minsize=500)
self.grid_columnconfigure(4, weight=1, minsize=500)
self.pack(expand = True)
def _hover_button_clicked(self, HoverButton):
self.lastbutton = self.selectedbutton
if self.lastbutton != None:
self.objs[self.lastbutton].default_coloring()
self.selectedbutton = HoverButton.position
window = tk.Tk()
window.geometry('1750x950')
window['bg'] = blue
window.title('Testing')
lf = AddOnFrame(window)
lf['bg'] = blue
window.mainloop()
I think I found the main source of the problem. When another button is clicked, you restore color of the last clicked button, but you do not reset its state. Change your default_coloring function to:
def default_coloring(self):
self.state = 0
self['background'] = self.defaultBackground
self['foreground'] = self.defaultForeground
But you should also prevent default_coloring if same button is pressed again:
def _hover_button_clicked(self, HoverButton):
self.lastbutton = self.selectedbutton
if (self.lastbutton != None) and (self.lastbutton != HoverButton.position):
self.objs[self.lastbutton].default_coloring()
self.selectedbutton = HoverButton.position
After cursory inspection, this sequence seems to be the problem:
When a button is clicked, the AddOnFrame._hover_button_clicked
method is invoked.
AddOnFrame.selectedbutton is initially None, which means the
if-statement in AddOnFrame._hover_button_clicked will not be
executed the first time. This is why the buttons seem to work the
first time you click them, but not after that.
However, the next time it is invoked (the next time a button is
pressed), AddOnFrame.selectedbutton is not None, and will never
be None again, meaning that from now on, every click will result in
a call to that HoverButton's default_coloring method.
default_coloring is invoked as soon as a button is clicked, which
results in a quick flash from the active color to the default color,
and the button does not stay highlighted.
The quick fix:
Basically, don't do the default_coloring stuff. It seems to be hurting you more than it's helping. Not really sure why you're doing it in the first place (all that stuff with setting the command, the lambda, the whole _hover_button_clicked method) since the buttons seem to be setting their colors back to the default just fine when on_leave or hover_click are invoked. You can fix your problem by changing the body of your HoverButton.default_coloring function to this:
def default_coloring(self):
return
The real fix would be some restructuring of your code.
EDIT I'm offering this to help you simplify things:
import tkinter as tk
colors = {
"white": "#FFFFFF",
"blue": "#0000BB"
}
class HoverButton(tk.Button):
def __init__(self, *args, **kwargs):
tk.Button.__init__(self, *args, **kwargs)
self.is_selected = False
self.is_highlighted = False
self["borderwidth"] = 0
self["relief"] = tk.FLAT
self["font"] = ("United Sans Cd Bk", 30)
self["activeforeground"] = colors["blue"]
self["activebackground"] = colors["white"]
self["highlightbackground"] = colors["white"]
self.recolor()
self.bind("<Enter>", self.on_enter)
self.bind("<Leave>", self.on_leave)
self.bind("<Button-1>", self.on_click)
def recolor(self):
self["background"] = [colors["blue"], colors["white"]][self.is_highlighted]
self["foreground"] = [colors["white"], colors["blue"]][self.is_highlighted]
def on_enter(self, *args):
self.is_highlighted = True
self.recolor()
def on_leave(self, *args):
if self.is_selected:
return
self.is_highlighted = False
self.recolor()
def on_click(self, *args):
self.is_selected = not self.is_selected
class Application(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title("Window")
self.geometry("256x256")
self.resizable(width=False, height=False)
self["background"] = colors["blue"]
button_labels = ["A", "B", "C"]
self.buttons = []
for row, button_label in enumerate(button_labels):
button = HoverButton(text=button_label)
button.grid(row=row, column=0, sticky=tk.W)
self.buttons.append(button)
def main():
application = Application()
application.mainloop()
return 0
if __name__ == "__main__":
import sys
sys.exit(main())

Where to place mainloop call in my tkinter app code?

I've been trying to figure out how to use how to place a mainloop() in my GUI so I don't need to have a while(True) anymore.
At the bottom of my code is an example of how I've been using this code. I know this is a lot of code to go through, so any help is appreciated
# Imports
import os
import os.path
import sys
import tkinter
tk = tkinter
from tkinter import font
#----- Set flag for JPEG support ---
noJPEG = False
try:
from PIL import Image
Pimg = Image
from PIL import ImageTk
Pimgtk = ImageTk
from PIL import ImageDraw
except ImportError:
noJPEG = True
#-----------------------------------
#
# Create an invisible global parent window to hold all children.
# Allows for easy closing of all windows by mouse click. If not
# closed programmatically, program exits if all windows are
# manually closed.
_root = tk.Tk()
_root.withdraw()
###
class ImageView(tk.Canvas):
def __init__(self, image, title, lotDiogram):
is_Diogram = lotDiogram
master = tk.Toplevel(_root) #sets master as a subwindow of _root
master.protocol("WM_DELETE_WINDOW", self.close)
if(is_Diogram == True):
"""When 'is_Diogram' is True, it means image is a
parking lot diogram, and does need scrollbars
added to its Toplevel"""
tk.Canvas.__init__(self, master,
width = 650, height = 525,
scrollregion=(0,0,image.getWidth(),
image.getHeight()))
self.master.title(title)
# Scrollbar added to master
vsb = tk.Scrollbar(master, orient="vertical", command=self.yview)
hsb = tk.Scrollbar(master, orient="horizontal", command=self.xview)
self.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)
self.grid(row=0, column=0, sticky="nswe")
vsb.grid(row=0, column=1, sticky="ns")
hsb.grid(row=1, column=0, sticky="ew")
master.grid_rowconfigure(0, weight=1)
master.grid_columnconfigure(0, weight=1)
master.minsize(width=600, height=500)
master.maxsize(width = image.getWidth(),
height = image.getHeight() )
self.foreground = "black"
self.image = image
self.height = image.getHeight()
self.width = image.getWidth()
self.mouseX = None
self.mouseY = None
self.bind("<Button-1>", self.onClick)
self.tags = None
_root.update() #redraw global window
else:
"""When 'is_Diogram' is False, it means image is a
lot sign, and does not need scrollbars
added to its Toplevel"""
tk.Canvas.__init__(self, master, width = image.getWidth(),
height = image.getHeight() )
self.master.title(title)
self.pack()
master.resizable(0,0)
self.foreground = "black"
self.image = image
self.height = image.getHeight()
self.width = image.getWidth()
self.mouseX = None
self.mouseY = None
self.bind("<Button-1>", self.onClick)
self.tags = None
_root.update() #redraw global window
def close(self):
"""Close a window."""
self.master.destroy()
self.quit()
_root.update()
def getMouseXY(self):
"""Return a tuple with x,y position in the image of the
mouse click."""
self.mouseX = None
self.mouseY = None
while (self.mouseX == None) or (self.mouseY == None) :
self.update()
return ((self.mouseX,self.mouseY))
def onClick(self, event):
"""Perform various functions when mouse is clicked."""
self.mouseX = int(self.canvasx(event.x))
self.mouseY = int(self.canvasy(event.y))
def drawShape(self, shape, tagz=None, txt=None, tfont="TkDefaultFont",
color1=None, color2=None, coords=None, w=None, OL=None):
"""Draws a shape, assigns it a tag, and binds the tag to a
mouse click."""
self.tags = tagz
if shape == 'rectangle':
"""Only will accept 2 pairs of XY coordinates, X0, Y0, X1, Y1)"""
self.create_rectangle(coords, fill = color1,
activefill = color2, tags = tagz)
elif shape == 'polygon':
"""Polygon will accept a minimum of 4 XY coordinate pairs"""
self.create_polygon(coords, fill = color1,
activefill = color2, outline = OL,
tags = tagz)
else: #should never get here since shape is required
print("No shape specified!")
_root.update() #redraw global window
def getTags(self):
self.getMouseXY()
obj = self.find_closest(self.mouseX, self.mouseY)
return self.gettags(obj)
def getHeight(self):
"""Return the height of the window."""
return self.height
def getWidth(self):
"""Return the width of the window."""
return self.width
class imagesMSUError(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
#------------------------------------------------------
class Image(object):
def __init__(self, file_or_type, *args):
global noJPEG
try:
if type(file_or_type) != str:
raise imagesMSUError(str(file_or_type))
except imagesMSUError as e:
print('imagesMSU_Error: "' + e.value + '" is not a '
+ 'valid image type or a valid GIF/JPG filename.')
sys.exit(1)
# If an image type was passed in, create a blank image
# of that type.
self.type = file_or_type.upper()
if self.type == 'GIF' or self.type == 'JPG':
# Create blank image; *args are width, height.
self.width, self.height = args
self.filename = 'blank' #default filename for saving
if self.type == 'GIF': #create blank gif
self.image = tk.PhotoImage(master =_root,
width = self.width,
height = self.height)
if self.type == 'JPG': #create blank jpg
try:
if noJPEG: #libjpeg not installed
raise imagesMSUError(noJPEG)
except imagesMSUError as e:
print('imagesMSU_Error: Support library for JPEGs '
+ 'not found. Use GIFs instead.')
sys.exit(1)
else:
self.image = Pimg.new(mode = "RGB",
size = (self.width, self.height))
else: #A filename was passed in. Validate then load into an image.
# Check for valid image type
self.type = file_or_type[-3:].upper() #file's 3 char extension
try:
if self.type != 'GIF' and self.type != 'JPG': #wrong extension
raise imagesMSUError(self.type)
except imagesMSUError as e:
print('imagesMSUError: "' + e.value
+ '" is not a valid image type.')
sys.exit(1)
# Check for a valid file
filename = file_or_type
try:
if not os.path.isfile(filename): #not a file or not found
raise imagesMSUError(filename)
except imagesMSUError as e:
print('imagesMSU_Error: File "' + e.value + '" not found.')
sys.exit(1)
if self.type == 'GIF':
self.image = tk.PhotoImage(file = filename, master = _root)
self.width = self.image.width()
self.height = self.image.height()
if self.type == 'JPG':
try:
if noJPEG: #libjpeg not installed
raise imagesMSUError(noJPEG)
except imagesMSUError as e:
print('imagesMSU_Error: Support library for JPEGs '
+ 'not found. Use GIFs instead.')
sys.exit(1)
else:
self.image = Pimg.open(filename)
box = self.image.getbbox()
self.width = box[2]
self.height = box[3]
def getType(self):
"""Returns the image type."""
return self.type
def getWidth(self):
"""Returns the width of the image in pixels."""
return self.width
def getHeight(self):
"""Returns the height of the image in pixels."""
return self.height
def draw(self, win):
"""Creates and opens a window on an image. The user must close
the window to return control to the caller."""
self.canvas = win
if self.type == 'GIF':
self.canvas.create_image(self.width // 2,
self.height // 2,
image = self.image)
if self.type == 'JPG':
self.photoImage = Pimgtk.PhotoImage(self.image)
self.canvas.create_image(self.width // 2,
self.height // 2,
image = self.photoImage)
# Update the hidden root window to draw the image.
_root.update()
#-------------Example program----------------------------#
def main():
# Load an image
img1 = Image('BURG.jpg')
# Draw the image in a window.
window1 = ImageView(img1, "Burg Lot", True)
img1.draw(window1)
window1.drawShape('rectangle', tagz="Handicap", coords=[391, 214, 429, 235],
color1=None, color2='red')
window1.drawShape('rectangle', tagz="Lot_Sign", coords=[486, 375, 509, 389],
color1=None, color2='red')
# Loop to click parking spots and display parking sign
while(True):
tags = window1.getTags()
for tag in tags:
if tag != 'current':
if tag == 'Handicap':
img2 = Image('BUR HC.jpg')
window2 = ImageView(img2, "Handicap", False)
img2.draw(window2)
if tag == 'Lot_Sign':
img2 = Image('Lot Sign.jpg')
window2 = ImageView(img2, "Lot Info", False)
img2.draw(window2)
if __name__ == '__main__':
main()
If I understand your question correctly, I would do the main-loop as:
https://docs.python.org/3/library/tkinter.html#a-simple-hello-world-program
and then refactor a bit.
Your current for tag in tags-loop can be done inside drawShape where you set the tags anyway. This will convert your program to be event driven.

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__)

Animated Tkinter

I am trying to write a basic tkinter example that will show a box stretching across a frame. At this point the code below will only print the final result, and not show the box moving. How do I fix the code so that it will work over time with out using something like move, so that I can modify the shape over time later?
from tkinter import Tk, Canvas, Frame, BOTH
from time import sleep
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.parent.title("Board")
self.pack(fill=BOTH, expand=1)
self.canvas = Canvas(self)
self.ctr = 10
self.initUI()
def initUI(self):
print(self.ctr)
#The first four parameters are the x,y coordinates of the two bounding points.
#The top-left and the bottom-right.
r = self.canvas.create_rectangle((self.ctr * 10), 0, (self.ctr * 10 + 50), 50,
outline="#fb0", fill="#fb0")
'''
canvas.create_rectangle(50, 0, 100, 50,
outline="#f50", fill="#f50")
canvas.create_rectangle(100, 0, 150, 50,
outline="#05f", fill="#05f")
'''
self.canvas.pack(fill=BOTH, expand=1)
if self.ctr > 0:
self.updateUI()
def updateUI(self):
self.ctr -= 1
sleep(1)
self.initUI()
def main():
root = Tk()
root.geometry("400x100+300+300")
ex = Example(root)
root.mainloop()
if __name__ == '__main__':
main()
This should get you partway there (you'll need to fix the indenting, the offsets are not correct and the counter doesn't get reset). In future, make sure you don't call sleep when you are using the event loop of a GUI for example. Most GUI's have a method to hook something into their event loops (the root.after call in this case). All I've done is make your code work partially - this should not be seen as indicative of idiomatic python.
from tkinter import Tk, Canvas, Frame, BOTH
from time import sleep
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.parent.title("Board")
self.pack(fill=BOTH, expand=1)
self.canvas = Canvas(self)
self.ctr = 10
def initUI(self):
print(self.ctr)
#The first four parameters are the x,y coordinates of the two bounding points.
#The top-left and the bottom-right.
r = self.canvas.create_rectangle((self.ctr * 10), 0, (self.ctr * 10 + 50), 50,
outline="#fb0", fill="#fb0")
'''
canvas.create_rectangle(50, 0, 100, 50,
outline="#f50", fill="#f50")
canvas.create_rectangle(100, 0, 150, 50,
outline="#05f", fill="#05f")
'''
self.canvas.pack(fill=BOTH, expand=1)
self.ctr += 1
if self.ctr > 0:
self.parent.after(1000, self.initUI)
def main():
root = Tk()
ex = Example(root)
root.geometry("400x100+300+300")
root.after(1000, ex.initUI)
root.mainloop()
if __name__ == '__main__':
main()

Categories