How to correctly use tkinter create_line() coordinates - python

This tutorial is using the size of the canvas as coordinates for the lines:
http://effbot.org/tkinterbook/canvas.htm
However, if we edit the code to give the canvas no padding, we can see that this is not working correctly. If you look closely the second create_line() is not lining up with the corners correctly:
from tkinter import *
master = Tk()
w = Canvas(master, width=200, height=100,bd=0,highlightthickness=0)
w.configure(bg="black")
w.pack()
w.create_line(0, 0, 200, 100, fill="red")
w.create_line(0, 100, 200, 0, fill="red")
master.mainloop()
Another example with a 3x3 canvas:
from tkinter import *
root = Tk()
root.configure(bg="blue")
canvas = Canvas(root, width=3, height=3, borderwidth=0, highlightthickness=0, bg="black")
canvas.pack()
canvas.create_line(0,0,3,3, fill="red")
canvas.create_line(0,3,3,0, fill="red")
root.mainloop()
This problem seems to only effect lines going from bottom-left to top-right, or top-right to bottom-left.
If we change the coordinates of the second create_line() to -1 and 3 it now works correctly:
from tkinter import *
root = Tk()
root.configure(bg="blue")
canvas = Canvas(root, width=3, height=3, borderwidth=0, highlightthickness=0, bg="black")
canvas.pack()
canvas.create_line(0,0,3,3, fill="red")
canvas.create_line(-1,3,3,-1, fill="red")
root.mainloop()
My questions are: Why does this only effect the second create_line()? Why does the coordinate 0 become -1, if 3 does not become 2? Is this the way it's supposed to work, or does tkinter just have an inherent problem with drawing positive slopes correctly? It seems to me that the latter is the case. If I want to make a program that draws many lines based on a given set of coordinates, I would seemingly have to calculate if every given segment is a positive or negative slope before creating it.
I have had to put the program I'm making on a complete hold for several days because of this. Can somebody please provide any insight to this issue? Is there something I am missing or not understanding?

You make mistake in second line
Python counts from 0 so left bottom corner is (0,2), not (0,3). So you have to start second line in point (0,2)
First line has points (0,0), (1,1), (2,2) and (3,3) which is outside of canvas. Similar second line should have (0,2), (1,1), (2,0) and (3,-1) which is outside of canvas. But you can't skip (3,3) and (3,-1) because create_line doesn't draw last point - last point doesn't belong to line (similar like x doesn't belong to range(x)). If you skip (3,3) and (3,-1) then create_line doesn't draw (2,2) and (2,0)
Correct lines
canvas.create_line(0,0,3,3, fill="red")
canvas.create_line(0,2,3,-1, fill="red")
in other words
first line (a,b,a+3,b+3) gives (0,0,0+3,0+3) = (0,0,3,3)
second line (a,b,a-3,b-3) gives (0,2,0-3,2-3) = (0,2,3,-1)

Related

Why does this label go off screen when anchored?

I want to anchor this label but for some reason, it keeps going off the screen in my computer, i don't know if its just me but here's my code:
import tkinter as tk
root = tk.Tk()
root.attributes("-fullscreen", True)
label_frame = tk.Frame(root)
answer_label = tk.Label(label_frame, text="text")
answer_label.grid(row = 0, column = 0)
label_frame.place(anchor = "n")
root.mainloop()
Why does this label go off screen when anchored?
Consider this line of code:
label_frame.place(anchor = "n")
The anchor option tells tkinter what part of the frame is to be placed at the given coordinates. n means "north", or the top-center of the frame. So, whatever coordinates are given, the top-center portion of the frame will be at those coordinates.
Since you did not give any coordinates, they default to x=0 and y=0. Thus, the top-middle portion of the frame will be placed at 0,0. That means that the left half of the frame will be to the left of coordinate 0,0.
If you insist on using place, and if you want the frame centered, you can give a relx of .5, meaning that the anchor position (n) will be placed half-way across the width of the containing widget.
label_frame.place(relx=.5, y=0, anchor = "n")
try
label_frame.place(anchor = "nw")
Tested this and it seems to put the label in the right place.

Why is this canvas one pixel too large?

MCVE
import Tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root, width=100, height=100)
canvas.grid(row=0, column=0)
def print_click(event):
print event.x, event.y
canvas.bind('<Button-1>', print_click)
root.mainloop()
Issue
Clicking the canvas on the very top left prints (0, 0).
Clicking the canvas on the very bottom right prints (100, 100). I expected (99, 99).
This means the canvas is actually 101 pixels wide and high, not 100.
In my real program, I am showing an array (as an image) on the canvas and need the precise click position. If that position does not exist in the underlying image (i.e. (100, 100) for an 100x100 array), the program will crash.
Questions
Am I doing something wrong creating the canvas? Why is it one wider and higher than expected?
Is the simple fix here to just subtract 1 from width and height whenever setting up a canvas that needs to have width width and height height?
There are other things that contribute to the overall width and height of a widget besides just the width and height attributes. For example, both borderwidth and highlightthickness contribute to the overall size of the widget. Since you aren't setting those to zero, you're relying on the defaults for your platform, and those defaults apparently aren't zero.
You need to explicitly set those attributes to zero:
canvas = tk.Canvas(root, width=100, height=100, highlightthickness=0, borderwidth=0)

Tkinter - relation between font type and width

I apologize in advance if my question is a duplicate however I have not found an answer to this question.
I'm learning Tkinter and I'm struggling with understanding the relation between a label's font type, it's size and it's width and the length of the string in it.
Specifically, what my problem is:
I have created a widget: a 800x640 canvas on which I want to place other
widgets.
On this canvas I want to place a label with some text which has the following
attributes: font: Helvetica, font size: 20, text = "main application". I want
to place this label widget at the very most top left corner of the
widget(meaning at point 0,0 with respect to the canvas). I want the label to
be 200 in width meaning it's background to take almost 1/3 of the canvas's
size(after I manage to do this I plan to add 2 more labels as well). I guess
the height of the label is determined by the font size in this case 20. I
placed the label at coordinate y=20 and this coordinate seems to be ok.
I did some googling and found out that the width parameter of the label widget is not the actual width but something related to the font and size of the label's text: something like if I understood correctly: if the width is 6 than the label will be wide enough to contain 6 characters of, in my case verdana size 20. But I was not able to figure out what width and what x coordinate I should give my label so it starts at the x point of the canvas. Here is the code that I wrote:
from tkinter import *
from tkinter.ttk import *
from tkinter import messagebox
from tkinter import Menu
# Define the application class where we will implement our widgets
class Application(Frame):
def __init__(self, master):
super(Application, self).__init__(master)
# CANVAS COLOUR DEFAULTS TO THE COLOUR OF THE WORKING WINDOW
canvas = Canvas(master, width=800, height = 640, bg="gray") # IF YOU DO .PACK() HERE IT WILL RETURN NONE AND THEN YOU WILL HAVE PROBLEMS BECAUSE .PACK() RETURNS A 'NONE' TYPE OBJECT
canvas.place(relx=0.5, rely=0.5, anchor=CENTER)
# The 'menu' of the application. The selection labels
main_application_label = Label(master, text="main_application", font=("Helvetica", 20))
main_application_window = canvas.create_window(103,20, window=main_application_label)
main_application = Tk()
main_application.title("main_application")
app = Application(main_application)
app_width = 800
app_height = 640
screen_width = main_application.winfo_screenwidth()
screen_height = main_application.winfo_screenheight()
x_coord = (screen_width/2) - (app_width/2)
y_coord = (screen_height/2) - (app_height/2)
main_application.geometry("%dx%d+%d+%d" % (app_width, app_height, x_coord, y_coord))
main_application.mainloop()
I have managed to somehow get the label at around point 0,0(by giving more values till I got it right) but the actual width of the label is not 200 pixels(~1/3 of the canvas). Please help me understand what values to the width parameter I should give so that my label's background is 1/3 of the canvas's size and if possible explain the relation between character font and width parameter so I can do that for any widgets regardless of their text's length. Thank you for reading my post!
Edit: What I wanted to do was to place 3 widgets(labels in this case but it doesn't matter) on the canvas. I did not understand what the 'anchor' option does and that was confusing me because I was expecting the center of the widget to be placed at the given coordinates all times but as I was changing anchor the placement of the center of the widget was changing and that was confusing me. It's all clear now thanks to #Bryan Oakley. Thanks.
If you want the upper left corner of the text to be at (0,0), you don't have to adjust the coordinates based on the width. You can use the anchor option when creating the canvas object:
main_application_window = canvas.create_window(0, 0, anchor="nw",
window=main_application_label)
If you really need to compute the actual size of the string, you can create a Font object and then use the measure method to find the actual width of a string in the given font.
from tkinter.font import Font
font = Font(family="Helvetica", size=20)
string_width = font.measure("main_application")
string_height = font.metrics("linespace")
This gives you the size of the rendered string. If you're using a label widget you'll also need to take into account the amount of padding and borders that the widget uses.
When you create items on a canvas, you can specify the width and height. For example, this makes the widget 200 pixels wide:
main_application_window = canvas.create_window(0, 0, anchor="nw", width=200,
window=main_application_label, width=400)

How to get the size of a tkinter Button?

How can I get the size of a button object?
If I do:
quitButton = Button(self, text="Quit", command=self.quit)
_x = quitButton.winfo_width()
_y = quitButton.winfo_height()
print _x, _y
It prints 1 1.
What am I doing wrong?
The size will be 1x1 until it is actually drawn on the screen, since the size is partly controlled by how it is managed (pack, grid, etc).
You can call self.update() after you've put it on the screen (pack, grid, etc) to cause it to be drawn. Once drawn, the winfo_width and winfo_height commands will work.

How to set canvas size properly in Tkinter?

I am using Tkinter to visualize my data points. My problem is that I cannot make the data points appear in the center of the canvas and meanwhile the canvas is big enough.
To make the canvas look good, I wish to fix it at around 800*600 (I think the unit is pixel). So I did the following:
class DisplayParticles(Canvas):
def __init__(self):
# Canvas
Canvas.__init__(self)
self.configure(width=800, height=600)
# Particles
self.particle_radius = 1
self.particle_color = 'red'
# User
self.user_radius = 4
self.user_color = 'blue'
self.ghost_color = None
However, my data to be plotted are in the unit of meter. Plus, they center around the origin (0, 0), which means that there are negative coordinates for both x and y.
Then when I plot them on the canvas I get something like this
Obviously, the data points were plotted in pixel!
I wish the canvas to be big enough on the screen and meanwhile the data are plotted in a proper scale centered at the canvas. (Place my origin (0, 0) at the canvas center)
How may I do that?
The visible center of the canvas can be changed by setting the scrollregion attribute. For example, if you set the scrollregion to (-400,-400, 400, 400) in a canvas that is 800 pixels wide, the coordinate 0,0 will appear in the center of the screen.
Here's an example that draws a square at 0,0, and which appears in the center of the screen:
import Tkinter as tk
class Example(tk.Frame):
def __init__(self,*args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
self.canvas = tk.Canvas(width=800, height=800)
self.canvas.pack(side="top", fill="both", expand=True)
self.canvas.configure(scrollregion=(-400, -400, 400, 400))
self.canvas.create_rectangle(-10,-10,10,10, fill="red", outline="black")
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(side="top", fill="both", expand=True)
root.mainloop()
You can also use the xview_scroll and yview_scroll methods to move 0,0 into the center of the visible portion of the canvas. Instead of setting the scrollregion as in the above example, you can do this after creating your canvas:
self.canvas.xview_scroll(800, "units")
self.canvas.yview_scroll(800, "units")
This will programmatically scroll the canvas over and down 800 pixels, so that 0,0 is in the center of the screen.
Use the xview and yview methods to scroll the canvas view so the origin is in the center.
Scaling is not supported, so if you need that, you need to transform the source data, like Brionius suggests.
Your Canvas is not going to automatically scale to fit what you've drawn - you'll have to figure out what the proper size is and set it.
Also, Canvas coordinates will always start with (0, 0) in the upper left corner - no way to change that. That means you'll have to translate all the points you plot on the canvas. Luckily, that's easy:
width = ... # Determine the correct width
height = ... # Determine the correct height
self.configure(width=width, height=height)
coords = (-20, -30, 10, 60) # The coordinates of a shape you want to draw
# add width/2 and height/2 to x and y coordinates respectively so that the (0, 0) coordinate is shifted to the center of the canvas:
newCoords = (coords[0]+width/2, coords[1]+height/2, coords[2]+width/2, coords[3]+height/2)
self.create_oval(*newCoords) # Create the translated shape

Categories

Resources