This is about moving a label on a canvas in Python 3.7 (I think) using tkinter.
There are lots of answers on Google and here on this site, but none of them have answered my question and yet I can't imagine my code couldbe much simpler. I'm not terribly experienced when it comes to Python and clearly I'm doing something fundamentally wrong.
What I want to do: move a label to a position on a canvas dictated by the click of a mouse.
Here we are:
root = tk.Tk()
root.geometry("800x600")
def ClickedCallback(event):
#print(f'Event: {event}, l2: {l2}, tags: {canvas.gettags()}')
canvas.move(l2, 5, 0)
canvas = tk.Canvas(root, width=1000, height=600, bg='blue')
canvas.bind("<Button-1>", ClickedCallback)
canvas.place(relwidth=0.9, relheight=0.8, relx = 0.05, rely = 0.05)
l2 = tk.Label(canvas, bg='red')
l2.bind("<Button-1>", ClickedCallback)
l2.place(relx=0.2, rely=0.2)
l2['text'] = "Test"
root.mainloop()
Now that causes a straight up error. "line 2591, in move
self.tk.call((self._w, 'move') + args)
_tkinter.TclError: invalid boolean operator in tag search expression"
I then saw an example that showed the object name entered into the method as a string rather than the object itself. So the line became:
canvas.move('l2', 5, 0)
Now this doesn't cause an error. But it also doesn't do anything.
I don't even know if moving on a canvas is the best way to do this. I just want to move a label around inside the window and this seemed to be the one that appeared in searches.
To place widget (ie. Label) on Canvas you have to use
label_id = canvas.create_window(position, window=l2,...)
not place()/grid()/pack()
And later you use this label_id to move it.
Simple example - Label is moved when you click Canvas
import tkinter as tk
# --- functions ---
def clicked_callback(event):
#canvas.move(label_id, 5, 0)
canvas.coords(label_id, event.x, event.y)
# --- main ---
root = tk.Tk()
root.geometry("800x600")
canvas = tk.Canvas(root, width=1000, height=600, bg='blue')
canvas.bind("<Button-1>", clicked_callback)
canvas.place(relwidth=0.9, relheight=0.8, relx=0.05, rely=0.05)
l2 = tk.Label(canvas, text="Test", bg='red')
#l2.bind("<Button-1>", clicked_callback)
label_id = canvas.create_window((100, 100), window=l2)
root.mainloop()
Related
so i was making a Ping pong game, and i forgot something very important, adding text! so i searched up a tutorial, and i found NONE, looked at some of posts, NONE, so i decided, to ask the comunity once again, (because i am new at python) anyways, i need to put text inside of this code.
import tkinter as tk
class Main:
def __init__ (self,root):
self.root = root
self.root.title("Ping Pong in Python")
self.root.geometry("2000x2000")
self.canvas = tk.Canvas(root, width=400, height=400, background="red")
self.canvas.pack(fill="both", expand=True)
if __name__ == '__main__':
root = tk.Tk()
obj = Main(root)
root.mainloop()
i tried searching up at youtube, found NONE, which i expected, because nowadays people only explain what they think is useful at youtube, so i searched up at stack overflow (this website) which i found one, but was closed, and didn't even work like last time! so i expected this again, so i decided to ask the comunity once again.
Google is your best friend. "Tkinter Text" lead to https://www.tutorialspoint.com/python/tk_text.htm
import tkinter as tk
class Main:
def __init__ (self,root):
self.root = root
self.root.title("Ping Pong in Python")
self.root.geometry("2000x2000")
self.canvas = tk.Canvas(root, width=400, height=400, background="red")
self.canvas.pack(fill="both", expand=True)
# Add text
self.text = tk.Label(self.root, text="Ping Pong text")
self.text.pack()
self.text.place(relx=0.5, rely=0.1, anchor=tk.N)
if __name__ == '__main__':
root = tk.Tk()
obj = Main(root)
root.mainloop()
The "relx" parameter specifies the horizontal position of the text (0 being the left edge and 1 being the right edge). The "rely" parameter specifies the vertical position of the text (0 being the top edge and 1 being the bottom edge). The "anchor" parameter specifies which point of the text widget should be placed at the specified position. For example, tk.N specifies that the top of the text widget should be placed at the top of the window.
Edit: In line 9, just add parameter bg='red' in tk.Label widget.
for some reason, it has a background that isn't the same color as the
background, is there a solution for that?
You don't need place().
Code:
import tkinter as tk
class Main:
def __init__ (self,root):
self.root = root
self.root.title("Ping Pong in Python")
self.root.geometry("200x200")
self.canvas = tk.Canvas(self.root, width=400, height=400, background="red")
self.canvas.pack(fill="both", expand=True)
# Add text
self.text = tk.Label(self.canvas, text="Ping Pong text", background="red")
self.text.pack(padx=5, pady=5, anchor=tk.N)
if __name__ == '__main__':
root = tk.Tk()
obj = Main(root)
root.mainloop()
Output:
I'm making a very simple python programming using tkinter. I want to draw some rectangles on a canvas and then when one clicks on a certain rectangle, show the tags of that rectangle. I can't get it to work. The problem seems to be that wherever I click on the canvas, the function get_closest returns 1. Any help is appreciated. This is my first time working with tkinter (and python for that matter), so any remarks about my code that aren't linked to the problems itself, are welcome as well!
import tkinter as tk
myrecs = [[None for j in range(4)] for i in range(4)]
class application:
def __init__(self, parent):
self.parent = parent
self.frame = tk.Frame(self.parent)
self.frame.grid(row=0)
self.quitbutton = tk.Button(self.frame, text = "Quit", command = lambda:quit())
self.quitbutton.grid(row=0, column = 0, sticky=tk.W + tk.E)
self.canvas = tk.Canvas(self.frame, width=200, height=200, bg = "blue")
self.canvas.bind("<ButtonPress-1>", self.buttonclick)
self.canvas.grid(row=1, columnspan = 2)
self.tag = self.canvas.create_text(10, 150, text="", anchor="nw")
self.makebutton = tk.Button(self.frame, text = "Make nice canvas", command = self.makecanvas)
self.makebutton.grid(row=0, column = 1, sticky = tk.W + tk.E)
def makecanvas(self):
for i in range(4):
for j in range(4):
myrecs[i][j] = self.canvas.create_rectangle(20*i, 20*j, 20*(i+1), 20*(j+1), tags=("rectangle", "i"+str(i), "j"+str(j)))
def buttonclick(self, event):
cnv = self.canvas
item = cnv.find_closest(cnv.canvasx(event.x), cnv.canvasy(event.y))[0]
tags = cnv.gettags(item)
cnv.itemconfigure(self.tag, text=tags[0])
if __name__ == "__main__":
root = tk.Tk()
root.title("Test")
app = application(root)
root.mainloop()
find_closest returns 1 means it's finding the very first element you created within the canvas, which in this case is the create_text.
Oddly enough, when you create_text with text="" it seems to be overtaking all your other elements. With a simple fix of text=" " it will now locate the closet rectangles on clicks.
With the other elements, when you assign option="" it actually disables (as far as I know) the option so instead of using its default values, you are actively telling tcl interpreter to not use it. This can be observed in other elements like create_rectangle(..., outline="") in which the default outline="black" will no longer apply, and you won't even get an outline. I have a feeling text="" yield a similar effect and for some reason basically covers the entire canvas area, so it causes find_closest to always return that element. Perhaps if you're lucky #BryanOakley (a tcl expert) can chime in on the backend reasoning.
In fact, if you tried find_above(item) you will notice that the text is consistently below your other elements drawn afterwards.
In short:
# Change this:
self.tag = self.canvas.create_text(10, 150, text="", anchor="nw")
# To this:
self.tag = self.canvas.create_text(10, 150, text=" ", anchor="nw")
Intitially when the frame and canvas size were smaller both scrollbars worked fine. After I increased their size to match that of the Toplevel, the x scrollbar disappear and the y one stopped working.
You may say I don't need the x scrollbar, because I adjusted the canvas create_text width attribute to fit the text within the window, and you'd be right, but I am trying to learn how to use scrollbars when the window's maximize button is on and when it's off.
The files I load to read are pretty lengthy, so I need the y scrollbar to scroll all the way to the end of the text. Checking some online notes on the scrollregion, I came a cross one that suggested using n,e,w,s coordintates, but when I use them, I get errores. I used scrollregion=(0,0,500,500) but that seems too finite to me.
from tkinter import *
from tkinter import font
newline=''
fileContent=[]
filePath='file.txt'
lines=open(filePath)
newline=lines.read()
w=Tk()
def openViewer():
pop = Toplevel(w)
pop.title('Operation Report')
pop.geometry("750x500+15+20")
pop.state('zoomed') # Window's Miximize button
frame=Frame(pop,width=780,height=560)
frame.grid(row=0,column=0)
canvas=Canvas(frame, width=780, height=560, background='black')
canvas.config(scrollregion=(0,0,500,500))
canvas.pack(side=LEFT, fill=BOTH)
verdana_font = font.Font(family = "Verdana",size = 13)
canvas.create_text((30, 0), anchor='nw', text=newline,
font=verdana_font, fill = 'light grey',
justify=LEFT, width=750)
hbar=Scrollbar(frame,orient=HORIZONTAL)
hbar.pack(side=BOTTOM,fill=X)
hbar.config(command=canvas.xview)
vbar=Scrollbar(frame,orient=VERTICAL)
vbar.pack(side=RIGHT,fill=Y)
vbar.config(command=canvas.yview)
canvas.config(xscrollcommand=hbar.set, yscrollcommand=vbar.set)
btn=Button(w, text='View Content', command=openViewer)
btn.pack()
w.mainloop()
from tkinter import *
from tkinter import font
newline=''
fileContent=[]
filePath='C:/path to/ file.txt'
lines=open(filePath)
newline=lines.read()
w=Tk()
def openViewer():
pop = Toplevel(w)
pop.title('Operation Report')
pop.geometry("750x500+15+20")
pop.state('zoomed') # Window's Miximize button
frame=Frame(pop,width=780,height=560)
frame.grid(row=0,column=0)
# I decreased the canvas height to show the x scrollbar and removed
# the create_text method width attribute to unwrap the text and
# activate the horizontal scrollbar
canvas=Canvas(frame, width=780,height=530, background='black')
verdana_font = font.Font(family = "Verdana",size = 13)
canvas.create_text((0, 0), anchor='nw', text=newline,
font=verdana_font, fill = 'light grey',
justify=LEFT) # Add this width=750 to wrap text
hbar=Scrollbar(frame,orient=HORIZONTAL)
hbar.pack(side=BOTTOM,fill=X)
hbar.config(command=canvas.xview)
vbar=Scrollbar(frame,orient=VERTICAL)
vbar.pack(side=RIGHT,fill=Y)
vbar.config(command=canvas.yview)
canvas.config(scrollregion=canvas.bbox(ALL))
canvas.config(xscrollcommand=hbar.set, yscrollcommand=vbar.set)
canvas.pack(side=LEFT, expand=True, fill=BOTH)
btn=Button(w, text='View Content', command=openViewer)
btn.pack()
w.mainloop()
Hopefully, this may help someone else with the same or similar problem. :)
So, this is a simplified form of the problem I'm facing:
The following code produces a canvas whose size is just big enough to accomidate the label, even though it was configured to be 400 by 400
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root, width=400, height=400)
canvas.pack()
label = tk.Label(canvas, text='foo')
label.pack()
If the last two lines are omitted, then the canvas is the size that I would expect it to be (400 by 400), but it does not contain the label, which is something that I want it to contain.
As I said, this is a simplified form of my problem, I actually want to put several widgets into the canvas, but this behavior is the cause of my problem. Is there a way to make the canvas the size that I want (in pixels) and use it as the master for other widgets?
What you can do is setting the size of the root window:
import tkinter as tk
root = tk.Tk()
root.geometry('400x400')
canvas = tk.Canvas(root)
canvas.pack()
label = tk.Label(canvas, text='foo')
label.pack()
root.mainloop()
If you can use the grid method, using row/column configure and sticky argument will do the rest of the job. I don't know pack enough.
You could also do this :
root = tk.Tk()
root.wm_minsize(400,400)
label = tk.Label(root, text='foo')
label.pack()
But you would loose the ability to resize the window under 400x400.
There is also this possibility but i'm not sure that it works:
root = tk.Tk()
root.resizable(width=False, height=False)
label = tk.Label(canvas, text='foo')
label.pack()
I'm trying to change the layering of Tkinter Canvas widgets. With most widgets you can force the widget above other widgets by using the lift method. However, if I try the same on a Canvas widget I get an error.
Error :
TypeError: tag_raise() got an unexpected keyword argument 'aboveThis'
An Example of my Problem :
import Tkinter as Tk
root = Tk.Tk()
w, h = 200, 200
a = Tk.Canvas(root, bg='red', width=w, height=h)
a.grid(column=0, row=0)
b = Tk.Canvas(root, bg='blue', width=w, height=h)
b.grid(column=0, row=0)
a.lift(aboveThis=None)
root.mainloop()
If I do the same thing with Frame widgets, it works.
Example:
import Tkinter as Tk
root = Tk.Tk()
w, h = 200, 200
a = Tk.Frame(root, bg='red', width=w, height=h)
a.grid(column=0, row=0)
b = Tk.Frame(root, bg='blue', width=w, height=h)
b.grid(column=0, row=0)
a.lift(aboveThis=None)
root.mainloop()
The canvas lift() method is an alias for tag_raise(), which is used to raise not the canvas itself but entities within the canvas.
I found this comment within the Tkinter.py source code:
# lower, tkraise/lift hide Misc.lower, Misc.tkraise/lift,
# so the preferred name for them is tag_lower, tag_raise
# (similar to tag_bind, and similar to the Text widget);
# unfortunately can't delete the old ones yet (maybe in 1.6)
If you replace a.lift(aboveThis=None) with Tk.Misc.lift(a, aboveThis=None) then the canvas widget is raised correctly.
I came to this question because I was actually wanting to implement the equivalent of a tk statement like
canvas-pathName raise tagOrId ?aboveThis?
in order to raise individual canvas items to a particular z-position. For those interested in the same thing, I'll just post my realization (after a little head-scratching) that this can be done in Python quite easily:
canvasObject.tag_raise(tagOrId, tagOrId2)
The second argument here just gets incorporated into the tk command line, and is then interpreted as an "aboveThis" value.