Tkinter: save coordinates of a drawn line of a given width - python

I have this code where the user can paint using his mouse:
from Tkinter import *
class Test:
def __init__(self):
self.b1="up"
self.xold=None
self.yold=None
def test(self,obj):
self.drawingArea=Canvas(obj)
self.drawingArea.pack()
self.drawingArea.bind("<Motion>",self.motion)
self.drawingArea.bind("<ButtonPress-1>",self.b1down)
self.drawingArea.bind("<ButtonRelease-1>",self.b1up)
def b1down(self,event):
self.b1="down"
def b1up(self,event):
self.b1="up"
self.xold=None
self.yold=None
def motion(self,event):
if self.b1=="down":
if self.xold is not None and self.yold is not None:
event.widget.create_line(self.xold,self.yold,event.x,event.y,fill="red",width=4,smooth=TRUE)
self.xold=event.x
self.yold=event.y
if __name__=="__main__":
root=Tk()
root.wm_title("Test")
v=Test()
v.test(root)
root.mainloop()
I wonder how to save the coordinates of the drawn line knowing that the thickness of the line is 4 (the width can be any integer number less than 10) ?
Without the thickness option, the answer is evident for me.
Thank you in advance.

You cannot get the information you want, if what you want is a list of all the pixels that get changed when you draw a wide line. The only information you get when creating a line on the canvas are the coordinates of the endpoints.
If the line is completely horizontal or vertical you can get the bounding box of the line, but that won't work for diagonal lines.

Related

how to measure the width of a tkinter canvas text

I was wondering how to measure the width of a text in tkinter canvas. I have a text displayed somewhere:
myCanvas.create_text(400,410, text="This is my text", tags="my_tag")
and at some point there is a shape (polygon line) that may overlap when the text gets longer (text will be changed with .itemconfigure()). In this case I wanted to break the text in lines, which works well with the width option.
To figure out if objects overlap there are a couple of possible methods: find_overlapping() or find_enclosed(), however, they do require 4 coordinates and myCanvas.coords("my_tag") only returns 2 coordinates. How could I figure out the other x2, y2?
An alternative was to use find_closest(), however, there are a bunch of shapes closer than the overlapping one, so that is not accurate.
Another alternative was that I could figure out the intersecting coordinate and then just take the distance to the x coordinate of the text and double that as a width (since text gets drawn from the center), for example:
intersection point at (350,405) => width = 2 * (400-350)
However, since the shape is a line (part of a polygon) that goes diagonal, I'm not sure how get the intersection point other than having a loop that creates a line that slowly increases till it overlaps with an object, for example:
text_center = (400,410)
start_x = text_center[0]
start_y = text_center[1]
extender = 1
while(myCanvas.find_overlapping(start_x, start_y, start_x+entender, start_y == ())
myCanvas.delete("mytestline")
extender += 1
myCanvas.create_line(start_x, start_y, start_x+extender, start_y, tags="mytestline")
overlapping_point = (start_x+extender, start_y)
Is there an easier way?
The bbox method will return the bounding box of any item or group of items on the canvas. The bounding box is the smallest rectangle that contains the elements, give or take a pixel or two. The returned value is a tuple of the two coordinates.
The following example creates a text item, gets and displays the bounding box, and also uses the coordinates to draw a rectangle around the text so you can visually see that the numbers are correct.
import tkinter as tk
root = tk.Tk()
myCanvas = tk.Canvas(root)
myCanvas.pack(fill="both", expand=True)
item_id = myCanvas.create_text(100,50, text="This is my text", tags="my_tag")
bbox = myCanvas.bbox(item_id)
myCanvas.create_rectangle(bbox, outline="red")
myCanvas.create_text(100, 70, text=f"bbox: {bbox}")
root.mainloop()

Button grid shrinking vertically when adding a certain amount of images

I'm making a chess board in Tkinter with chess piece images to get used to the module, and have success with placing the first N-1 images in a row in an NxN grid.
However, upon coding an Nth image on any row, the row in question shrinks vertically.
I'm trying to use an OOP approach and am thinking there may be a method i'm misusing or missing entirely.
I've tried adjusting the arguments for the grid() method such as row and column, but they don't appear to affect the result. Adjusting the width and height of the buttons did not do anything significant. I used a 4x3 grid as a testing example and the images fit with the grid if I leave an empty button without an image in a row.
import tkinter as tk
class ChessBoard(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self,parent,*args,**kwargs)
self.parent = parent
self.button_identities = [[[] for x in range(3)] for y in range(3)]
self.pictures = {"Rook":tk.PhotoImage(file="rook.png")}
self.initialize_board()
def initialize_board(self):
for r in range(3):
for c in range(3):
button = tk.Button(self.parent,bg="white",\
width=10,height=5)
button.grid(row=r,column=c,sticky="sewn")
self.button_identities[r][c] = button
self.setup_starting_positions()
def setup_starting_positions(self):
# Commenting out one of the lines in this method keeps the example row intact
# In this example I'm trying to make 3 images fit in a single row without the row shrinking
self.button_identities[0][0].config(image=self.pictures["Rook"])
self.button_identities[0][1].config(image=self.pictures["Rook"])
self.button_identities[0][2].config(image=self.pictures["Rook"])
class MainApplication(tk.Frame):
# The Main Application hosts the Chessboard frame
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self,parent,*args,**kwargs)
self.parent = parent
self.parent.geometry('{}x{}'.format(1000,800))
self.chessboard = ChessBoard(self)
self.chessboard.grid_propagate(False)
self.chessboard.grid(row=1,column=1,columnspan=3)
if __name__ == "__main__":
root = tk.Tk()
MainApplication(root).grid(row=0,column=0)
root.mainloop()
I expected the buttons to show some sign of changing when I tried editing the grid() methods or width/height arguments when initializing a Button, but nothing remarkable seemed to change. I feel as though I'm not understanding how the Grid manager works in this case. Sorry for the lack of images.
In empty Button (or with text) height=5 means 5 lines of text. When you put image then it means 5 pixels.
When you have empty button in row then it has size 5 lines and it is the highest widget in row so other buttons are resized to row size. When you add last button then all buttons has height 5 pixels and there is bigger widget to resize row.
So you have to set width and height in pixels and put all images.
Or simple remove width, height from buttons and they will use width, height of image.
button = tk.Button(self.parent, bg="white")
See: Button

Python Tkinter Rotate Window, turn to the right

Someone has written a really simple "paint" program in Python and I want to modify it a little bit. Some my question is. How can implement a rotate function to this program? I want to be able to rotate the window that the program is represented in 90 degrees to the right. Is this possible? Would it also be possible to implement a function to remove the border from the window. (It's the gui window I'm talking about).
from Tkinter import *
"""paint.py: not exactly a paint program.. just a smooth line drawing demo."""
b1 = "up"
xold, yold = None, None
def main():
root = Tk()
drawing_area = Canvas(root)
drawing_area.pack()
drawing_area.bind("<Motion>", motion)
drawing_area.bind("<ButtonPress-1>", b1down)
drawing_area.bind("<ButtonRelease-1>", b1up)
root.mainloop()
def b1down(event):
global b1
b1 = "down" # you only want to draw when the button is down
# because "Motion" events happen -all the time-
def b1up(event):
global b1, xold, yold
b1 = "up"
xold = None # reset the line when you let go of the button
yold = None
def motion(event):
if b1 == "down":
global xold, yold
if xold is not None and yold is not None:
event.widget.create_line(xold,yold,event.x,event.y,smooth=TRUE)
# here's where you draw it. smooth. neat.
xold = event.x
yold = event.y
if __name__ == "__main__":
main()
The solution was to just rotate the screen in Linux. I managed to do this with the command:
xrandr --output HDMI1 --rotate right
'Rotating' your screen, so that a different edge of the screen is treated as the top edge, is something that the operating system has to do. For instance, on Win 7, right click on screen, select Resolution, then select Orientation. The choices are Landscape (normal), Portrait (left edge, starting from Landscape) is top), Inverted Landscape, and Inverted Portrait. This works even if screen is not physically rotated -- but is a bit weird since cursor movement assumes that the screen is rotated.
Why exactly do you want to rotate the window? is it just because of the geometry? if so you could just resize the canvas to give the appearance of having been rotated using width and height arguments when it is created (or using configure on it after its been created)
and to remove the border (and the titlebar with it) you can use:
root.overrideredirect(True)
in your main()

Python GUI and TKinter Grid

I'm new to Python, and I'm trying to write a program that displays an 8x8 grid. The vertical lines are supposed to be red and the horizontal lines are supposed to be blue. But I can't seem to figure it out. I know it must be in a loop, but I'm not sure even where to start. Please help!
Here's my code so far:
from tkinter import *
class Canvas:
def __init__(self):
self.window = Tk()
self.window.title("Grid")
self.canvas = Canvas(window, width = 200, height = 200,
bg = "white")
self.canvas.pack()
def drawGrid(self):
self.canvas.create_line()
Thanks!
Take a look at http://effbot.org/tkinterbook/canvas.htm#Tkinter.Canvas.create_line-method for details about the create_line() method.
You need two arguments: the coordinates of the line and a fill color. The coordinates are a list of [x0, y0, x1, y1], and they correspond to pixel values with an origin at the top-left corner of the parent widget, so to draw a horizontal green line on your 200x200 Canvas, you'd write:
self.canvas.create_line(0,0,200,200, fill='green')
To create a grid of lines, a for or while loop would work, which modified the list of coordinates at every iteration and passed it to a new create_line() function at the end of each loop.
That should get you started.

Create a line using a list of coordinates in python GUI

I've been trying to create a graph using a create_line and a list of (x,y) points.
import Tkinter
Screen = [a list of screen coordinates]
World = []
for x,y in screen:
World.append(somefunctiontochange(x,y))
if len(World) >= 2:
Canvas.create_line(World)
The line doesn't show in my canvas though, and no error was given. Any help?
Took me a while but this is how you draw to a canvas in the way you want:
import Tkinter as tk
root = tk.Tk()
root.geometry("500x500")
root.title("Drawing lines to a canvas")
cv = tk.Canvas(root,height="500",width="500",bg="white")
cv.pack()
def linemaker(screen_points):
""" Function to take list of points and make them into lines
"""
is_first = True
# Set up some variables to hold x,y coods
x0 = y0 = 0
# Grab each pair of points from the input list
for (x,y) in screen_points:
# If its the first point in a set, set x0,y0 to the values
if is_first:
x0 = x
y0 = y
is_first = False
else:
# If its not the fist point yeild previous pair and current pair
yield x0,y0,x,y
# Set current x,y to start coords of next line
x0,y0 = x,y
list_of_screen_coods = [(50,250),(150,100),(250,250),(350,100)]
for (x0,y0,x1,y1) in linemaker(list_of_screen_coods):
cv.create_line(x0,y0,x1,y1, width=1,fill="red")
root.mainloop()
You need to supply create_line with the x,y positions at the start and end point of the line, in the example code above (works) I'm drawing four lines connecting points (50,250),(150,100),(250,250),(350,100) in a zigzag line
Its worth pointing out also that the x,y coords on a canvas start at the top left rather than the bottom left, think of it less like a graph with the x,y = 0,0 in the bottom left of the canvas and more how you would print to a page starting in top left corner moving to the right in the x and with the y incrementing as you move down the page.
I used:
http://www.tutorialspoint.com/python/tk_canvas.htm as reference.
If you aren't getting errors and you're certain your function is being called, you probably have one of three problems:
Is your canvas visible? A common mistake for beginners is to either forget to pack/grid/place the canvas, or to neglect to do that for all of its containers. An easy way to verify is to temporarily give your canvas a really bright background so that it stands out from the rest of the GUI.
Have you set the scroll region? The other explanation is that the drawing is happening, but it's happening in an area that is outside the viewable portion of the canvas. You should be setting the scrollregion attribute of the canvas after creating your drawings, to make sure everything you're drawing can be made visible.
Does your canvas and canvas objects have an appropriate color? It's possible that you've changed the background of the canvas to black (since you don't show that code in your question), and you're using a default color of black when creating your lines.

Categories