ARC on tkinter canvas glitches when zoomed - python-3.x

I ran into a problem with rendering an arc on tkinter canvas.
(I am using recommended methods for scaling and scrolling the canvas, see my code...)
The code creates an arc on the canvas, its style is 'pieslice'.
At first everything seems to work OK, but when I keep zooming-in to the curved edge of the shape, at some point it starts to mismatch with the other edges and eventually it disappears...
If I keep zooming even more, other edges disappear as well...
from tkinter import *
root = Tk()
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
cnv = Canvas(root)
cnv.grid(row=0, column=0, sticky='nswe')
cnv.create_arc(20, 20, 250, 250, start=0, extent=30)
def scroll_start(event):
cnv.configure(cursor='fleur')
cnv.scan_mark(event.x, event.y)
def scroll_move(event):
cnv.scan_dragto(event.x, event.y, 1)
def scroll_end(event):
cnv.configure(cursor='arrow')
def zoom(event):
if event.delta > 0:
cnv.scale('all', cnv.canvasx(event.x), cnv.canvasy(event.y), 1.1, 1.1)
else:
cnv.scale('all', cnv.canvasx(event.x), cnv.canvasy(event.y), 0.9, 0.9)
cnv.bind('<Button-3>', scroll_start)
cnv.bind('<B3-Motion>', scroll_move)
cnv.bind('<ButtonRelease-3>', scroll_end)
cnv.bind('<MouseWheel>', zoom)
root.mainloop()
Is there a way to fix this or am I getting to the limitations of tkinter? Thanks for any help.

This is a partial answer that gives a measure of the limit where scaling seems to break down:
Adding a variable to record the scaling_factor, I can reach a 336 times magnification before observing the phenomena described by the OP. I speculate that this is maybe a float precision issue, or a limitation of canvas size, or some other reason?
from tkinter import *
root = Tk()
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
cnv = Canvas(root)
cnv.grid(row=0, column=0, sticky='nswe')
cnv.create_arc(20, 20, 250, 250, start=0, extent=30)
scaling_factor = 1
def scroll_start(event):
cnv.configure(cursor='fleur')
cnv.scan_mark(event.x, event.y)
def scroll_move(event):
cnv.scan_dragto(event.x, event.y, 1)
def scroll_end(event):
cnv.configure(cursor='arrow')
def zoom(event):
global scaling_factor
if event.delta > 0:
cnv.scale('all', cnv.canvasx(event.x), cnv.canvasy(event.y), 1.1, 1.1)
scaling_factor *= 1.1
print(scaling_factor)
else:
cnv.scale('all', cnv.canvasx(event.x), cnv.canvasy(event.y), 1/1.1, 1/1.1)
scaling_factor *= .9
print(scaling_factor)
cnv.bind('<Button-3>', scroll_start)
cnv.bind('<B3-Motion>', scroll_move)
cnv.bind('<ButtonRelease-3>', scroll_end)
cnv.bind('<MouseWheel>', zoom)
root.mainloop()

Related

Why my tkinter frame is not on the top of the canvas and canvas can still be scrolled up?

I'm creating a GUI tool and while working on it, I faced an issue that I couldn't figure out why it is happening.
I have a scrollable frame inside a canvas, however that frame is not on the top side of the canvas "even though I want it on the top side" and I noticed that the canvas can still be scrolled up above the frame (frame background is green) which is I consider a wrong behavior.
It doesn't matter how many times, I checked the code and revised/edited it, I still cannot figure it out, so I decided to check here for any hints and thank you in advance.
My code is as follow
import tkinter.ttk as ttk
from tkinter import *
root = Tk()
root.title("Checklist Buddy")
root.config(padx=10, pady=5)
root.geometry("800x500")
top_frame = Frame(root, bg="black")
top_frame.grid(row=0, column=0, sticky="NSEW")
mf = Frame(root, bg="brown")
mf.grid(row=1, column=0, sticky="NEWS")
canvas = Canvas(mf, bg="yellow")
canvas.grid(row=0, column=0, sticky="NEWS")
yscrollbar = ttk.Scrollbar(mf, command=canvas.yview)
yscrollbar.grid(row=0, column=1, sticky="ns")
canvas.bind('<Configure>', lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
canvas.configure(yscrollcommand=yscrollbar.set)
bpo_frame = Frame(canvas, background="green")
win = canvas.create_window((0, 0), window=bpo_frame, height=100)
def _on_mousewheel(event):
canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
def onCanvasConfigure(event):
canvas.itemconfigure(win, width=event.width)
canvas.bind('<Configure>', onCanvasConfigure, add="+")
canvas.bind_all("<MouseWheel>", _on_mousewheel)
root.columnconfigure("all", weight=1)
root.rowconfigure("all", weight=1)
mf.columnconfigure(0, weight=1)
# mf.rowconfigure("all", weight=1)
root.mainloop()
Below is a picture to show what I mean, there shall be no empty yellow space above the green window (no scroll up shall be there as the window shall be on the top side)
The main issue here is the scrollregion of the canvas, which is set to a region with negative y-coordinates. If you want the point (0,0) to be at the top of the canvas, you need to define it. And don't change it once the canvas is reconfigured.
Also, the canvas window needs an anchor in the top left corner. Otherwise, it is centered on (0,0) and reaches half into the negative x and y coordinates.
If you change the lines 15-25 as follows, the behavior is as expected:
(Personally, I would also set the width of the window equal to the width of the canvas before scrolling the first time instead of binding this change to the <Configure>. But you may have a reason for this)
canvas = Canvas(mf, bg="yellow",scrollregion=(0, 0, 1000, 1000))
canvas.grid(row=0, column=0, sticky="NEWS")
yscrollbar = ttk.Scrollbar(mf, command=canvas.yview)
yscrollbar.grid(row=0, column=1, sticky="ns")
#canvas.bind('<Configure>', lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
canvas.configure(yscrollcommand=yscrollbar.set)
bpo_frame = Frame(canvas, background="green")
win = canvas.create_window((0, 0), window=bpo_frame, height=100, anchor=NW)

canvas circle getting distorted in python

I am writing a code where there are multiple balls (or circles) of various sizes in Tkinter canvas. The problem is that during the running process when ever ball gets close to the canvas boundary and sometimes even away from the boundary the shape of the ball gets distorted. I am unsure why this is happening. is there something I am missing during the update of the window object?
This is the main file:
from tkinter import *
from Ball import *
import time
HEIGHT = 500
WIDTH = 500
window = Tk()
canvas = Canvas(window, height=HEIGHT, width=WIDTH)
canvas.pack()
volley_ball = Ball(canvas, 0, 0, 100, 2, 2,'red')
cricket_ball = Ball(canvas, 10, 10, 30, 4, 2,'green')
golf_ball = Ball(canvas, 100, 100, 20, 8, 6,'white')
while True:
volley_ball.move()
cricket_ball.move()
golf_ball.move()
window.update()
time.sleep(0.01)
window.mainloop()
This is the Ball class module code:
class Ball:
def __init__(self, canvas, x, y, diameter, xVelocity, yVelocity, color):
self.canvas = canvas
self.image = canvas.create_oval(x, y, diameter, diameter, fill=color)
self.xVelocity = xVelocity
self.yVelocity = yVelocity
self.diameter = diameter
def move(self):
self.coordinates = self.canvas.coords(self.image)
if(self.coordinates[2] >= self.canvas.winfo_width() or self.coordinates[0] < 0):
self.xVelocity = -self.xVelocity
if (self.coordinates[3] >= self.canvas.winfo_height() or self.coordinates[1] < 0):
self.yVelocity = -self.yVelocity
self.canvas.move(self.image, self.xVelocity, self.yVelocity)
The visual artifacts are created by over driving your move function. Try changing delay to 1 millisecond and observe the artifacts! Slow it down a little and use xVelocity, yVelocity to control speed.
Also try changing your while loop for window.after.
def animate():
volley_ball.move()
cricket_ball.move()
golf_ball.move()
window.after(20, animate)
window.after(500, animate)
window.mainloop()

Python / tkinter: horizontal scrollbar not working properly

I have a problem using a horizontal tk.Scrollbar on a tk.Text widget. It is displayed too small for the region it is supposed to scroll - if I move it to the left, it reaches the end of the scrollable region before the small bar touches the right side, as you'd expect. Also, if I let it go it visually jumps back to its original position at the start, the scroll state is not altered though.
What's weird is that I'm using the exact same syntax for the vertical scrollbar and it works flawlessly.
I included how I'm (ab)using the tk.Text-widget, which is tk.Label-widgets embedded into the tk.Text-widget via the window_create method.
Screenshots:
Code:
import tkinter as tk
class app(tk.Tk):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.frame = tk.Frame(self)
self.frame.grid(row=0, column=0, sticky='NW')
# widget definitions
cellYScrollbar = tk.Scrollbar(self, orient="vertical")
cellXScrollbar = tk.Scrollbar(self, orient="horizontal")
full_width = 30
self.cell = tk.Text(
self, yscrollcommand=cellYScrollbar.set, xscrollcommand=cellXScrollbar.set,
width=full_width, height=full_width / 2, wrap="none",
cursor="arrow"
)
cellYScrollbar.config(command=self.cell.yview)
cellXScrollbar.config(command=self.cell.xview)
# widget gridding
self.cell.grid(row=0, column=0, sticky='NW', padx=[5, 0], pady=[5, 0])
cellXScrollbar.grid(row=1, column=0, columnspan=2, sticky='NEW', padx=[5, 0], pady=[0, 5])
cellYScrollbar.grid(row=0, column=1, sticky='NSW', padx=[0, 5], pady=[5, 0])
tiles = []
for i in range(0, 9):
for j in range(0, 9):
test = tk.Label(self.cell, text='TEST'+str(i)+str(j), width=7, height=3)
tiles.append(test)
self.cell.window_create("end", window=test, padx=1, pady=1)
self.cell.insert("end", "\n")
for tile in tiles:
tile.bind("<MouseWheel>", lambda event: self.cell.yview_scroll(int(-1 * (event.delta / 120)), "units"))
self.cell.config(state='disabled')
root = app()
root.mainloop()
EDIT: fixed the code, .set was missing in xscrollcommand=cellXScrollbar.set, thank you to acw1668!

Cropping multiple parts of the image and placing on the canvas in Tkinter

I am new to the Tkinter platform so please help me where I'm going wrong.
I have a floorplan image in which I want to cut the objects in it and place it on a canvas screen so that individual objects can be dragged if I want.
I'm able to cut and paste the objects on the screen except the first object. It is not placed on the screen. Can anyone help me?
I am using a Matlab code to identify the objects in the floorplan image. I am attaching the Matlab file.
Is it possible to add the wall to the screen? I have no idea at all. Can anyone suggest how to add the wall?
Here is my code
import tkinter as tk
from tkinter import *
from PIL import Image,ImageTk
from scipy.io import loadmat
root = tk.Tk()
canvas = tk.Canvas(width=800, height=800)
canvas.grid(row=4,column=0,sticky=(N,W,E,S))
#canvas.config(width=100,height=100)
root.grid_columnconfigure(0, weight=1)
root.grid_rowconfigure(4, weight=1)
mfile=loadmat('C:\\Users\\User\\Desktop\\tkinter_codes\\obj identification\\ans.mat')
#print(mfile.values())
#print(len(mfile['ans'][0]))
print(mfile.values())
class Example(tk.Frame):
def __init__(self, parent):
self.parent =parent
tk.Frame.__init__(self, parent)
self.canvas = tk.Canvas(width=800, height=800)
self.canvas.grid(row=0,column=0,sticky=(N,W,E,S))
#canvas.pack (expand =1, fill =tk.BOTH)
self.canvas.tag_bind("DnD","<Button-1>")
self._drag_data = {"x": 0, "y": 0, "item": None}
self.canvas.tag_bind("token", "<ButtonPress-1>", self.drag_start)
self.canvas.tag_bind("token", "<ButtonRelease-1>", self.drag_stop)
self.canvas.tag_bind("token", "<B1-Motion>", self.drag)
self.canvas.bind("<ButtonPress-1>", self.on_button_1)
self.iimg=Image.open("C:\\Users\\User\\Desktop\\tkinter_codes\\floorplans\\ROBIN\\Dataset_3rooms\\Cat1_1.jpg")
#iimg=iimg.resize((1000, 800), Image.ANTIALIAS)
self.canvas.img=ImageTk.PhotoImage(self.iimg)
#canvas.img = canvas.img.resize((250, 250), Image.ANTIALIAS)
self.canvas_img=canvas.create_image(0,0,image=self.canvas.img,anchor="nw")
self.mylist=[]
for x in range(len(mfile['ans'][0])):
#canvas.create_rectangle((mfile['ans'][0][x][0][0],mfile['ans'][0][x][0][1],mfile['ans'][0][x][0][0]+mfile['ans'][0][x][0][2],mfile['ans'][0][x][0][1]+mfile['ans'][0][x][0][3]),outline='red',tags=("token","DnD"))
self.im_crop = self.iimg.crop((mfile['ans'][0][x][0][0],mfile['ans'][0][x][0][1],mfile['ans'][0][x][0][0]+mfile['ans'][0][x][0][2],mfile['ans'][0][x][0][1]+mfile['ans'][0][x][0][3]))
self.canvas.im_crop2=ImageTk.PhotoImage(self.im_crop)
self.canvas.create_image(mfile['ans'][0][x][0][0],mfile['ans'][0][x][0][1], image=self.canvas.im_crop2)
#canvas.create_image(1000,1000,image=im_crop2)
#if(x>=0):
self.mylist.append(self.canvas.im_crop2)
#im_crop.show()
#canvas.iiiimg=ImageTk.PhotoImage(im_crop)
#canvas.create_image(150,150,image=canvas.im_crop2)
self.popup = tk.Menu(root, tearoff=0)
#self.popup.add_command(label="delete",command=lambda: self.dele(id))
self.popup.add_command(label="delete",
command=lambda: self.dele(id))
self.popup.add_command(label="add",command= lambda: root.bind("<Button-1>",self.addn))
root.bind("<Button-3>", self.do_popup)
self.canvas.delete(self.canvas_img)
def do_popup(self,event):
# display the popup menu
try:
self.popup.tk_popup(event.x_root, event.y_root, 0)
finally:
# make sure to release the grab (Tk 8.0a1 only)
self.popup.grab_release()
def on_button_1(self, event):
iid = self.canvas.find_enclosed(event.x-150, event.y-150, event.x + 150, event.y + 100)
#iid=canvas.find_closest(event.x, event.y)[0]
self.canvas.itemconfigure("DEL")
self.canvas.dtag("all","DEL")
self.canvas.itemconfigure(iid, tags=("DEL","DnD","token","drag"))
#canvas.unbind("<Button-1>")
def create_token(self, x, y, color):
"""Create a token at the given coordinate in the given color"""
self.canvas.create_rectangle(
x ,
y ,
x + 50,
y + 50,
outline=color,
fill=color,
tags=("token","DnD"),
)
def create_token1(self,x,y,color):
self.canvas.create_rectangle(
x ,
y ,
x + 25,
y + 25,
outline=color,
fill=color,
tags=("token","DnD"),
)
def drag_start(self, event):
"""Begining drag of an object"""
# record the item and its location
self._drag_data["item"] = self.canvas.find_closest(event.x, event.y)[0]
rect=self.canvas.bbox(self._drag_data["item"])
self.canvas.addtag_enclosed("drag",*rect)
print(rect)
self._drag_data["x"] = event.x
self._drag_data["y"] = event.y
def drag_stop(self, event):
"""End drag of an object"""
# reset the drag information
self._drag_data["item"] = None
self._drag_data["x"] = 0
self._drag_data["y"] = 0
self.canvas.dtag("drag","drag")
def drag(self, event):
"""Handle dragging of an object"""
# compute how much the mouse has moved
self.delta_x = event.x - self._drag_data["x"]
self.delta_y = event.y - self._drag_data["y"]
# move the object the appropriate amount
self.canvas.move("drag", self.delta_x, self.delta_y)
# record the new position
self._drag_data["x"] = event.x
self._drag_data["y"] = event.y
def dele(self,id):
#canvas.tag_bind(id,"<Button-1>")
self.canvas.delete("DEL")
def addn(self,event):
canvas.create_rectangle(event.x,event.y,event.x+25,event.y+25,fill='red',tags=("DnD","token"))
root.unbind("<Button-1>")
Example(root) #pack(fill="both", expand=True)
root.mainloop()
This is the Matlab code I am using for identifying objects

tkinter Canvas window size

I need to know when the Canvas is resized (eg when the master frame gets maximized) the new Canvas window size.
Unfortunately, if I try self.canvas['width'] it always seems to me I get back the width it had whenever I initialized it and not the current width.
How do I get the current Canvas window dimensions?
When you retrieve self.canvas['width'], you are asking tkinter to give you the configured width of the widget, not the actual width. For the actual width you can use .winfo_width().
If you want to know when the canvas is resized, you can add a binding to the <Configure> event on the widget. The event object that is passed to the binding has a width attribute which also has the actual width of the widget.
Here's an example:
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root, width=200, height=200, background="bisque")
canvas.pack(side="bottom", fill="both", expand=True)
canvas.create_text(10, 30, anchor="sw", tags=["event"])
canvas.create_text(10, 30, anchor="nw", tags=["cget"])
def show_width(event):
canvas.itemconfigure("event", text="event.width: %s" % event.width)
canvas.itemconfigure("cget", text="winfo_width: %s" % event.widget.winfo_width())
canvas.bind("<Configure>", show_width)
root.mainloop()
one possible solution:
try:
import Tkinter as tk
except:
import tkinter as tk
class myCanvas(tk.Frame):
def __init__(self, root):
#self.root = root
self.w = 600
self.h = 400
self.canvas = tk.Canvas(root, width=self.w, height=self.h)
self.canvas.pack( fill=tk.BOTH, expand=tk.YES)
root.bind('<Configure>', self.resize)
def resize(self, event):
self.w = event.width
self.h = event.height
print ('width = {}, height = {}'.format(self.w, self.h))
root = tk.Tk()
root.title('myCanvas')
myCanvas(root)
root.mainloop()
Notice that the size informed by the event is 2 pixels wider in either direction. That's the border, I suppose.

Resources