Tkinter : Make canvas draggable - python

i am creating a project that involves making a RawTurtle on a canvas. And I was wondering what would I do if the drawing is out of the screen. Can anyone tell me how to make the Tkinter Canvas widget draggable ?
from tkinter import *
root = Tk()
c = Canvas(root)
t = RawTurtle(c)
....# What can i do to have a drag function
root.mainloop()

this is an answer for python 3, but change the imports and it should work fine with python 2
#!python3
import tkinter as tk
import turtle
def run_turtles(*args):
for t, d in args:
t.circle(200, d)
root.after_idle(run_turtles, *args)
def scroll_start(event):
screen.scan_mark(event.x, event.y)
def scroll_move(event):
screen.scan_dragto(event.x, event.y, gain=1)
root = tk.Tk()
root.geometry("700x700")
root.withdraw()
frame = tk.Frame(bg='black')
frame.pack(fill='both', expand=True)
tk.Label(frame, text=u'Hello', bg='grey', fg='white').pack(fill='x')
screen = turtle.ScrolledCanvas(frame)
screen.pack(fill="both", expand=True)
turtle1 = turtle.RawTurtle(screen)
turtle2 = turtle.RawTurtle(screen)
screen.bind("<ButtonPress-1>", scroll_start)
screen.bind("<B1-Motion>", scroll_move)
turtle1.ht(); turtle1.pu()
turtle1.left(90); turtle1.fd(200); turtle1.lt(90)
turtle1.st(); turtle1.pd()
turtle2.ht(); turtle2.pu()
turtle2.fd(200); turtle2.lt(90)
turtle2.st(); turtle2.pd()
root.deiconify()
run_turtles((turtle1, 3), (turtle2, 4))
root.mainloop()
worth noting:
for some reason once a turtle is added the canvas bindings stop working, the best fix for this is to add the bindings after adding the turtles. alternatively you can bind to the top window instead.
the canvas can only be panned to the edge of its scroll region, if you want to pan further you'll need to make this bigger.

Related

Scrollbar in tkinter doesn't appear (using .place)

I am trying to add a scrollbar to my GUI and found a pretty nice tutorial on youtube. Even when he uses pack in the code, there is a comment which translates the code to make it able to use it with place. I made a few adaptions but it didn't work, only the (non-functional) arrows and the background of the scrollbar appear as you can see here. I've tried some other solutions in SO like
Scrollbar in Canvas doesn't work - Tkinter Python
Tkinter scrollbar not appearing
So here is what I tried so far:
import tkinter as tk
from tkinter import ttk
window1.destroy() # previous window where user enters meta data
input_window= tk.Tk()
input_window.geometry(str(width) + 'x' + str(height)) # height=840, width=480
# Adding a Full Screen ScrollBar - Python Tkinter GUI Tutorial #96 https://youtu.be/0WafQCaok6g">
# Create canvas
my_canvas = tk.Canvas(input_window, width=width, height=height)
my_canvas.place(x=0, y=0)
# creating frame
main_frame = tk.Frame(my_canvas, width=width, height=height)
main_frame.place(x=0, y=0)
# Add A Scrollbar To The Canvas
my_scrollbar = ttk.Scrollbar(input_window, orient='vertical', command=my_canvas.yview)
my_scrollbar.place(x=width-20, y=0, height=height)
# Configure The Canvas
my_canvas.configure(yscrollcommand=my_scrollbar.set)
my_canvas.bind('<Configure>', lambda e: my_canvas.configure(scrollregion=my_canvas.bbox("all")))
def _on_mouse_wheel(event): # scrolling mousewheel
my_canvas.yview_scroll(-1 * int((event.delta / 120)), "units")
my_canvas.bind_all("<MouseWheel>", _on_mouse_wheel)
# Create ANOTHER Frame INSIDE the Canvas
second_frame = tk.Frame(my_canvas, width=width, height=height)
# Add that New frame To a Window In The Canvas
my_canvas.create_window((0, 0), window=second_frame, anchor="nw")
...
window1.mainloop() # previous window
My guess is, that the window is larger than the height of 840 because I can only see the upper arrow of the grey scrollbar. If I enlarge the window I can see the lower arrow of the gray scrollbar but the bar never appears.
Thanks in advance for your help!

Changing the color of Tkinter canvas after set period of time

I'm trying to set a Tkinter canvas to red/green for one second, then back to white afterward. However, despite the fact that the code setting the canvas to red/green precedes the code reverting back to white, the window doesn't reflect the initial color change. I understand that by calling .after, the program freezes until the specified duration is over, but I don't understand why it doesn't change to red or green before freezing.
if is_correct:
self.canvas.config(bg="green")
else:
self.canvas.config(bg="red")
self.window.after(1000, self.canvas.config(bg="white"))
Refer to this simple program.
from tkinter import *
root=Tk()
def change_bg():
canvas.config(bg="red")
root.after(1000,lambda: canvas.config(bg="white"))
canvas=Canvas(root,bg="white")
canvas.pack()
root.after(1000,change_bg)
root.mainloop()
from tkinter import *
import time
def change_color():
can.config(bg="red")
can.update()
change_color2()
def change_color2():
time.sleep(1)
can.config(bg="white")
root = Tk()
root.geometry("500x500")
can = Canvas(root, bg="white", height=450, width=500)
can.pack()
Button(root, text="Change color for 1 sec", command=change_color).pack()
root.mainloop()
You can refer to this code

How to speed up the turtle while in a tkinter canvas

I'm trying to make the turtle move faster, which I would normally do using
import turtle as t
t.speed(0)
t.tracer(0,0)
But when I have it in a canvas using RawTurtle(), I'm not sure how to do this.
root = tk.Tk() #create root window
#create turtle canvas
canvas = tk.Canvas(root,width=500,height=500)
canvas.pack()
t = turtle.RawTurtle(canvas)
t.ht()
Here's my code. Anyone know how?
First, either do one or the other of these, not both:
t.speed(0)
t.tracer(0,0)
The speed(0) aka speed('fastest') gets you the fastest drawing animation. The tracer(0) eliminates drawing animation altogether. They don't add together.
You can get maximum speed, i.e. eliminate drawing animation, in turtle embedded in a Canvas by using turtle's TurtleScreen() wrapper:
import tkinter as tk
from turtle import TurtleScreen, RawTurtle
root = tk.Tk()
canvas = tk.Canvas(root, width=500, height=500)
canvas.pack()
screen = TurtleScreen(canvas)
screen.tracer(False)
turtle = RawTurtle(screen)
turtle.circle(100)
screen.tracer(True)
screen.mainloop()
I made a few edits to your code. For starters, you need to work all the commands after the t = turtle.RawTurtle(canvas) line. Then you need to add a .mainloop() function at the end.
This would be your final code:
import tkinter as tk
root = tk.Tk() #create root window
import turtle
#create turtle canvas
canvas = tk.Canvas(root,width=500,height=500)
canvas.pack()
t = turtle.RawTurtle(canvas)
t.speed(1)
t.forward(100)
t.ht()
root.mainloop()
When the speed is set to 1, this is the output:
When the speed is set to 0, this is the output:
Sorry about the gifs, and hope this helps!

How to find out the width of a tkinter window without an update?

I want to center a tkinter window on the screen, which can be done with:
root.geometry(f"+{(root.winfo_screenwidth()-root.winfo_width())//2}+"
f"{(root.winfo_screenheight()-root.winfo_height())//2}")
This is using the screen width and the width of the window to calculate the upper left corner. However, in order to find out the window width, I have to run root.update() as shown in the following example, which leads to the window showing up at a wrong position for a tiny moment.
import tkinter as tk
root = tk.Tk()
for i in range(20):
tk.Label(root, text='Hello World! '*5).pack()
# without the following line, the window dimensions are not being calculated
root.update()
root.geometry(f"+{(root.winfo_screenwidth()-root.winfo_width())//2}"
f"+{(root.winfo_screenheight()-root.winfo_height())//2}")
root.mainloop()
To avoid this, I can think of two solutions:
defining the window size in pixels, which means that the window size does not adjust automatically anymore, and
doing something like root.update() without the window being visible.
I don't know how to avoid the call to update(), but you could initially make the window completely transparent, which would prevent it from even momentarily showing up in the wrong position — thereby granting you the opportunity to position it properly and manually making it visible.
import tkinter as tk
root = tk.Tk()
# This changes the alpha value (how transparent the window should be).
# It ranges from 0.0 (completely transparent) to 1.0 (completely opaque).
root.attributes("-alpha", 0.0)
for i in range(20):
tk.Label(root, text='Hello World! '*5).pack()
tk.Button(root, text='Quit', command=root.quit).pack()
root.update() # Allow the window's size to be calculated,
# Move it so it's centered on the screen.
root.geometry(f"+{(root.winfo_screenwidth()-root.winfo_width())//2}"
f"+{(root.winfo_screenheight()-root.winfo_height())//2}")
# Now make it visible.
root.attributes("-alpha", 1.0)
root.mainloop()
The simplest way to achieve a clean window display is shown in the following code.
withdraw the master immediately, create widgets, update master before centering window and finally deiconify.
Works every time.
import tkinter as tk
def screencenter(o):
w, h = o.winfo_width(), o.winfo_height()
x = int((o.winfo_screenwidth() - w) / 2)
y = int((o.winfo_screenheight() - h) / 2)
o.geometry(f'{w}x{h}+{x}+{y}')
master = tk.Tk()
master.withdraw()
# create whatever widgets you need
master.update()
screencenter(master)
master.deiconify()
master.mainloop()
What you're looking for is root.withdraw() and root.deiconify().
The former will hide your window from view and the latter will show it.
I've included a full example below.
from tkinter import Tk
def show_it():
height = root.winfo_height()
width = root.winfo_width()
root.geometry(f"+{(s_width - width)//2}+"
f"{(s_height - height)//2}")
# show it again
root.deiconify()
root = Tk()
s_height = root.winfo_screenheight()
s_width = root.winfo_screenwidth()
root.withdraw()
# hide the window
root.after(200, show_it)
root.mainloop()

Scrolling through multiple labels in Tkinter

I am trying to make a simple application launcher using Tkinter ( for the games that I have made using pygame). The code for the same is below.It runs in full screen mode(without any maximize or minimize buttons).
import Tkinter as tk
from Tkinter import *
import random
import os
import subprocess
def call(event,x):
print "begin"
if x==0:
p = subprocess.Popen("python C:\Users\Akshay\Desktop\i.py")
p.wait()
print "end"
root = tk.Tk()
root.geometry("1368x768+30+30")
root.overrideredirect(True)
root.geometry("{0}x{1}+0+0".format(root.winfo_screenwidth(), root.winfo_screenheight()))
games = ['Game1','Game2','Game3','Game4','Game5','Game6','Game7','Game8']
labels = range(8)
for i in range(8):
ct = [random.randrange(256) for x in range(3)]
brightness = int(round(0.299*ct[0] + 0.587*ct[1] + 0.114*ct[2]))
ct_hex = "%02x%02x%02x" % tuple(ct)
bg_colour = '#' + "".join(ct_hex)
l = tk.Label(root,
text=games[i],
fg='White' if brightness < 120 else 'Black',
bg=bg_colour)
l.place(x = 320, y = 30 + i*150, width=700, height=100)
l.bind('<Button-1>', lambda event, arg=i: call(event, arg))
root.mainloop()
There is no issue with this piece of code but I want a scroll bar at the right side or a way to scroll/move down using the arrow keys so that if I add more number of labels , they also become visible.
I tried to understand some code snippets from the internet and read the Tkinter documentations as well but didn't understood anything. Also tried to follow one more stackoverflow discussion and understood about the frame and canvas methods.
Python Tkinter scrollbar for frame
Frame , canvas and all is getting a bit complicated. I just want to keep it simple.Can something be added to the code snippet above to make the scroll thing work and all the labels become visible?
Something like the above but with a scroll bar!!
Here is an MCVE of how to add a scrollbar to a tkinter app, hide the scrollbar, and scroll with the up/down arrows or the mouse wheel.
from tkinter import *
parent=Tk() # parent object
canvas = Canvas(parent, height=200) # a canvas in the parent object
frame = Frame(canvas) # a frame in the canvas
# a scrollbar in the parent
scrollbar = Scrollbar(parent, orient="vertical", command=canvas.yview)
# connect the canvas to the scrollbar
canvas.configure(yscrollcommand=scrollbar.set)
scrollbar.pack(side="right", fill="y") # comment out this line to hide the scrollbar
canvas.pack(side="left", fill="both", expand=True) # pack the canvas
# make the frame a window in the canvas
canvas.create_window((4,4), window=frame, anchor="nw", tags="frame")
# bind the frame to the scrollbar
frame.bind("<Configure>", lambda x: canvas.configure(scrollregion=canvas.bbox("all")))
parent.bind("<Down>", lambda x: canvas.yview_scroll(3, 'units')) # bind "Down" to scroll down
parent.bind("<Up>", lambda x: canvas.yview_scroll(-3, 'units')) # bind "Up" to scroll up
# bind the mousewheel to scroll up/down
parent.bind("<MouseWheel>", lambda x: canvas.yview_scroll(int(-1*(x.delta/40)), "units"))
labels = [Label(frame, text=str(i)) for i in range(20)] # make some Labels
for l in labels: l.pack() # pack them
parent.mainloop() # run program
This is a better answer to my question.Yes I know I can't make the scroll bars work properly but yes as I asked now through this code one can move down/scroll via arrow keys without a scroll bar actually being there. This is the way I used to make menu for my pygame made games and the same technique I applied here.It works but only in fullscreen mode.
import Tkinter as tk
from Tkinter import *
import random
import os
import subprocess
class stat:
j=0
def call(event,x):
print "begin"
if x==0:
p = subprocess.Popen("python C:\Users\Akshay\Desktop\i.py")
p.wait()
print "end"
def OnEntryDown(event):
stat.j=stat.j+1
print stat.j
xmain()
def OnEntryUp(event):
stat.j=stat.j-1
print stat.j
xmain()
def xmain():
root = tk.Tk()
root.geometry("1368x768+30+30")
root.overrideredirect(True)
root.geometry("{0}x{1}+0+0".format(root.winfo_screenwidth(), root.winfo_screenheight()))
root.bind("<Down>", OnEntryDown)
root.bind("<Up>", OnEntryUp)
languages = ['Game1','Game2','Game3','Game4','Game5','Game6','Game7','Game8']
labels = range(8)
k=0
print stat.j
for i in range(stat.j,stat.j+5):
ct = [random.randrange(256) for x in range(3)]
brightness = int(round(0.299*ct[0] + 0.587*ct[1] + 0.114*ct[2]))
ct_hex = "%02x%02x%02x" % tuple(ct)
bg_colour = '#' + "".join(ct_hex)
l = tk.Label(root,
text=languages[i],
fg='White' if brightness < 120 else 'Black',
bg=bg_colour)
l.place(x = 320, y = 30 + k*150, width=700, height=100)
l.bind('<Button-1>', lambda event, arg=stat.j: call(event, arg))
k=k+1
root.mainloop()
xmain()

Categories