I want to draw a line between two labels on a canvas, but I do not know how to get the label coordinates to do this - I need to generate a large and variable number of labels each time, which I position on the canvas using pack() or grid.
An example of my code is as below:
import tkinter as tk
master = tk.Tk()
def line(x1, y1, x2, y2):
w.create_line(x1, y1, x2, y2)
label1 = tk.Label(master, text="ONE", bg="red", fg="white")
label1.pack(side="left")
label2 = tk.Label(master, text="TWO", bg="blue", fg="white")
label2.pack(side="right")
w = tk.Canvas(master, width=800, height=100)
w.pack()
line(label1.winfo_rootx(), label1.winfo_rooty(),
label2.winfo_rootx(), label2.winfo_rooty())
master.mainloop()
The above code does not generate any line, but it does display the labels. How can I get the correct coordinates for these labels so I can then use them in the line method to actually draw a line connecting the two labels?
I found I can do what I need done (i.e. draw connecting lines between labels generated on the canvas) by using place geometry instead of pack. For a large number of automatically generated labels, this means using a small method to generate the placement coordinates, but once those sets of coordinates are generated, then joining the labels is easy.
For example:
label1 = tk.Label(master, text="ONE", bg="red", fg="white")
label1.place(x=50, y=80)
label2 = tk.Label(master, text="TWO", bg="blue", fg="white")
label2.place(x=720, y=80)
line(50, 80, 720, 80)
Another way to do this is to generate canvas text objects instead of labels. So this would be like follows:
import tkinter as tk
master = tk.Tk()
def line(x1, y1, x2, y2):
print(x1, y1, x2, y2)
w.create_line(x1, y1, x2, y2, fill="green")
w = tk.Canvas(master, width=800, height=100)
w.pack()
l1 = w.create_text(50, 20, text="ONE", fill="red", tag="l1")
l2 = w.create_text(720, 20, text="TWO", fill="blue", tag="l2")
x1 = w.coords(l1)[0] + 20
y1 = w.coords(l1)[1]
x2 = w.coords(l2)[0] - 20
y2 = w.coords(l2)[1]
line(x1, y1, x2, y2)
Related
from tkinter import Tk, Canvas, Frame, BOTH
from tkinter import *
from tkinter import Tk
#Welcome screen
root = Tk()
root.configure(bg = "steel blue")
canvas = Canvas(root, bg = "steel blue", highlightthickness = 0)
canvas.config(width = 350, height = 250)
#canvas.config(width = root.winfo_screenwidth(), height = root.winfo_screenheight() )
canvas.pack()
#Making of the round rectangle
class Rectangle:
def round_rectangle(x1, y1, x2, y2, radius=25, **kwargs):
points = [x1+radius, y1,
x1+radius, y1,
x2-radius, y1,
x2-radius, y1,
x2, y1,
x2, y1+radius,
x2, y1+radius,
x2, y2-radius,
x2, y2-radius,
x2, y2,
x2-radius, y2,
x2-radius, y2,
x1+radius, y2,
x1+radius, y2,
x1, y2,
x1, y2-radius,
x1, y2-radius,
x1, y1+radius,
x1, y1+radius,
x1, y1]
return canvas.create_polygon(points, **kwargs, smooth=True)
my_rectangle = round_rectangle(10, 60, 330, 250, radius=20, outline = "black", fill="white")
canvas.place(relx=0.5, rely=0.5,anchor=CENTER)
def UserIdentification():
newWindow = Toplevel(root)
Next_button=Button(root,text = "Next", anchor = W, command = UserIdentification)
Next_button.configure(width = 10, bg = "black", fg = "blue" ,borderwidth = 4)
Next_button = canvas.create_window(150, 200, anchor=NW, window=Next_button)
canvas.create_text(170, 100, text = "Hey there, welcome to DeliverToday! \nWe bring your needs right at your doorstep ~ \nhave a great journey ahead!")
root.mainloop()
Hi, so the code above works completely but I am confused on how to make the "Next" button create a new blank screen(which I will design later but blank for now to make it less complicated). I cant use top-level because I don't want a whole new window to open up but I want it to open in the same window (one screen to exit and another to open). How do I do that with a very easy code?? (the button is on a canvas)
You can delete everything on the canvas with the delete method. If you pass it the literal string "all" it will delete everything on the canvas.
def UserIdentification():
canvas.delete("all")
I've been working on a chatbot in tkinter and have been using this, https://stackoverflow.com/a/56032923/12406236 as a starting point for my code. I want to be able to add more speech bubbles (I can only make three) and then scroll through them.
How would I be able to accomplish this?
My code:
from tkinter import *
root = Tk()
frame = Frame(root,width=200,height=200)
frame.grid(row=0,column=0,columnspan=2)
canvas = Canvas(frame,bg="white",width=300,height=300,scrollregion=(0,0,500,500))
canvas.pack(side=LEFT,expand=True,fill=BOTH)
vbar = Scrollbar(frame,orient="vertical",command=canvas.yview)
vbar.pack(side=RIGHT,fill=Y)
canvas.configure(yscrollcommand=vbar.set)
bubbles = []
class BotBubble:
def __init__(self,master,message=""):
self.master = master
self.frame = Frame(master,bg="light grey")
self.i = self.master.create_window(90,160,window=self.frame)
Label(self.frame, text=message,font=("Helvetica", 9),bg="light grey").grid(row=1,column=0)
root.update_idletasks()
self.master.create_polygon(self.draw_triangle(self.i),fill="light grey",outline="light grey")
def draw_triangle(self,widget):
x1, y1, x2, y2 = self.master.bbox(widget)
return x1, y2 - 8, x1 - 10, y2 + 5, x1, y2
def send_message(event):
if bubbles:
canvas.move(ALL, 0, -65)
a = BotBubble(canvas,message=entry.get())
bubbles.append(a)
entry = Entry(root,width=100)
entry.grid(row=1,column=0)
entry.bind("<Return>",send_message)
root.mainloop()
I have modified your code:
do not set scrollregion when creating canvas
update scrollregion of canvas after new bubble is added or window is resized
do not scroll up old bubbles, just add the new bubble below last bubble
Updated code:
from tkinter import *
root = Tk()
frame = Frame(root)
frame.pack(fill=BOTH, expand=1)
canvas = Canvas(frame, bg="white")
canvas.pack(side=LEFT, expand=True, fill=BOTH)
vbar = Scrollbar(frame, orient="vertical", command=canvas.yview)
vbar.pack(side=RIGHT, fill=Y)
canvas.configure(yscrollcommand=vbar.set)
bubbles = []
LINE_GAP = 10 # gap between bubbles
class BotBubble:
def __init__(self, master, message=""):
self.master = master
self.frame = Frame(master, bg="light grey")
bbox = self.master.bbox(ALL)
y = (bbox[3] if bbox else 0) + LINE_GAP
self.i = self.master.create_window(15, y, window=self.frame, anchor='nw')
Label(self.frame, text=message, font=("Helvetica",9), bg="light grey").grid(row=1, column=0)
self.master.update()
self.master.create_polygon(self.draw_triangle(self.i), fill="light grey", outline="light grey")
def draw_triangle(self,widget):
x1, y1, x2, y2 = self.master.bbox(widget)
return x1, y2-8, x1-10, y2+5, x1, y2
def update_scrollregion(event=None):
canvas.config(scrollregion=canvas.bbox(ALL))
canvas.yview_moveto(1) # scroll to bottom
def send_message(event):
a = BotBubble(canvas, message=entry.get())
bubbles.append(a)
update_scrollregion()
entry = Entry(root, width=100)
entry.pack(fill=X)
entry.bind("<Return>", send_message)
entry.focus_force()
frame.bind('<Configure>', update_scrollregion)
root.mainloop()
How can I create buttons on top of this canvas for each square each with their own unique value (starting from (1,1) in the bottom left corner)? I am trying to make a program to play chess with. I need these squares on the canvas to each have a button with a defined coordinate. This also could be changed to just 64 buttons but this is for a program where squares will be highlighted if they have the option to be moved to.
import tkinter as tk
class Layout(tk.Tk):
colours = ["#563a12", "#9f9362"]#square colours dark then light
def __init__(self, n=8):
super().__init__()
self.n = n
self.leftframe = tk.Frame(self)
self.leftframe.grid(row=0, column=0, rowspan=10, padx=100)
self.middleframe = tk.Frame(self)
self.middleframe.grid(row=0, column=8, rowspan=8)
self.canvas = tk.Canvas(self, width=1200, height=768, )
self.canvas.grid(row=0, column=1, columnspan=8, rowspan=8)
self.board = [[None for row in range(n)] for col in range(n)]
self.colourindex = 0
def changecolours(self):
self.colourindex = (self.colourindex + 1) % 2
def drawboard(self):
for col in range(self.n):
self.changecolours()
for row in range(self.n):
x1 = col * 90
y1 = (7-row) * 90
x2 = x1 + 90
y2 = y1 + 90
colour = self.colours[self.colourindex]
self.board[row][col] = self.canvas.create_rectangle(x1, y1, x2, y2, fill=colour)
self.changecolours()
board = Layout()
board.drawboard()
board.mainloop()
It looks like you just want the coordinates of the board tiles. If so you can tag each tile with a unique name and then bind them using canvas.tag_bind:
import tkinter as tk
class Layout(tk.Tk):
colours = ["#563a12", "#9f9362"]#square colours dark then light
def __init__(self, n=8):
super().__init__()
self.n = n
self.leftframe = tk.Frame(self)
self.leftframe.grid(row=0, column=0, rowspan=10, padx=100)
self.middleframe = tk.Frame(self)
self.middleframe.grid(row=0, column=8, rowspan=8)
self.canvas = tk.Canvas(self, width=1200, height=768, )
self.canvas.grid(row=0, column=1, columnspan=8, rowspan=8)
self.board = [[None for row in range(n)] for col in range(n)]
def drawboard(self):
from itertools import cycle
for col in range(self.n):
color = cycle(self.colours[::-1] if not col % 2 else self.colours)
for row in range(self.n):
x1 = col * 90
y1 = (7-row) * 90
x2 = x1 + 90
y2 = y1 + 90
self.board[row][col] = self.canvas.create_rectangle(x1, y1, x2, y2, fill=next(color), tags=f"tile{col+1}{row+1}")
self.canvas.tag_bind(f"tile{col+1}{row+1}","<Button-1>", lambda e, i=col+1, j=row+1: self.get_location(e,i,j))
def get_location(self, event, i, j):
print (i, j)
board = Layout()
board.drawboard()
board.mainloop()
I made a paint program, but I can not draw smoothly and save the images each time with a different names. Please help!
from tkinter import *
# by Canvas I can't save image, so i use PIL
import PIL
from PIL import Image, ImageDraw
def save():
filename = 'image.png'
image1.save(filename)
def paint(event):
x1, y1 = (event.x), (event.y)
x2, y2 = (event.x + 1), (event.y + 1)
cv.create_oval((x1, y1, x2, y2), fill='black', width=10)
# --- PIL
draw.line((x1, y1, x2, y2), fill='black', width=10)
root = Tk()
cv = Canvas(root, width=640, height=480, bg='white')
# --- PIL
image1 = PIL.Image.new('RGB', (640, 480), 'white')
draw = ImageDraw.Draw(image1)
# ----
cv.bind('<B1-Motion>', paint)
cv.pack(expand=YES, fill=BOTH)
btn_save = Button(text="save", command=save)
btn_save.pack()
root.mainloop()
You could use continuous drawing instead of drawing separate small circles.
The following example stores the last values of the position of the mouse to draw a line to the current value.
You need to click, and move the mouse to draw; release the click to stop.
The image name includes a number that is incremented by 1 each time you save; you can therefore save all the intermediate images as you draw the full picture.
from tkinter import *
import PIL
from PIL import Image, ImageDraw
def save():
global image_number
filename = f'image_{image_number}.png' # image_number increments by 1 at every save
image1.save(filename)
image_number += 1
def activate_paint(e):
global lastx, lasty
cv.bind('<B1-Motion>', paint)
lastx, lasty = e.x, e.y
def paint(e):
global lastx, lasty
x, y = e.x, e.y
cv.create_line((lastx, lasty, x, y), width=1)
# --- PIL
draw.line((lastx, lasty, x, y), fill='black', width=1)
lastx, lasty = x, y
root = Tk()
lastx, lasty = None, None
image_number = 0
cv = Canvas(root, width=640, height=480, bg='white')
# --- PIL
image1 = PIL.Image.new('RGB', (640, 480), 'white')
draw = ImageDraw.Draw(image1)
cv.bind('<1>', activate_paint)
cv.pack(expand=YES, fill=BOTH)
btn_save = Button(text="save", command=save)
btn_save.pack()
root.mainloop()
Allegedly not less terrible than yours, but the lines are continuous...
You can also use Save As dialog if you want to save image as other name and extension:
from tkinter import *
from tkinter.filedialog import asksaveasfilename as saveAs
import PIL
from PIL import Image, ImageDraw
def save():
filename=saveAs(title="Save image as...",filetype=(("PNG images","*.png"),("JPEG images","*.jpg"),("GIF images","*.gif")))
image1.save(filename)
def activate_paint(e):
global lastx, lasty
cv.bind('<B1-Motion>', paint)
lastx, lasty = e.x, e.y
def paint(e):
global lastx, lasty
x, y = e.x, e.y
cv.create_line((lastx, lasty, x, y), width=1)
draw.line((lastx, lasty, x, y), fill='black', width=1)
lastx, lasty = x, y
def clear():
cv.delete('all')
def exitt():
exit()
win = Tk()
win.title("Paint - made in Python")
win.iconbitmap('Paint.ico') # Delete this line if you don't have file "Paint.ico" in this folder
lastx, lasty = None, None
cv = Canvas(win, width=640, height=480, bg='white')
image1 = PIL.Image.new('RGB', (640, 480), 'white')
draw = ImageDraw.Draw(image1)
cv.bind('<1>', activate_paint)
cv.pack(expand=YES, fill=BOTH)
save_ = Button(text="Save image", command=save)
save_.pack()
reset=Button(text='Reset canvas',command=clear)
reset.pack(side=LEFT)
_exit=Button(text='Exit',command=exitt)
_exit.pack(side=RIGHT)
win.mainloop()
Paint.pyw - Drawing window
I have the following piece of code that takes an image within a canvas and then whenever I click the paint function draws a dot over it.
Everything is working fine except that the paint function is not working as expected.
Desirable output
Click event draws a dot. No need to drag the on click event
Actual output
I have to drag the on mouse click event to see a drawing on the canvas.
I guess there might be a slight problem with the paint function. However, I haven't been able to know what it is exactly.
from tkinter import *
from PIL import Image, ImageTk
class Main(object):
def __init__(self):
self.canvas = None
def main(self):
master = Tk()
# Right side of the screen / image holder
right_frame = Frame(master, width=500, height=500, cursor="dot")
right_frame.pack(side=LEFT)
# Retrieve image
image = Image.open("./image/demo.JPG")
image = image.resize((800, 700), Image.ANTIALIAS)
photo = ImageTk.PhotoImage(image)
# Create canvas
self.canvas = Canvas(right_frame, width=800, height=700)
self.canvas.create_image(0, 0, image=photo, anchor="nw")
self.canvas.pack()
self.canvas.bind("<B1-Motion>", self.paint)
mainloop()
def paint(self, event):
python_green = "#476042"
x1, y1 = (event.x - 1), (event.y - 1)
x2, y2 = (event.x + 1), (event.y + 1)
self.canvas.create_oval(x1, y1, x2, y2, fill=python_green, outline=python_green, width=10)
if __name__ == "__main__":
Main().main()
Fix:
Added these two methods:
def on_button_press(self, event):
self.x = event.x
self.y = event.y
def on_button_release(self, event):
python_green = "#476042"
x0,y0 = (self.x, self.y)
x1,y1 = (event.x, event.y)
changed canvas to this:
self.canvas.bind("<ButtonPress-1>", self.on_button_press)
self.canvas.bind("<ButtonRelease-1>", self.on_button_release)
When you only click and don't move the mouse, B1-Motion isn't triggering.
To bind to mouse press (as well as mouse moving), you can add self.canvas.bind("<ButtonPress-1>", self.paint) before the mainloop.