Scrolling through multiple labels in Tkinter - python

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()

Related

Mouseover on tkinter button

Is it possible to get the popup option (pop function in the code) on hovering the cursor on the button?
import tkinter as tk
from tkinter import Tk, BOTH, Menu
def pop(bt):
try:
x = bt.winfo_rootx()+238
y = bt.winfo_rooty()+10
popup.tk_popup(x, y, 0)
finally:
popup.grab_release()
root = tk.Tk()
popup = Menu(root, tearoff=0,relief='raised')
popup.add_command(label="About")
popup.add_command(label="User manual")
popup.add_command(label="Contact us")
button1 =tk.Button(root, text="HELP",height=3,width=26,command=lambda: controller.show_frame(HelpPage))
button1.configure(command = lambda: pop(button1))
button1.place(x=0,y=0
)
root.mainloop()
.
button1.bind('<Enter>',pop(button1)) #gives the following output without the mouse cursor over that button.
Try this:
import tkinter as tk
window = None
def leave_window(event):
global window
if 0 < root.winfo_pointerx() - root.winfo_rootx() < button.winfo_width():
if 0 < root.winfo_pointery() - root.winfo_rooty() < button.winfo_height():
# Mouse still over button
return None
if window is not None:
window.destroy()
window = None
def create_window(event):
global window
if window is not None:
# The window is already open
return None
window = tk.Toplevel(root)
window.overrideredirect(True)
label = tk.Label(window, text="Text", bg="black", fg="white")
label.pack()
window.update()
# Move the window to the cursor's
x = root.winfo_pointerx()
y = root.winfo_pointery()-window.winfo_height()
window.geometry("+%i+%i" % (x, y))
window.bind("<Leave>", leave_window)
root = tk.Tk()
button = tk.Button(root, text="-------- Hover the mouse here --------")
button.pack(fill="both", expand=True)
button.bind("<Enter>", create_window)
button.bind("<Leave>", leave_window)
root.mainloop()
I binded to <Enter> and <Leave> to check if the mouse is over the button. If it is, it creates a window with a label called text. You can change it to it showing the menu.
For more answers look here.
Please bind your Button with mouse enter and call pop function.
Check out this code :
button1.bind('<Enter>', pop)
Control your popup location as well as destroy popup on '' case again bind it and call custom function that will destroy popup.
You need <Enter> and <Leave> bind sequences to map and unmap the Menu. Tkinter Menu has two methods post and unpost where post shows the menu at given coordinates, and unpost hides it away. Unfortunately, I couldn't test it as the unpost functionality doesn't work on macOS or Linux [refer to this link for the same]. I also changed the x, y coords to map the Menu in the center of the widget (Button), it can be changed if required.
Here is the complete sample code.
import tkinter as tk
from tkinter import Tk, BOTH, Menu
def pop(evt):
but = evt.widget
if str(evt.type) == "Enter":
# Map the menu in the center of the width.
x = but.winfo_rootx() + int(but.winfo_width()/2)
y = but.winfo_rooty() + int(but.winfo_height()/2)
popup.tk_popup(x, y)
elif str(evt.type) == "Leave":
popup.unpost()
root = tk.Tk()
root.geometry("300x300")
popup = Menu(root, tearoff=0, relief='raised')
popup.add_command(label="About")
popup.add_command(label="User manual")
popup.add_command(label="Contact us")
button1 = tk.Button(root, text="HELP", height=3, width=26)
button1.bind('<Enter>', pop)
button1.bind('<Leave>', pop)
button1.pack(pady=100)
root.mainloop()
Like said, unpost doesn't work on macOS or Linux, so I couldn't test the sample code 100% but it should work fine.

Why isn't the label animation working till the last value of the loop?

I am new to python and I have been learning tkinter recently. So I thought with myself that using the grid_forget() function I can remove a widget and redefine it. I thought of this animation that changes the padding of a label so it would create space (kind of like moving the label but not exactly). However, the animation does not work at all. The program freezes until the label reaches the last value of the padding. How can I fix this? Or is there a better way to animate a label moving in the screen?
Here is my code:
from tkinter import *
import time
root = Tk()
lbl = Label(root, text='------')
lbl.grid(row=0, column=0)
def animation():
padding = 0
while padding < 31:
lbl.grid_forget()
padding += 1
lbl.grid(row=0, column=0, padx=padding)
time.sleep(0.2)
# alternative: root.after(200, lambda: lbl.grid(row=0, column=0, padx=padding))
btn = Button(root, text='Animate', command=animation)
btn.grid(row=1, column=1)
root.mainloop()
You need to update the screen for changes to be shown.
Here is a working version using the .update() method:
from tkinter import *
import time
root = Tk()
lbl = Label(root, text='------')
lbl.grid(row=0, column=0)
def animation():
padding = 0
while padding < 31:
lbl.grid_forget()
padding += 1
lbl.grid(row=0, column=0, padx=padding)
root.update()
time.sleep(0.2)
# alternative: root.after(200, lambda: lbl.grid(row=0, column=0, padx=padding))
btn = Button(root, text='Animate', command=animation)
btn.grid(row=1, column=1)
root.mainloop()
Here is a way I also use to animate stuff on the screen, I am not able to understand what you were trying to achieve with your code snippet above, I tried making some changes to it but I feel this way is much better and let's you get more control of your window.
This uses the widely used Canvas widget in the tkinter library.
The Canvas is a general purpose widget, You can use it for a lot of things. Visit the hyper link for more clarity
Here is a short example of how you would create text on the screen.
from tkinter import *
root = Tk()
root.title("My animation")
c = Canvas(root)
x = 20
y = 20 #Instead of using row and column, you simply use x and y co-ordinates
#We will use these co-ordinates to show where the text is in the starting
my_text = c.create_text(x,y,text = '-----')
c.pack()
# This is all you need to create this text on your screen!
root.mainloop()
The idea is that you put your canvas up on your window , and then place whatever you want on it.
There are a lot more attributes that you can add to make your text look even better. Here is an in-depth tutorial on it.
Now that we have made your text widget, It is now time to move it around. Let us move it to 90,20 From our initial position which is 20,20
Here is how we will do it. If we simply move to text object to 90,90, We won't see any animations, it will just directly have it there. So what we will do is first create it at 21,20. Then 22,20. And so on...
We do this really fast till we reach 90,20
This looks like we are moving the text
from tkinter import *
import time
root = Tk()
root.title("My animation")
c = Canvas(root)
x = 20
y = 20 #Instead of using row and column, you simply use x and y co-ordinates
#We will use these co-ordinates to show where the text is in the starting
my_text = c.create_text(x,y,text = 'weee')
c.pack()
def animation():
y = 0.1
x = 0
for _ in range(1000):
c.move(my_text,x,y)
root.update()
anlabel = Button(root,text = 'Animate!',command = animation).pack()
root.mainloop()
This is not only applicable to text, but everything (like other images)that is there on the canvas. The canvas also has Events which will let you use mouse-clicks and other keys on the computer too.
I have made some changes from the previous code, But it is executable and you can try it for yourself to see how it works. increasing the value in time.sleep() makes the animation slower, the lesser the value, the faster.
Are you sure you aren't trying to do something more like the below example? Animating the padding on one of your widgets is going to screw up the rest of your display.
from tkinter import *
import time
root = Tk()
lbl = Label(root, text='')
lbl.grid(row=0, column=0)
def animation(step=12):
step = 12 if step < 0 else step
lbl['text'] = ' ------ '[step:step+6]
root.after(200, lambda: animation(step-1))
Button(root, text='Animate', command=animation).grid(row=1, column=0, sticky='w')
root.mainloop()

Tkinter Listbox and Scrollbar not displaying

I am having this issue where the scroll bar is not displaying on the listbox. I do not know what the issue is as.
I believe the issue is originating from the Scrollbar variables as the Listbox appears to be displaying and functioning properly.
The output is displaying the listbox with no scrollbar on the right (as set)
Here is the Listbox with the for loop however, it is displaying the wrong dimensions
Here is the code:
#imports
from tkinter import *
from tkinter import messagebox as ms
from tkinter import ttk
import sqlite3
from PIL import Image,ImageTk
import datetime
global time
time = datetime.datetime.now()
class main:
def __init__(self,master):
self.master = master
def search_user_sql(self):
self.search_user_sqlf = Frame(self.master, height=300, width =200)
scrollbar = Scrollbar(self.search_user_sqlf)
scrollbar.pack(side = RIGHT,fill = BOTH)
myList = Listbox(self.search_user_sqlf, yscrollcommand= scrollbar.set)
myList.pack( side = LEFT, fill = BOTH, expand = 2)
scrollbar.config( command = myList.yview )
self.search_user_sql()
root = Tk()
root.title("Gym Membership System")
main(root)
root.mainloop()
You need to pack the frame to display it. To pack() the frame with the correct size settings, try:
search_user_sqlf = Frame(master, height=300, width=200)
search_user_sqlf.pack(expand=True, fill='both')
search_user_sqlf.pack_propagate(0)
Here is how to attach a scrollbar to list set in a frame in Tkinter:
from tkinter import *
master = Tk()
search_user_sqlf = Frame( master, width=400, height=400)
search_user_sqlf.pack(expand=True, fill='both')
search_user_sqlf.pack_propagate(0)
scrollbar = Scrollbar(search_user_sqlf)
scrollbar.pack(side=RIGHT, fill=Y)
myList = Listbox(search_user_sqlf, yscrollcommand=scrollbar.set)
for line in range(100):
myList.insert(END, "This is line number " + str(line))
myList.pack( side = LEFT, fill = BOTH , expand = 2)
scrollbar.config( command = myList.yview )
mainloop()

How to redirect mouse click/move/drag events from a custom grip Label to PanedWindow parent

I'm trying to add a nice handle to Tkinter.PanedWindow sash. To do that I place a Label with a custom grip image next to a pane. Example:
from Tkinter import *
root = Tk()
pw = PanedWindow(root, orient=HORIZONTAL)
l1 = Listbox(pw)
pw.add(l1)
l2 = Listbox(pw)
pw.add(l2)
pw.pack(fill=BOTH, expand=1)
gripimg = PhotoImage(data="R0lGODlhBAAvAPEAALetnfXz7wAAAAAAACH5BAEAAAIALAAAAAAEAC8AAAIjRBwZwmKomjsqyVdXw/XSvn1RCFlk5pUaw42saL5qip6gnBUAOw==")
griplabel = Label(pw, image=gripimg)
griplabel.place(relx=1, rely=0.5, anchor=W, in_=l1)
root.mainloop()
It looks ok. But now the Label overlaps the sash, steals mouse events and I can't resize PanedWindow by dragging the Label. How can I make griplabel ignore mouse events and redirect them all to the PanedWindow sash?
I tried bindtags, but:
griplabel.bindtags(pw.bindtags())
does not seem to do anything, i.e. I still can't drag the Label to resize PanedWindow.
Or is there a better way to create a custom handle for PanedWindow?
With a kind help of #tcl freenode channel I came up with this:
from Tkinter import *
root = Tk()
pw = PanedWindow(root, orient=HORIZONTAL)
l1 = Listbox(pw)
pw.add(l1)
l2 = Listbox(pw)
pw.add(l2)
pw.pack(fill=BOTH, expand=True)
gripimg = PhotoImage(data="R0lGODlhBAAvAPEAALetnfXz7wAAAAAAACH5BAEAAAIALAAAAAAEAC8AAAIjRBwZwmKomjsqyVdXw/XSvn1RCFlk5pUaw42saL5qip6gnBUAOw==")
griplabel = Label(pw, image=gripimg, cursor="sb_h_double_arrow")
griplabel.place(relx=1, rely=0.5, anchor=W, in_=l1)
griplabel.bind("<Button-1>", lambda e:pw.event_generate("<Button-1>",x=e.x+griplabel.winfo_x(),y=e.y+griplabel.winfo_y()))
griplabel.bind("<B1-Motion>", lambda e:pw.event_generate("<B1-Motion>",x=e.x+griplabel.winfo_x(),y=e.y+griplabel.winfo_y()))
root.mainloop()
Two griplabel.bind(...) calls forward mousedown+mousemove events from Label to PanedWindow, adjusting x and y coords. Those two events are enough to make the sash move.
And griplabel mouse "cursor" is set to sb_h_double_arrow as that's the cursor PanedWindow uses for sash by default, according to the Tk documentation:
Command-Line Name: -sashcursor
Mouse cursor to use when over a sash. If null, sb_h_double_arrow will be used for horizontal panedwindows, and sb_v_double_arrow will be used for vertical panedwindows.
And it's also one of cursor names recognized by Tk on all platforms.
TCL wiki mentions another way to set a custom sash handle bar, using ttk.PanedWindow with custom ttk.Style layout:
from Tkinter import *
import ttk
root = Tk()
gripimg = PhotoImage(data="R0lGODlhBAAvAPEAALetnfXz7wAAAAAAACH5BAEAAAIALAAAAAAEAC8AAAIjRBwZwmKomjsqyVdXw/XSvn1RCFlk5pUaw42saL5qip6gnBUAOw==")
style = ttk.Style()
style.element_create("Sash.xsash", "image", gripimg, sticky=W+E)
style.layout("MySash.TPanedWindow", [('Sash.xsash', {})])
pw = ttk.PanedWindow(root, orient=HORIZONTAL, style="MySash.TPanedWindow")
l1 = Listbox(pw)
pw.add(l1)
l2 = Listbox(pw)
pw.add(l2)
pw.pack(fill=BOTH, expand=True)
root.mainloop()
But it looks and works differently. Essentially it replaces background of ttk.PanedWindow with a tiled image, which remains static, and the sash becomes a viewport sliding over it. That looks unusual, still someone may like it.

Tkinter : Make canvas draggable

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.

Categories