Drawing with mouse on Tkinter Canvas - python-3.x

I found the following piece of code here.
from tkinter import *
canvas_width = 500
canvas_height = 150
def paint( event ):
python_green = "#476042"
x1, y1 = ( event.x - 1 ), ( event.y - 1 )
x2, y2 = ( event.x + 1 ), ( event.y + 1 )
w.create_oval( x1, y1, x2, y2, fill = python_green )
master = Tk()
master.title( "Painting using Ovals" )
w = Canvas(master,
width=canvas_width,
height=canvas_height)
w.pack(expand = YES, fill = BOTH)
w.bind( "<B1-Motion>", paint )
message = Label( master, text = "Press and Drag the mouse to draw" )
message.pack( side = BOTTOM )
master.mainloop()
It lets you draw on a Tkinter canvas by dragging your mouse around. It works just fine when I move my mouse very very slowly but the moment I move it a bit faster, the lines end up broken. Here's what I mean:
Screenshot of the Tkinter window
The line on the top was drawn slowly.
The one on the bottom, rather quickly.
As you can see, the bottom one is missing points in many places. How do I fix this?

If you want to draw a line, you need to register the coordinates of where the mouse moves and create line based on that using create_line method because it connects those points. So append positions of mouse to a list and draw a line and use the coordinates from that list. When you start drawing (click mouse), append the starting coordinates. Then on movement append the coordinates, delete the previous line (basically updating) and draw a new one. When mouse button is released clear the list of points and set the current line id to None to indicate that currently no line is drawn.
import tkinter as tk
line_id = None
line_points = []
line_options = {}
def draw_line(event):
global line_id
line_points.extend((event.x, event.y))
if line_id is not None:
canvas.delete(line_id)
line_id = canvas.create_line(line_points, **line_options)
def set_start(event):
line_points.extend((event.x, event.y))
def end_line(event=None):
global line_id
line_points.clear()
line_id = None
root = tk.Tk()
canvas = tk.Canvas()
canvas.pack()
canvas.bind('<Button-1>', set_start)
canvas.bind('<B1-Motion>', draw_line)
canvas.bind('<ButtonRelease-1>', end_line)
root.mainloop()
Also:
I strongly advise against using wildcard (*) when importing something, You should either import what You need, e.g. from module import Class1, func_1, var_2 and so on or import the whole module: import module then You can also use an alias: import module as md or sth like that, the point is that don't import everything unless You actually know what You are doing; name clashes are the issue.

Related

about Python canvas moving object

I want to move a rectangle from left to right with a step of 50,but the canvas doesn't draw the rectangle until it arrive right side.
import tkinter as tk
import time
root=tk.Tk()
c_width,c_height=500,250
cv = tk.Canvas(root,bg = 'white',width=c_width,height=c_height)
l_x=0
l_y=0
r_x=50
r_y=50
step=50
r1=cv.create_rectangle(l_x,l_y,r_x,r_y,fill='red')
while l_x<c_width-50:
cv.delete(r1)
l_x=l_x+step
r_x=r_x+step
r1=cv.create_rectangle(l_x,l_y,r_x,r_y,fill='red')
print(c_width,l_x)
time.sleep(1)
cv.pack()
root.mainloop()
It is not recommended to use while/for loop in tkinter application because it will block the tkinter mainloop() from handling pending events and updates. Use .after() instead.
Also you don't need to delete and recreate the rectangle item, just move the rectangle item using cv.move().
Below is the update code:
import tkinter as tk
root = tk.Tk()
c_width, c_height = 500, 250
cv = tk.Canvas(root, bg='white', width=c_width, height=c_height)
cv.pack()
l_x = 0
l_y = 0
r_x = 50
r_y = 50
step = 50
r1 = cv.create_rectangle(l_x, l_y, r_x, r_y, fill='red')
def move_rect(x):
# move the rectangle by "step" pixels horizontally
cv.move(r1, step, 0)
x += step
if x < c_width:
cv.after(1000, move_rect, x)
cv.after(1000, move_rect, r_x)
root.mainloop()

tkinter: why does this movable label move when i don't move it?

i have a moveable label. its position is read from a txt file and wrote to txt file when it was changed by dragging.
but the position of the label changes when i don't drag it, just by running the snippet.
i tried it with multiple labels and found out that each time i run the snippet the x and y of every single moveable label increment with the same number.
import tkinter as tk
def make_draggable(widget):
widget.bind("<Button-1>", on_drag_start)
widget.bind("<B1-Motion>", on_drag_motion)
def on_drag_start(event):
widget = event.widget
widget._drag_start_x = event.x
widget._drag_start_y = event.y
def on_drag_motion(event):
widget = event.widget
x = widget.winfo_x() - widget._drag_start_x + event.x
y = widget.winfo_y() - widget._drag_start_y + event.y
widget.place(x=x, y=y)
def get_new_positions():
x, y = label1.winfo_rootx(), label1.winfo_rooty()
positions["moveable_label"] = (x, y)
dictfile = open("labelposition.txt", "w") # save new positions
dictfile.write(str(positions))
dictfile.close()
def close_window():
main.destroy()
root = tk.Tk()
root.geometry('1200x900')
tk.Label(root, text="test").pack()
root.withdraw()
# start window
aWindow = tk.Toplevel(root)
aWindow.geometry('1200x900')
def change_window():
# call function which reads the y coordinates
aWindow.update()
get_new_positions()
#remove the other window entirely
aWindow.destroy()
#make root visible again
root.iconify()
root.deiconify()
tk.Button(aWindow, text="OK", command=change_window).pack()
# load dict with label positions
dictfile = open("labelposition.txt", "r")
positions = eval(dictfile.read()) # load label positions
dictfile.close()
# position label
label1 = tk.Label(aWindow, text="moveable")
label1.place(x=positions["moveable_label"][0], y=positions["moveable_label"][1])
make_draggable(label1)
root.mainloop()
The goal of my code snippet should be:
show a moveable label and position it according to the x y data saved in a txt file.
if the label is moved to another position with the mouse and the ok button is klicked, the new x y position is saved to the txt file.
a new empty page opens
but: when i run the snippet multiple times without touching the label with the mouse, the label position changes its position every time anyway (x and y get higher)
can you see where this x y change comes from?
The reason the location keeps changing throughout runs is because the method you use to capture the x and y coordinates of your widget return the coordinates relative to the top left corner of the screen, not the parent.
winfo_rootx():
Get the pixel coordinate for the widget’s left edge, relative to the screen’s upper left corner.
Returns: The root coordinate.
Instead of using winfo_root*, use winfo_*, which returns the location relative to the parent window.
winfo_x():
Returns the pixel coordinates for the widgets’s left corner, relative to its parent’s left corner.
So inside your get_new_positions function, the x and y coordinates should be:
def get_new_positions():
x, y = label1.winfo_x(), label1.winfo_y()
positions["moveable_label"] = (x, y)
dictfile = open("labelposition.txt", "w") # save new positions
dictfile.write(str(positions))
dictfile.close()

Is there any command in turtle which stops turtle from further drawing?

I am creating a tkinter based program which used turtle graphics to draw graphs. I have a button which draws a square in the screen. But when I press that button while turtle is drawing something else, the turtle start to draw square and after finishing it again continue its previous task. I don't want it to continue it previous function. I want to directly stop it wherever it is and just do its new job.
This is the sample code but this is not actually what I need it for. I have tried to simplify the problem.
import tkinter as tk
from turtle import RawTurtle
def square():
t.up()
t.home()
t.down()
for i in range(4):
t.fd(100)
t.rt(90)
def drawseries():
l = 2
for i in range(100):
t.fd(l)
t.lt(90)
l += 2
root = tk.Tk()
button = tk.Button(root,command = square, text = 'Draw Square')
button.pack()
canvas = tk.Canvas(root, width = 500, height = 500)
canvas.pack()
t = RawTurtle(canvas)
drawseries()
You are always calling the drawseries() command at the end of the file. When the for loop is done, it calls drawseries() again.

How to enhance window size selection on a tkinter project including button-image as label?

I'm currently working on a little project on python-3.x including some tkinter ressources. My program is made to display on a screen a list of pictures included in a directory, each picture is put on a button that is a sixth of the original image, and if we click on it, it display the image on his original size on a new window. The original window is set by the amount of pictures i put in the columns (i can choose in the code) and i ve made a scrollbar because i have to work with a lot of pictures.
But here is my problem, it's works fine except that if i change the window size, like reduce it for example, the buttons don't follow, they just vanish behind the window, and with the scrollbar.
I'm not particularly good in python so i was wondering that maybe by doing like a threading we could get the window size in live and then if the window size is inferior/superior of our columns of buttons, we could resize it and change the amount of columns then reload the page, but i will have to work with multiple image so it will take a lot of time.
from tkinter import *
from tkinter.filedialog import *
from tkinter.messagebox import *
from PIL import Image, ImageTk
import tkinter as tk
import glob
import os
import cv2
import copy
import _thread
import time
folder = 'X:/users/Robin/data/dataset-valid/visu/*.jpg'
a=glob.glob(folder)
fic = "../data/list.txt"
fichObj=open(fic,"w")
p = []
for f in a:
fichObj.write(f+"\n")
fichObj.close()
class SuperPhoto(object):
def __init__(self, photo , image):
self.photo = photo
temp = cv2.resize(image, (int((self.photo.width())/6) , int((self.photo.height())/6)))
red = temp[:,:,2].copy()
blue = temp[:,:,0].copy()
temp[:,:,0] = red
temp[:,:,2] = blue
temp = Image.fromarray(temp)
self.miniature = ImageTk.PhotoImage(temp)
def agrandir(self):
Newfen=Toplevel()
Newfen.geometry("+60+60")
#self.photo.resize((500,500))
print(type(self.photo))
label = Label(Newfen, image=self.photo, width=self.photo.width(), height=self.photo.height())
label.image = self.photo # keep a reference!
label.pack()
if os.path.exists (fic): #os.path utile
count = len(open(fic).readlines())
print(count)
#lin = open(fic).readlines()
#print(lin)
class ScrollableCanvas(Frame):
def __init__(self, parent, *args, **kw):
Frame.__init__(self, parent, *args, **kw)
canvas=Canvas(self,bg='#FFFFFF',width=300,height=300,scrollregion=(0,0,500,500))
canvas.update_idletasks()
vbar=Scrollbar(self,orient=VERTICAL)
vbar.pack(side=RIGHT, fill=Y)
vbar.config(command=canvas.yview)
canvas.config(width=1200,height=700)
canvas.config(yscrollcommand=vbar.set)
canvas.pack(side=LEFT,expand=True,fill=BOTH)
# create a frame inside the canvas which will be scrolled with it
self.interior = interior = Frame(canvas)
interior_id = canvas.create_window(0, 0, window=interior, anchor=NW )
# track changes to the canvas and frame width and sync them,
# also updating the scrollbar
def _configure_interior(event):
# update the scrollbars to match the size of the inner frame
size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
canvas.config(scrollregion="0 0 %s %s" % size)
if interior.winfo_reqwidth() != canvas.winfo_width():
# update the canvas's width to fit the inner frame
canvas.config(width=interior.winfo_reqwidth())
interior.bind('<Configure>', _configure_interior)
def _configure_canvas(event):
if interior.winfo_reqwidth() != canvas.winfo_width():
# update the inner frame's width to fill the canvas
canvas.itemconfigure(interior_id, width=canvas.winfo_width())
canvas.bind('<Configure>', _configure_canvas)
class Main_frame(Frame):
# Init
def __init__(self, fenetre_principale=None):
Frame.__init__(self, fenetre_principale)
self.grid()
self.scrollable_canvas = ScrollableCanvas(self)
self.scrollable_canvas.grid(row=1,column=1)
nbCol = 4
for file in a:
image = Image.open(file)
photo = ImageTk.PhotoImage(image)
w = photo.width()
L.append(int(w/6))
#print(L)
sumL = int(sum(L)/nbCol)
print(sumL)
p.append(SuperPhoto(photo, cv2.imread(file)))
for ligne in range(int(count/nbCol)):
for colonne in range(nbCol):
photo = p[ligne * nbCol + colonne]
button = Button(self.scrollable_canvas.interior, image=photo.miniature, command=photo.agrandir)
button.grid(row=ligne, column=colonne)
if __name__ == "__main__":
root = Tk()
root.title("VISU")
root.geometry("+0+0")
L= []
interface = Main_frame(fenetre_principale=root)
root.update_idletasks()
print(root.winfo_width())
print(root.geometry())
interface.mainloop()
So, I except this program to work like a classic directory display, with the columns that change automatically when we resize the window and with the scrollbar that follow it.
If you have any solutions it will really help me ..
You can try it, just put some jpeg pictures in a directory and change the folder variable with the link of your directory.
Thanks in advance for your help, if you have any questions to understand more clearly what i've said don't hesitate.
Each time the root window is resized, a <Configure> event is triggered. Catch it as follows:
def resize(event):
root.update_idletasks()
#update all image sizes here if needed
#all widgets can be 're-grided' here based on new width and height of root window
root.bind('<Configure>', resize)
If you want to ensure that your window cannot be resized, use the following:
root.resizable(False, False)

tkinter: dragging widgets

I'd like to make a drag-n-drop function for a widget. The code is this:
from tkinter import *
root = Tk()
root.config(background = "red", width = 500, height = 500)
root.title("root")
def frameDrag(event):
frame.place(x = event.x , y = event.y)
frame = Frame(root, width = 60, height = 30)
frame.place(x=0, y=0)
frame.bind("<B1-Motion>", frameDrag)
root.mainloop()
Basically, it should place the widget to the location you move your mouse to. Instead, the widget jumps around all over the window.
Any ideas how to fix this?
It is jumping all over the place because you are telling it to as shown by:
def frameDrag(event):
print event.x, event.y
frame.place(x = event.x , y = event.y)
Better to use a canvas widget and better to use B1-Click and B1-Release events and compute the delta. Look for the widget demo that comes along with Tkinter.

Resources