I am trying to use zoom in, zoom out feature in python. I have previously tried to use the functionality to zoom in a line in turtle, canvas, etc. but nothing seem to work out, instead of zooming, the code is either increasing or decreasing the length of the line. I want to zoom in the line to add text on the line so that when a user zoom's in the line he/she can see the text. here is the code which I am trying to change.
from tkinter import *
root = Tk()
Label(root).pack()
canvas = Canvas(root, width=400, height=400)
canvas.pack(fill=BOTH, expand=1)
widget = Button(None, text='zoomin-out')
widget.pack()
canvas.create_line(175,175,225,225)
def zoomin(event):
d = event.delta
if d < 0:
amt=0.9
else:
amt=1.1
canvas.scale(ALL, 200,200, amt, amt)
widget.bind('<Button-1>', zoomin)
def zoomout(event):
d = event.delta
if d >0:
amt=1.1
else:
amt=0.7
canvas.scale(ALL, 200,200 , amt, amt)
widget.bind('<Double-1>', zoomout)
widget.mainloop()
root.mainloop()
There are two issues. First, when you add both the Button-1 and Double-1 events to your Button widget, doing a double-click fires both events. They end up cancelling each other out, so only the single-click works as expected.
Second, as I pointed out in this SO answer, certain elements, like text, won't zoom, they'll remain fixed. You'll need to manually scale your fonts to simulate text zoom.
Below's a rework of your code along the above lines. Instead of the problematic single and double click, I've changed it so that left and right clicks on the button cause the canvas to zoom in or out:
from tkinter import *
EXAMPLE_TEXT = "Left or Right click button to zoom in/out"
FONT_NAME = "Helvetica"
font_size = 12
def zoom(amount):
global font_size
canvas.scale(ALL, 200, 200, amount, amount)
font_size *= amount
canvas.itemconfigure(text_item, font=(FONT_NAME, int(font_size)))
root = Tk()
canvas = Canvas(root, width=400, height=400)
canvas.pack(fill=BOTH, expand=1)
text_item = canvas.create_text(200, 200, font=(FONT_NAME, font_size), text=EXAMPLE_TEXT)
canvas.create_oval(50, 50, 350, 350)
widget = Button(root, text='zoom in/out')
widget.pack()
widget.bind('<Button-1>', lambda e: zoom(1.1))
widget.bind('<Button-2>', lambda e: zoom(0.7))
root.mainloop()
If you comment out the line that starts with canvas.itemconfigure(...), you'll see that the circle continues to zoom in and out, but the text remains fixed size.
Related
So, I'm trying to draw vertical lines in canvas on the click of the button "line".
These are the problems and my requirements:
When i try click on the line drawn to drag it to a position, it repels away from the mouse cursor but moves in the correct direction. What do i do to prevent this?
On subsequent clicks on the "line" button, i want a new line to be drawn (every time i click) while the original lines stays in the canvas unmoved.
The latest line is the only one which can be dragged. All other lines should be static.
I want coordinates of all these drawn lines. How do i get these?
This is the code i've written:
from tkinter import *
import tkinter
root = Tk()
canvas = tkinter.Canvas(root, width = 480,height = 600)
canvas.pack()
def draw_lines():
canvas.create_line(300, 35, 300, 200, dash=(4, 2))
def drag(event):
event.widget.place(x=event.x_root, y=event.y_root,anchor=CENTER)
canvas.bind("<B1-Motion>", drag)
btn1 = Button(root, text = 'line', bd = '5',command = draw_lines)
btn2 = Button(root, text = 'Close', bd = '5',command = root.destroy)
btn1.pack(side = 'top')
btn2.pack(side = 'top')
canvas.mainloop()
please help!!!!
Using Python 3.11.0rc1.
I added:
geometry. so it will not resize when dragging.
Canvas. You have to play around with height set to 800
I removed bd=5. You can suit yourself. Don't use quote.
I removed Quotes for this btn1.pack(side=TOP)
At last canvas.pack() always next to mainloop()
Here is the code re-worked:
from tkinter import *
root = Tk()
root.geometry('400x650')
canvas = Canvas(root, width=480, height=600)
def draw_lines():
canvas.create_line(300, 35, 300, 200, dash=(4, 2))
def drag(event):
event.widget.place(x=event.x_root, y=event.y_root,anchor=CENTER)
canvas.bind("<B1-Motion>", drag)
btn1 = Button(root, text='line', command=draw_lines)
btn2 = Button(root, text= 'Close', command=root.destroy)
btn1.pack(side=TOP)
btn2.pack(side=TOP)
canvas.pack()
canvas.mainloop()
Result output:
I have a Tkinter canvas and bound a mouse button press event to it.
I was wondering if I could figure out on what specific shape a user clicked when bound to a canvas:
def callback(event):
pass
canvas = Canvas(root, width=100, height=100)
canvas.create_rectangle(10,50,40,90, tags="tile")
canvas.bind("<Button-1>", callback)
I know I can bind it to the the rectangle, the problem is that there might be a another shape overlaying the rectangle and then the click event doesn't work anymore.
I was thinking of using the find_overlapping method:
def callback(event):
canvas.find_overlapping(event.x, event.y,event.x, event.y)
but was wondering if there is an easier way?
You can use the special item tag current to refer to the object under the cursor. According to the official documentation:
The tag current is managed automatically by Tk; it applies to the current item, which is the topmost item whose drawn area covers the position of the mouse cursor (different item types interpret this in varying ways; see the individual item type documentation for details). If the mouse is not in the canvas widget or is not over an item, then no item has the current tag.
Here is a simple example. It draws a bunch of colored rectangles, and then sets the color to white when you click on them.
import tkinter as tk
import random
def click_handler(event):
event.widget.itemconfigure("current", fill="white")
root = tk.Tk()
canvas = tk.Canvas(root, bg="bisque", width=400, height=400)
canvas.pack(fill="both", expand=True)
canvas.bind("<1>", click_handler)
for i in range(100):
x = random.randint(0, 350)
y = random.randint(0, 350)
color = random.choice(("red", "orange", "green", "blue"))
width = random.randint(25, 50)
height = random.randint(25, 50)
canvas.create_rectangle(x, y, x+width, y+height, fill=color)
root.mainloop()
I am having some trouble with a basic GUI I am trying to create. The goal is to have a button that generates a certain amount of tiles based off of a number in a spin box. I have gotten it to work mostly and clicking the 'generate' button will generate a new number of tiles if the spin box value is changed to a larger number, as expected, however if I change the spin box to a smaller number, the old tile generation can be seen behind the new. I would expect that the canvas.delete('all') to take of the previous canvas objects.
I have tried to solve this problem by not redefining the canvas inside of the function which I have a feeling is causing the problem, but then I am not able to redraw the canvas boundaries so the maximum number of tiles that can be generated will be constrained by the initially drawn canvas size. Normally I would think that I could return canvas from the function, but since it is attached to the button through the command functionality and a lambda function, I am not sure how to go about doing that.
import tkinter as tk
def generate_start(window, canvas, num_tiles):
canvas.delete('all')
canvas = tk.Canvas(window, width=40*num_tiles, height=40)
canvas.grid(row=1, columnspan=num_tiles)
for i in range(num_tiles):
tile_width = 40
x1 = i * tile_width
y1 = 0
x2 = (i + 1)*tile_width - 1
y2 = tile_width - 1
canvas.create_rectangle(x1, y1, x2, y2, fill='blue')
window = tk.Tk()
window.title('GUI')
tk.Label(window, text = "Number of starting tiles:").grid(row=0)
default_start_num = 6
var = tk.IntVar(value=default_start_num)
start_num = tk.Spinbox(window, from_=1, to=100,
textvariable=var)
start_num.grid(row=0, column=1)
canvas = tk.Canvas(window, width=40*default_start_num, height=40)
canvas.grid(row=1, columnspan=default_start_num)
generate_btn = tk.Button(window, text='Generate',
command=lambda: generate_start(window, canvas, int(start_num.get())))
generate_btn.grid(row=0, column=2)
window.mainloop()
Every time you click the button you are creating a new canvas. Thus, when you call canvas.delete('all'), you are deleting them from the old canvas then creating a new canvas.
You need to create the canvas once. That, or delete the old canvas before creating the new canvas.
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()
I want to present several questions, one after another. The first question is shown as I like, with the cursor set in the entry field. Then I destroy the window and call the function again to create a new window. This time the window is not shown in the front and therefore I first have to click on the screen in order to have the cursor set to the entry field. Also the escape key does not work until I click on the screen to bring the window to the top. I'd be very happy for your help!
Thank you in advance!
Here's my code:
from Tkinter import *
def text_input_restricted(fn,question, nr_letters, limit, len_min, len_max,keys, justify):
class MyApp():
def validate(root, S):
return all(c in keys for c in S)
def __init__(self, q= None):
#save response after "next"-button has been clicked
def okClicked():
lines = e.get()
if len_min < len(lines) < len_max:
lines = unicode(lines).encode('utf-8')
datFile = open(fn, "a")
datFile.write(" '%s'"%(lines))
datFile.close()
self.root.destroy()
self.root = Tk()
vcmd = (self.root.register(self.validate), '%S')
#quit if escape-key has been pressed
self.root.bind('<Escape>', lambda q: quit())
#colors
color = '#%02x%02x%02x' % (200, 200, 200)
self.root.configure(bg=color)
#set window size to screen size
RWidth=MAXX
RHeight=MAXY
self.root.geometry(("%dx%d")%(RWidth,RHeight))
#remove buttons (cross, minimize, maximize)
self.root.overrideredirect(1)
#remove title
self.root.title("")
#item
labelWidget = Label(self.root,text=question, font=("Arial", int(0.02*MAXX)), bd=5, bg=color, justify="center")
labelWidget.place(x=0, y=RHeight/40,width=RWidth)
#"next"-button
ok_width = RWidth/15
ok_height = RWidth/15
okWidget = Button(self.root, text= "next", command = okClicked, font=("Arial",int(0.015*MAXX)), bd=5, justify="center")
okWidget.place(x=RWidth/2-ok_width/2,y=13*RHeight/40, width=ok_width,height=ok_height)
def callback(sv):
c = sv.get()[0:limit]
sv.set(c)
sv = StringVar()
width=nr_letters * int(0.02*MAXX)*1.3
sv.trace("w", lambda name, index, mode, sv=sv: callback(sv))
e = Entry(self.root, textvariable=sv,font=("Arial", int(0.02*MAXX)),justify=justify,validate="key", validatecommand=vcmd)
e.place(x=RWidth/2-width/2, y=9*RHeight/40, width=width)
#show cursor
e.focus_set()
self.root.mainloop()
MyApp()
MAXX=1366
MAXY=768
fn = "D:/test.dat"
text_input_restricted(fn = fn, question=u"f for female, m for male", nr_letters=1, limit =1, len_min =0, len_max=2, keys = 'fm', justify="center")
text_input_restricted(fn = fn, question="How old are you?", nr_letters=2,limit=2, len_min = 1, len_max = 3, keys = '1234567890',justify="center")
In Tk you use the raise command to bring a window to the front of the Z-order. However, raise is a keyword in Python so this has been renamed to lift. Provided your application is still the foreground application you can call the lift() method on a toplevel widget. If the application is not the foreground application then this will raise the window but only above other windows from the same application. On Windows this causes the taskbar icon for your application to start flashing.
You might do better to destroy the contents of the toplevel and replace them. Or even better - create a number of frames holding each 'page' of your application and toggle the visibility of each frame by packing and pack_forgetting (or grid and grid forget). This will avoid loosing the focus completely - you can just set the focus onto the first widget of each frame as you make it visible.