I am new to Tkinter and trying to figure out how to be able to zoom in and out on a canvas without messing up the drawn lines created by live data. Every second a new line is drawn and is supposed to be connected to the previous one, but if I zoom in on the canvas the next line that will be drawn is not connected to the previous one. It seems like all the lines are drawn based on some window coordinates (which are consistent no matter how much I zoom in or out), instead of being based on the canvas itself. I want to be able to both zoom in and zoom out as much as I want and still see a line being drawn from the upper left corner of the canvas to the bottom right corner of the canvas. I have tried to provide some code to make it easier to understand.
import tkinter as tk
import threading
import time
root = tk.Tk()
pressed = False
flag_run = False
GRID_W = 500
GRID_H = 500
def thread_entry(name):
print("<starting thread>")
i = 0
j = 0
while flag_run:
time.sleep(1)
canvas.create_line(i, j, i+1, j+1, fill="red")
i += 1
j += 1
print("<ending thread>")
def start():
global flag_run
flag_run = True
global thread
thread = threading.Thread(target=thread_entry, args=(1,))
thread.start()
def stop():
global flag_run
if flag_run is True:
flag_run = False
global thread
thread.join(timeout=0.1)
# move
def move_start(event):
canvas.scan_mark(event.x, event.y)
def move_move(event):
canvas.scan_dragto(event.x, event.y, gain=1)
# move
def pressed2(event):
global pressed
pressed = not pressed
canvas.scan_mark(event.x, event.y)
def move_move2(event):
if pressed:
canvas.scan_dragto(event.x, event.y, gain=1)
# windows zoom
def zoomer(event):
if (event.delta > 0):
canvas.scale("all", event.x, event.y, 1.1, 1.1)
elif (event.delta < 0):
canvas.scale("all", event.x, event.y, 0.9, 0.9)
canvas.configure(scrollregion = canvas.bbox("all"))
def zooming_in(event):
canvas.scale("all", event.x, event.y, 1.1, 1.1)
def zooming_out(event):
canvas.scale("all", event.x, event.y, 0.9, 0.9)
canvas = tk.Canvas(root, width=GRID_W, height=GRID_H, background="white")
canvas.grid(row=0, column=0, sticky="nsew")
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
for i in range(GRID_W//10 + 1):
canvas.create_line(i*10, 0, i*10, GRID_H, fill="#c9c9c9")
for j in range(GRID_H//10 + 1):
canvas.create_line(0, j*10, GRID_W, j*10, fill="#c9c9c9")
canvas.create_line(GRID_W // 2, 0, GRID_W // 2, GRID_H, fill="black", width=2)
canvas.create_line(0, GRID_H // 2, GRID_W, GRID_H // 2, fill="black", width=2)
canvas.bind("<ButtonPress-1>", move_start)
canvas.bind("<B1-Motion>", move_move)
canvas.bind("<ButtonPress-2>", pressed2)
canvas.bind("<Motion>", move_move2)
zoom_in = tk.Button(root, text="+")
zoom_in.grid(row=1, column=0)
zoom_in.bind("<Button-1>", zooming_in)
zoom_out = tk.Button(root, text="-")
zoom_out.grid(row=1, column=1)
zoom_out.bind("<Button-1>", zooming_out)
button = tk.Button(root, text="Start", command = start)
button.grid(row=2, column=0)
button_s = tk.Button(root, text="Stopp", command = stop)
button_s.grid(row=2, column=1)
root.mainloop()
I appreciate all the help and guidance I can get!
try stopping the flag_run while you hit the start button (zooming_in(event)) and continue when done zooming.
try remembering where the line was, and keep logging the data while you zoom in and out so you are not missing any values as you currently do.
Related
I was trying to understand how the scaling of canvas works.
Take for example, the following code. Why is that canvas.scale("all", ...), which is bind to mouse wheel, is scaling all the rectangles and not the text as well.
How can I achieve scaling of text along with the rectangles?
import Tkinter as tk
import random
pressed = False
class Example(tk.Frame):
def __init__(self, root):
tk.Frame.__init__(self, root)
self.canvas = tk.Canvas(self, width=400, height=400, background="bisque")
self.xsb = tk.Scrollbar(self, orient="horizontal", command=self.canvas.xview)
self.ysb = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.ysb.set, xscrollcommand=self.xsb.set)
self.canvas.configure(scrollregion=(0,0,1000,1000))
self.xsb.grid(row=1, column=0, sticky="ew")
self.ysb.grid(row=0, column=1, sticky="ns")
self.canvas.grid(row=0, column=0, sticky="nsew")
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
#Plot some rectangles
for n in range(50):
x0 = random.randint(0, 900)
y0 = random.randint(50, 900)
x1 = x0 + random.randint(50, 100)
y1 = y0 + random.randint(50,100)
color = ("red", "orange", "yellow", "green", "blue")[random.randint(0,4)]
self.canvas.create_rectangle(x0,y0,x1,y1, outline="black", fill=color, activefill="black", tags=n)
self.canvas.create_text(50,10, anchor="nw", text="Click and drag to move the canvas\nScroll to zoom.")
# This is what enables using the mouse:
self.canvas.bind("<ButtonPress-1>", self.move_start)
self.canvas.bind("<B1-Motion>", self.move_move)
self.canvas.bind("<ButtonPress-2>", self.pressed2)
self.canvas.bind("<Motion>", self.move_move2)
#linux scroll
self.canvas.bind("<Button-4>", self.zoomerP)
self.canvas.bind("<Button-5>", self.zoomerM)
#windows scroll
self.canvas.bind("<MouseWheel>",self.zoomer)
# Hack to make zoom work on Windows
root.bind_all("<MouseWheel>",self.zoomer)
#move
def move_start(self, event):
self.canvas.scan_mark(event.x, event.y)
def move_move(self, event):
self.canvas.scan_dragto(event.x, event.y, gain=1)
#move
def pressed2(self, event):
global pressed
pressed = not pressed
self.canvas.scan_mark(event.x, event.y)
def move_move2(self, event):
if pressed:
self.canvas.scan_dragto(event.x, event.y, gain=1)
#windows zoom
def zoomer(self,event):
if (event.delta > 0):
self.canvas.scale("all", event.x, event.y, 1.1, 1.1)
elif (event.delta < 0):
self.canvas.scale("all", event.x, event.y, 0.9, 0.9)
self.canvas.configure(scrollregion = self.canvas.bbox("all"))
#linux zoom
def zoomerP(self,event):
self.canvas.scale("all", event.x, event.y, 1.1, 1.1)
self.canvas.configure(scrollregion = self.canvas.bbox("all"))
def zoomerM(self,event):
self.canvas.scale("all", event.x, event.y, 0.9, 0.9)
self.canvas.configure(scrollregion = self.canvas.bbox("all"))
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
The Scaling of text is not "really" a scaling of text. It is (at least with the Canvas-widget) a change of font.
From the documentation :
This method will not change the size of a text item, but may move it.
Edit:
This behaviour also applies to images (created with Tkinter.Canvas.create_image(...)) and windows (created with Tkinter.Canvas.create_window(...)). As the documentation defines scaling as
.scale(tagOrId, xOffset, yOffset, xScale, yScale)
Scale all objects according to their distance from a point P=(xOffset, yOffset). The scale factors xScale and yScale are based on a value of 1.0, which means no scaling. Every point in the objects selected by tagOrId is moved so that its x distance from P is multiplied by xScale and its y distance is multiplied by yScale.
Please note the italic part for how scaling works.
End Edit
If you want to perform an increase of font-size you need to configure all the text elements by yourself using the itemconfigure method.
To Implement it in a very functional way you could use tags to identify all text elements. Increasing can happen not only absolute but also relatively if you first get the fontsize (itemcget) and then increase it depending on scale factor.
I know this post is old but I solved a similar question so I thought I would post a solution for the problem as well.
The idea is to add a tag to the text item and then reconfigure it each time the zoomer is called. Note, this solution does not change the linux zoom (zoomerP)
import tkinter as tk
import random
pressed = False
class Example(tk.Frame):
def __init__(self, root):
tk.Frame.__init__(self, root)
self.canvas = tk.Canvas(self, width=400, height=400, background="bisque")
self.xsb = tk.Scrollbar(self, orient="horizontal", command=self.canvas.xview)
self.ysb = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.ysb.set, xscrollcommand=self.xsb.set)
self.canvas.configure(scrollregion=(0,0,1000,1000))
self.xsb.grid(row=1, column=0, sticky="ew")
self.ysb.grid(row=0, column=1, sticky="ns")
self.canvas.grid(row=0, column=0, sticky="nsew")
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
self.fontSize = 10
#Plot some rectangles
for n in range(50):
x0 = random.randint(0, 900)
y0 = random.randint(50, 900)
x1 = x0 + random.randint(50, 100)
y1 = y0 + random.randint(50,100)
color = ("red", "orange", "yellow", "green", "blue")[random.randint(0,4)]
self.canvas.create_rectangle(x0,y0,x1,y1, outline="black", fill=color, activefill="black", tags=n)
self.canvas.create_text(50,10, anchor="nw", text="Click and drag to move the canvas\nScroll to zoom.", font = ("Helvetica", int(self.fontSize)), tags="text")
# This is what enables using the mouse:
self.canvas.bind("<ButtonPress-1>", self.move_start)
self.canvas.bind("<B1-Motion>", self.move_move)
self.canvas.bind("<ButtonPress-2>", self.pressed2)
self.canvas.bind("<Motion>", self.move_move2)
#linux scroll
self.canvas.bind("<Button-4>", self.zoomerP)
self.canvas.bind("<Button-5>", self.zoomerM)
#windows scroll
self.canvas.bind("<MouseWheel>",self.zoomer)
# Hack to make zoom work on Windows
root.bind_all("<MouseWheel>",self.zoomer)
#move
def move_start(self, event):
self.canvas.scan_mark(event.x, event.y)
def move_move(self, event):
self.canvas.scan_dragto(event.x, event.y, gain=1)
#move
def pressed2(self, event):
global pressed
pressed = not pressed
self.canvas.scan_mark(event.x, event.y)
def move_move2(self, event):
if pressed:
self.canvas.scan_dragto(event.x, event.y, gain=1)
#windows zoom
def zoomer(self,event):
if (event.delta > 0):
self.canvas.scale("all", event.x, event.y, 1.1, 1.1)
self.fontSize = self.fontSize * 1.1
elif (event.delta < 0):
self.canvas.scale("all", event.x, event.y, 0.9, 0.9)
self.fontSize = self.fontSize * 0.9
self.canvas.configure(scrollregion = self.canvas.bbox("all"))
for child_widget in self.canvas.find_withtag("text"):
self.canvas.itemconfigure(child_widget, font=("Helvetica", int(self.fontSize)))
print(self.fontSize)
#linux zoom
def zoomerP(self,event):
self.canvas.scale("all", event.x, event.y, 1.1, 1.1)
self.canvas.configure(scrollregion = self.canvas.bbox("all"))
def zoomerM(self,event):
self.canvas.scale("all", event.x, event.y, 0.9, 0.9)
self.canvas.configure(scrollregion = self.canvas.bbox("all"))
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
I'm french so my English is a little bit bad so don't pay attention.
I have made a tkinter calculator and for that I have delete the top of the window where the title and close button are situated and replace it by a canvas and it looks very nice, but I can no longer move the window on my screen, the window stays in the upper-left corner, and I can't move it in any way...I hope someone would have an idea.
For deleting the top of the window I have used win.overrideredirect(1).
I have tried something but this doesn't work:
import tkinter as tk
win = tk.Tk()
def coordsSouris(event):
# win.geometry()
print(event.x, event.y)
win.after(100, coordsSouris)
can = tk.Canvas(height = 400, width = 400)
can.pack()
can.bind("<Button-1>", coordsSouris)
win.mainloop()
The function misses the arg event and I can't give this argument with after()...
I have another idea: Is it possible to place the tittle bar in front of my canvas and hide it and cute i when it was in front of my exit button and menu ? so we doesnt see it but it work normaly
Set root .geometry( Width x Height + X + Y ).
keep Width & Height the same, just change X & Y parameters.
Edit: Yeah, that's a little more complicated. You need to store the initial drag position, then subtract it from subsequent events. Clear value once the button has been released. You also want to set min / max values to make sure it isn't going off the screen.
#! /usr/bin/env python3
import tkinter
Width, Height, = 100, 33
Xpos, Ypos = 20, 20
dragposX, dragposY = 0, 0
root = tkinter .Tk()
root .geometry( f'{Width}x{Height}+{Xpos}+{Ypos}')
root .overrideredirect( 1 ) ## no titlebar
Screenwidth = root .winfo_screenwidth()
Screenheight = root .winfo_screenheight()
Xmax = Screenwidth -Width
Ymax = Screenheight -Height
fontname = 'ariel'
fontsize = 8
fontstyle = 'normal'
font = fontname, fontsize, fontstyle
def left():
global Xpos ; Xpos -= 5
if Xpos < 0: Xpos = 0
root .geometry( f'{Width}x{Height}+{Xpos}+{Ypos}')
def right():
global Xpos ; Xpos += 5
if Xpos > Xmax: Xpos = Xmax
root .geometry( f'{Width}x{Height}+{Xpos}+{Ypos}')
def up():
global Ypos ; Ypos -= 5
if Ypos < 0: Ypos = 0
root .geometry( f'{Width}x{Height}+{Xpos}+{Ypos}')
def down():
global Ypos ; Ypos += 5
if Ypos > Ymax: Ypos = Ymax
root .geometry( f'{Width}x{Height}+{Xpos}+{Ypos}')
def drag( event ):
global Xpos, Ypos, dragposX, dragposY
if dragposX == 0 and dragposY == 0:
dragposX = event .x
dragposY = event .y
Xpos += event .x -dragposX
Ypos += event .y -dragposY
if Xpos < 0: Xpos = 0
if Ypos < 0: Ypos = 0
if Xpos > Xmax: Xpos = Xmax
if Ypos > Ymax: Ypos = Ymax
root .geometry( f'{Width}x{Height}+{Xpos}+{Ypos}')
def clear( event ):
global dragposX, dragposY
dragposX, dragposY = 0, 0
left_button = tkinter .Button( root, text='<', font=font, padx=2, pady=0, bg='blue', activebackground='lightblue', command=left )
left_button .grid( row=0, column=0, padx=1, pady=4 )
right_button = tkinter .Button( root, text='>', font=font, padx=2, pady=0, bg='blue', activebackground='lightblue', command=right )
right_button .grid( row=0, column=1, padx=1, pady=4 )
down_button = tkinter .Button( root, text='v', font=font, padx=2, pady=0, command=down )
down_button .grid( row=0, column=2, padx=1, pady=4 )
up_button = tkinter .Button( root, text='^', font=font, padx=2, pady=0, command=up )
up_button .grid( row=0, column=3, padx=1, pady=4 )
close_button = tkinter .Button( root, text='X', font=font, padx=2, pady=0, bg='red', activebackground='pink', command=root.destroy )
close_button .grid( row=0, column=4, padx=1, pady=4 )
root .bind( '<B1-Motion>', drag )
root .bind( '<ButtonRelease-1>', clear )
root .mainloop()
Here is a simplified method for moving canvas.
import tkinter as tk
def realcenter( o, w, h ) ->'o(w,h) centered on screen':
x = o.winfo_screenwidth( ) - w
y = o.winfo_screenheight( ) - h
o.geometry( '{0:d}x{1:d}+{2:d}+{3:d}'.format( w, h, int( x/2 ), int( y/2 ) ) )
def restore( ev ):
master.overrideredirect( 0 )
def unrestore( ev ):
master.overrideredirect( 1 )
master = tk.Tk()
sizew, sizeh = 400, 400
canvas_box = tk.Canvas(master, width=sizew, height=sizeh )
canvas_box.grid(row=0,column=0)
master.geometry( '400x400' )
realcenter( master, 400, 400 )
master.update_idletasks()
master.overrideredirect( 1 )
master.bind( '<F1>', restore )
master.bind( '<F2>', unrestore )
tk.mainloop()
I've included a window centering function
A slight modification to make it 2.x compatible.
I Have find the solution and its work perfectly !!!
I hope it will help people to do a personalized title bar!!!
my code :
from tkinter import *
import tkinter as tk
root = Tk()
x = 0
y = 0
def move_window(event):
# global x, y
print(x, y)
#event.x_the_name_of_the_widows is false you have to use x_root
#event if your page is called win or ...
root.geometry('+{0}+{1}'.format(event.x_root - x, event.y_root - y))
root.overrideredirect(True) # turns off title bar, geometry
root.geometry('400x200+200+200') # set new geometry
# make a frame for the title bar
title_bar = Frame(root, bg='white', relief='raised', bd=2)
# put a close button on the title bar
close_button = Button(title_bar, text='X', command=root.destroy)
# a canvas for the main area of the window
window = Canvas(root, bg='black')
# pack the widgets
title_bar.pack(expand=1, fill=X)
close_button.pack(side=RIGHT)
window.pack(expand=1, fill=BOTH)
# find the position of the cursor on the window and not on the screen
def set_xy(event):
global x,y
x=event.x_root - root.winfo_x()
y=event.y_root - root.winfo_y()
# print(x,y)
return x,y;
title_bar.bind('<1>',set_xy)
# bind title bar motion to the move window function
title_bar.bind('<B1-Motion>', move_window)
root.mainloop()
I have been using the tkinter scrollbar for my project. But, when I use the properties troughcolor, the color of the scrollbar doesn't change. So, I want to make a custom scrollbar for tkinter and python that can be used to scroll through a frame. I then will add color to this custom scrollbar. Is there any way to do so? Here is my code:
root=Tk()
container = ttk.Frame(root)
canvas = Canvas(container, highlightbackground="black", highlightthickness=1, bg="black", width=400, height=600)
scrollbar = Scrollbar(container, orient="vertical", command=canvas.yview, troughcolor="red")
scrollable_frame = ttk.Frame(canvas)
scrollable_frame.bind(
"<Configure>",
lambda e: canvas.configure(
scrollregion=canvas.bbox("all")
)
)
canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
canvas.configure(yscrollcommand=scrollbar.set)
container.pack()
canvas.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
root.mainloop()
Based on Bryan Oakley's comment and a variety of his posts on SO, and several days of work on my part, here is a pretty complete answer to the question. This scrollbar is configurable and can be optionally hidden when not needed. I am a beginner and this can be improved, but it seems to work OK. I left my comments in, as well as Bryan Oakley's from an original post where he showed part of this code. This was not easy for me to do and the comments might help someone understand it better. I wrote this code a few weeks ago and it's working fine so far.
import tkinter as tk
'''
Much of this code was elucidated by Bryan Oakley on StackOverflow.com.
Without his explanations and examples, I would not have figured out how to
create a configurable Tkinter scrollbar. Any mistakes in this code are mine
of course.
I didn't add the little arrows at the ends of the trough.
'''
class Scrollbar(tk.Canvas):
'''
A scrollbar is gridded as a sibling of what it's scrolling.
'''
def __init__(self, parent, orient='vertical', hideable=False, **kwargs):
print('kwargs is', kwargs)
'''
kwargs is {
'width': 17,
'command': <bound method YView.yview of
<widgets.Text object .!canvas.!frame.!frame.!text>>}
https://stackoverflow.com/questions/15411107
You can use dict.pop:... delete an item in a dictionary only if the given key exists... not certain if key exists in the dictionary...
mydict.pop("key", None)
...if the second argument, None is not given, KeyError is raised if the key is not in the dictionary. Providing the second argument prevents the conditional exception... the second argument to .pop() is what it returns if the key is not found.
'''
self.command = kwargs.pop('command', None)
print('self.command is', self.command)
tk.Canvas.__init__(self, parent, **kwargs)
self.orient = orient
self.hideable = hideable
self.new_start_y = 0
self.new_start_x = 0
self.first_y = 0
self.first_x = 0
self.slidercolor = 'steelblue'
self.troughcolor = 'lightgray'
self.config(bg=self.troughcolor, bd=0, highlightthickness=0)
# coordinates are irrelevant; they will be recomputed
# in the 'set' method
self.create_rectangle(
0, 0, 1, 1,
fill=self.slidercolor,
width=2, # this is border width
outline='teal',
tags=('slider',))
self.bind('<ButtonPress-1>', self.move_on_click)
self.bind('<ButtonPress-1>', self.start_scroll, add='+')
self.bind('<B1-Motion>', self.move_on_scroll)
self.bind('<ButtonRelease-1>', self.end_scroll)
def set(self, lo, hi):
'''
For resizing & repositioning the slider. The hideable
scrollbar portion is by Fredrik Lundh, one of Tkinter's authors.
'''
lo = float(lo)
hi = float(hi)
if self.hideable is True:
if lo <= 0.0 and hi >= 1.0:
self.grid_remove()
return
else:
self.grid()
height = self.winfo_height()
width = self.winfo_width()
if self.orient == 'vertical':
x0 = 2
y0 = max(int(height * lo), 0)
x1 = width - 2
y1 = min(int(height * hi), height)
# This was the tricky part of making a horizontal scrollbar
# when I already knew how to make a vertical one.
# You can't just change all the "height" to "width"
# and "y" to "x". You also have to reverse what x0 etc
# are equal to, comparing code in if and elif. Till that was
# done, everything worked but the horizontal scrollbar's
# slider moved up & down.
elif self.orient == 'horizontal':
x0 = max(int(width * lo), 0)
y0 = 2
x1 = min(int(width * hi), width)
y1 = height
self.coords('slider', x0, y0, x1, y1)
self.x0 = x0
self.y0 = y0
self.x1 = x1
self.y1 = y1
def move_on_click(self, event):
if self.orient == 'vertical':
# don't scroll on click if mouse pointer is w/in slider
y = event.y / self.winfo_height()
if event.y < self.y0 or event.y > self.y1:
self.command('moveto', y)
# get starting position of a scrolling event
else:
self.first_y = event.y
elif self.orient == 'horizontal':
# do nothing if mouse pointer is w/in slider
x = event.x / self.winfo_width()
if event.x < self.x0 or event.x > self.x1:
self.command('moveto', x)
# get starting position of a scrolling event
else:
self.first_x = event.x
def start_scroll(self, event):
if self.orient == 'vertical':
self.last_y = event.y
self.y_move_on_click = int(event.y - self.coords('slider')[1])
elif self.orient == 'horizontal':
self.last_x = event.x
self.x_move_on_click = int(event.x - self.coords('slider')[0])
def end_scroll(self, event):
if self.orient == 'vertical':
self.new_start_y = event.y
elif self.orient == 'horizontal':
self.new_start_x = event.x
def move_on_scroll(self, event):
# Only scroll if the mouse moves a few pixels. This makes
# the click-in-trough work right even if the click smears
# a little. Otherwise, a perfectly motionless mouse click
# is the only way to get the trough click to work right.
# Setting jerkiness to 5 or more makes very sloppy trough
# clicking work, but then scrolling is not smooth. 3 is OK.
jerkiness = 3
if self.orient == 'vertical':
if abs(event.y - self.last_y) < jerkiness:
return
# scroll the scrolled widget in proportion to mouse motion
# compute whether scrolling up or down
delta = 1 if event.y > self.last_y else -1
# remember this location for the next time this is called
self.last_y = event.y
# do the scroll
self.command('scroll', delta, 'units')
# afix slider to mouse pointer
mouse_pos = event.y - self.first_y
if self.new_start_y != 0:
mouse_pos = event.y - self.y_move_on_click
self.command('moveto', mouse_pos/self.winfo_height())
elif self.orient == 'horizontal':
if abs(event.x - self.last_x) < jerkiness:
return
# scroll the scrolled widget in proportion to mouse motion
# compute whether scrolling left or right
delta = 1 if event.x > self.last_x else -1
# remember this location for the next time this is called
self.last_x = event.x
# do the scroll
self.command('scroll', delta, 'units')
# afix slider to mouse pointer
mouse_pos = event.x - self.first_x
if self.new_start_x != 0:
mouse_pos = event.x - self.x_move_on_click
self.command('moveto', mouse_pos/self.winfo_width())
def colorize(self):
print('colorize')
self.slidercolor = 'blue'
self.troughcolor = 'bisque'
self.config(bg=self.troughcolor)
if __name__ == '__main__':
def resize_scrollbar():
root.update_idletasks()
canvas.config(scrollregion=canvas.bbox('all'))
def resize_window():
root.update_idletasks()
page_x = content.winfo_reqwidth()
page_y = content.winfo_reqheight()
root.geometry('{}x{}'.format(page_x, page_y))
root = tk.Tk()
root.config(bg='yellow')
root.grid_columnconfigure(0, weight=1)
root.grid_rowconfigure(0, weight=1)
root.grid_rowconfigure(1, weight=0)
canvas = tk.Canvas(root, bg='tan')
canvas.grid(column=0, row=0, sticky='news')
content = tk.Frame(canvas)
content.grid_columnconfigure(0, weight=1)
content.grid_rowconfigure(0, weight=1)
ysb_canv = Scrollbar(root, width=24, hideable=True, command=canvas.yview)
xsb_canv = Scrollbar(root, height=24, hideable=True, command=canvas.xview, orient='horizontal')
canvas.config(yscrollcommand=ysb_canv.set, xscrollcommand=xsb_canv.set)
frame = tk.Frame(content)
frame.grid_columnconfigure(0, weight=0)
frame.grid_rowconfigure(0, weight=1)
text = tk.Text(frame, bd=0)
ysb_txt = Scrollbar(frame, width=17, command=text.yview)
text.config(yscrollcommand=ysb_txt.set)
space = tk.Frame(content, width=1200, height=500)
ysb_canv.grid(column=1, row=0, sticky='ns')
xsb_canv.grid(column=0, row=1, sticky='ew')
frame.grid(column=0, row=0, sticky='news')
text.grid(column=0, row=0)
ysb_txt.grid(column=1, row=0, sticky='ns')
space.grid(column=0, row=1)
with open(__file__, 'r') as f:
text.insert('end', f.read())
canvas.create_window(0, 0, anchor='nw', window=content)
resize_scrollbar()
resize_window()
root.mainloop()
I wrote a simple python code which creates canvas and items in canvas can be moved freely using mouse only. Also, previous shapes created in canvas can be moved. However, I have some problems here:
When moving items like 1 rect. 1 circle, somehow they overlap or one of them completely unseen in canvas.
My delete button works but I can't do anything after using delete button. System gives error. (I think I couldn't make object_id value 0 because after reset, I add rect. and object_id doesn't start from 0, and interestingly it has no coord?? )
When I try to drag shapes in canvas, mouse automatically picks top left corner of the shape, how can I change it? (Maybe moving shapes from the point that I try to click with mouse)
My code:
from tkinter import *
from tkinter import messagebox
def click(event):
my_label.config(text="Coordinates: x: "+ str(event.x) +" y: " +str(event.y))
if object_id is not None:
for i in range(object_id):
coord = my_canvas.coords(i+1) # +1 added because for loop starts from 0
print(coord)
print(object_id)
if ((event.x<=coord[2]+10) and (event.x>=coord[0]-10) and (event.y<=coord[3]+10) and
(event.y>=coord[1])-10):
print(coord)
width = coord[2] - coord[0]
height = coord[3] - coord[1]
my_canvas.coords(i+1, event.x, event.y, event.x+width, event.y+height)
else:
pass
else:
pass
def label_mod(event): #While not pressing button, show coords!
my_label.config(text="Coordinates: x: "+ str(event.x) +" y: " +str(event.y))
def delete():
global object_id #Delete butonunda sistem fail oluyor
msg = messagebox.askyesnocancel('Info', 'Delete my_canvas ?')
if msg == True:
my_canvas.delete(ALL)
print(object_id)
object_id=0
print(object_id)
def create_rectangle():
global object_id
object_id=my_canvas.create_rectangle(10, 10, 70, 70, fill='white', outline='blue', width=3)
def create_line():
global object_id
object_id=my_canvas.create_line(200, 200, 100, 100, fill='red', width=5)
def create_circle():
global object_id
object_id=my_canvas.create_oval(10, 10, 70, 70, fill='orange', outline='blue')
# Main Codes
object_id = 0
root = Tk()
root.title('Moving objects')
root.resizable(width=False, height=False)
root.geometry('1200x600+100+50')
root.configure(bg='light green')
my_label=Label(root, text="")
my_label.pack()
my_canvas = Canvas(root, bg='white', height=500, width=500)
my_canvas.pack(side=RIGHT)
my_canvas.bind("<B1-Motion>", click)
my_canvas.bind("<Motion>", label_mod)
btn_line = Button(root, text='Line', width=30, command=create_line)
btn_line.pack()
btn_rectangle = Button(root, text='Rectangle', width=30, command=create_rectangle)
btn_rectangle.pack()
btn_circle = Button(root, text='Circle', width=30, command=create_circle)
btn_circle.pack()
btn_delete = Button(root, text='Delete', width=30, command=delete)
btn_delete.pack()
root.mainloop()
You don't need object_id at all. You can use my_canvas.find_overlapping() to select the canvas item you want, and use my_canvas.move() to move the selected item:
def on_click(event):
selected = my_canvas.find_overlapping(event.x-10, event.y-10, event.x+10, event.y+10)
if selected:
my_canvas.selected = selected[-1] # select the top-most item
my_canvas.startxy = (event.x, event.y)
print(my_canvas.selected, my_canvas.startxy)
else:
my_canvas.selected = None
def on_drag(event):
if my_canvas.selected:
# calculate distance moved from last position
dx, dy = event.x-my_canvas.startxy[0], event.y-my_canvas.startxy[1]
# move the selected item
my_canvas.move(my_canvas.selected, dx, dy)
# update last position
my_canvas.startxy = (event.x, event.y)
def delete():
msg = messagebox.askyesnocancel('Info', 'Delete my_canvas ?')
if msg == True:
my_canvas.delete(ALL)
def create_rectangle():
my_canvas.create_rectangle(10, 10, 70, 70, fill='white', outline='blue', width=3)
def create_line():
my_canvas.create_line(200, 200, 100, 100, fill='red', width=5)
def create_circle():
my_canvas.create_oval(10, 10, 70, 70, fill='orange', outline='blue')
...
my_canvas.bind("<Button-1>", on_click)
my_canvas.bind("<B1-Motion>", on_drag)
from tkinter import Tk, Label, PhotoImage, TkVersion, Frame, Canvas
def up(event):
w.move(car, 0, -10)
root = Tk()
root.geometry("800x800")
w = Canvas(root, width=800, height=800)
img = PhotoImage(file="track.png")
track = w.create_image(0, 0, image=img, anchor='nw')
img_2 = PhotoImage(file="car.png")
car = w.create_image(380, 380, image=img_2, anchor='nw')
w.pack()
root.bind("<Up>", up)
root.mainloop()
I have a simple tkinter program here that moves a car image up the screen when the up arrow key is pressed. However, when I run the program, I can visibly see the lag, as each frame is animated one by one. I never had this kind of issue in other graphics programming like SDL in C++ - the movement was always very smooth. What could be going on here?
Fixed using dictionary and after:
from tkinter import Tk, Label, PhotoImage, TkVersion, Frame, Canvas
keys = {
"U": False,
"L": False,
"D": False,
"R": False
}
def up(event):
keys["U"] = True
def noup(event):
keys["U"] = False
def left(event):
keys["L"] = True
def noleft(event):
keys["L"] = False
def down(event):
keys["D"] = True
def nodown(event):
keys["D"] = False
def right(event):
keys["R"] = True
def noright(event):
keys["R"] = False
def move_car():
if keys["U"]:
w.move(car, 0, -5)
if keys["L"]:
w.move(car, -5, 0)
if keys["D"]:
w.move(car, 0, 5)
if keys["R"]:
w.move(car, 5, 0)
root.after(10, move_car)
root = Tk()
root.geometry("800x800")
w = Canvas(root, width=800, height=800)
img = PhotoImage(file="track.png")
track = w.create_image(0, 0, image=img, anchor='nw')
img_2 = PhotoImage(file="car.png")
car = w.create_image(380, 380, image=img_2, anchor='nw')
w.pack()
root.bind("<KeyPress-Up>", up)
root.bind("<KeyRelease-Up>", noup)
root.bind("<KeyPress-Left>", left)
root.bind("<KeyRelease-Left>", noleft)
root.bind("<KeyPress-Down>", down)
root.bind("<KeyRelease-Down>", nodown)
root.bind("<KeyPress-Right>", right)
root.bind("<KeyRelease-Right>", noright)
root.after(0, move_car)
root.mainloop()