Assume a small Tkinter window with two frames, one having 20 buttons vertically and the other having 100 buttons vertically. These frames be one below the other. The window is small and so I create a scrollbar like this:
from tkinter import*
root = Tk()
root.geometry('200x100')
main_frame = Frame(root)
main_frame.pack(fill = BOTH, expand = 1)
main_canvas = Canvas(main_frame, bg = 'snow')
main_canvas.pack(side = LEFT, fill = BOTH, expand = 1)
vertical_scrollbar = ttk.Scrollbar(main_frame, orient = VERTICAL, command = main_canvas.yview)
vertical_scrollbar.pack(side = RIGHT, fill = Y)
main_canvas.configure(yscrollcommand = vertical_scrollbar.set)
main_canvas.bind('<Configure>', lambda e: main_canvas.configure(scrollregion = main_canvas.bbox("all")))
screen_frame = Frame(main_canvas, bg = 'snow')
main_canvas.create_window((0,0), window = screen_frame, anchor = 'nw')
frame1 = Frame(screen_frame, bg = 'snow').grid(row = 0, column = 0)
frame2 = Frame(screen_frame, bg = 'snow').grid(row = 1, column = 1)
for i in range(0, 20):
Button(frame1, text = 'Button').grid(row = i, column = 0)
for j in range(0, 100):
Button(frame2, text = 'Button').grid(row = j, column = 0)
root.mainloop()
So at this point, we will be able to scroll till the end of the second frame. But Now if I do the following:
for widgets in frame2.winfo_children():
widgets.destroy()
Frame 2 becomes empty. But now the scrollbar doesn't end at the end of frame1. Rather there is an empty space below frame1 where 100 buttons of frame2 used to be.
So my doubt is, how do we remove this empty space?
Related
I want to create a scrollable frame and add some widgets but I have a problem, when I use entry.place() it doesn't show any entry but if I use pack() it works perfectly, any solution?
from tkinter import *
from tkinter import ttk
root = Tk()
root.title("Entry Widgets")
mainframe = Frame(root)
mainframe.pack(fill = "both", expand = True)
canvas = Canvas(mainframe)
canvas.pack(side = "left", fill = "both", expand = True)
scrollbar = Scrollbar(mainframe, orient = "vertical", command = canvas.yview)
scrollbar.pack(side = "right", fill = "y")
canvas.configure(yscrollcommand = scrollbar.set)
frame = Frame(canvas)
frame.pack(fill = "both", expand = True)
y=0
for i in range(50):
entry = Entry(frame)
entry.place(x=0,y=y)
y = entry.winfo_y() + entry.winfo_height() + 5
canvas.create_window((0, 0), window = frame, anchor = 'nw')
frame.update_idletasks()
canvas.configure(scrollregion = canvas.bbox("all"))
root.mainloop()
Edited:
I want to create a scrollable frame and add some widgets but I have a
problem, when I use entry.place() it doesn't show any entry.
You cannot used pack() and place() when doing looping for Entry widget.
Just use grid() for second frame.
In case, you want to use ttk module.
Code:
from tkinter import *
from tkinter import ttk
root = Tk()
root.title("Entry Widgets")
mainframe = ttk.Frame(root)
mainframe.pack(fill = "both", expand = True)
canvas = Canvas(mainframe)
canvas.pack(side = "left", fill = "both", expand = True)
scrollbar = ttk.Scrollbar(canvas, orient = "vertical", command = canvas.yview)
scrollbar.pack(side = "right", fill = "y")
canvas.configure(yscrollcommand = scrollbar.set)
frame = ttk.Frame(root)
frame.pack(fill = "both", expand = True)
for i in range(50):
entry = Entry(frame, width=50)
entry.grid(column=0,row=i+10)
canvas.create_window((0, 0), window = frame, anchor = 'nw')
frame.update_idletasks()
canvas.configure(scrollregion = canvas.bbox("all"))
root.mainloop()
Screenshot:
I am trying to resolve an error in which I create a second frame within Tkinter with a group of labels and entry boxes and I'm trying to implement a scrollbar for future use when I place many more labels and boxes but for some reason only the frame, boxes, and labels pop up - but no scrollbar. Here is the code so far:
from tkinter import *
from tkinter import ttk
win = Tk()
# Creating Frames
wrapper1 = LabelFrame(win, width = 1500, height = 1000)
wrapper2 = LabelFrame(win, width = 1500, height = 1000)
mycanvas = Canvas(wrapper1)
mycanvas.pack(side = LEFT, fill = "both", expand ="yes")
myframe = Frame(mycanvas)
mycanvas.create_window((0,0), window = myframe, anchor = "nw")
wrapper1.pack(fill = "both", expand = "yes", padx = 10, pady = 10)
# Creating a Hide Frame Function
def hide_menu_frames():
# Destroying the children widgets in each frame
for widget in wrapper1.winfo_children():
widget.destroy()
for widget in wrapper2.winfo_children():
widget.destroy()
# Hiding all frames
wrapper1.pack_forget()
wrapper2.pack_forget()
def home():
#hide_menu_frames()
myframe.pack(fill = "both", expand = 1)
start_label = Label(myframe, text = "Choose Length of Loan Model", font = ("Helvetica", 18)).pack(pady = 100)
#start_label.grid(row = 0, column = 1, pady = 50)
# Creating Buttons
home_button1 = Button(myframe, text = "12 Month Model", bg='#ffffff', activeforeground='#4444ff', font = ("Helvetica", 15, BOLD), command = second_home).pack(pady = 10)
def second_home():
hide_menu_frames()
second_label = Label(wrapper2, text = "Please Input Loan Parameters", font = ("Helvetica", 18), pady = 10).pack()
# Creating Input Box for Original Balance Month 1
global og_bal_input
og_bal_input = Entry(wrapper2)
og_bal_input.pack()
# Creating Label for Original Balance Month 1
global og_bal_label
og_bal_label = Label(wrapper2, text = "Enter Original Balance for Month 1")
og_bal_label.pack()
# Creating Input Box for Original Balance Month 2
global og_bal_input2
og_bal_input2 = Entry(wrapper2)
og_bal_input2.pack()
# Creating Label for Original Balance Month 2
global og_bal_label2
og_bal_label2 = Label(wrapper2, text = "Enter Original Balance for Month 2")
og_bal_label2.pack()
# Creating Input Box for Original Balance Month 3
global og_bal_input3
og_bal_input3 = Entry(wrapper2)
og_bal_input3.pack()
# Creating Label for Original Balance Month 3
global og_bal_label3
og_bal_label3 = Label(wrapper2, text = "Enter Original Balance for Month 3")
og_bal_label3.pack()
mycanvas = Canvas(wrapper2)
mycanvas.pack(side = LEFT, fill = "both", expand ="yes")
yscrollbar = ttk.Scrollbar(wrapper2, orient = VERTICAL, command = mycanvas.yview)
yscrollbar.pack(side = RIGHT, fill = "y")
mycanvas.configure(yscrollcommand = yscrollbar.set)
mycanvas.bind('<Configure>',lambda e: mycanvas.configure(scrollregion = mycanvas.bbox('all')))
wrapper2.pack(fill = "both", expand = "yes", padx = 10, pady = 10)
myframe = Frame(mycanvas)
mycanvas.create_window((0,0), window = myframe, anchor = "nw")
Everything works until I press the first button to get to the 'second home' - I can see the input boxes and labels but no scrollbar
You haven't put the entries and labels inside myframe so they won't be in the scrolled canvas. You need to rearrange your code so the canvas (mycanvas) and its frame (myframe) are created first, then make the entries and labels children of the frame in the canvas (myframe). I've added a loop which demonstrates the scrolling, you can remove this.
def second_home():
hide_menu_frames()
mycanvas = Canvas(wrapper2)
mycanvas.pack(side = LEFT, fill = "both", expand ="yes")
yscrollbar = ttk.Scrollbar(wrapper2, orient = VERTICAL, command = mycanvas.yview)
yscrollbar.pack(side = RIGHT, fill = "y")
mycanvas.configure(yscrollcommand = yscrollbar.set)
mycanvas.bind('<Configure>',lambda e: mycanvas.configure(scrollregion = mycanvas.bbox('all')))
wrapper2.pack(fill = "both", expand = "yes", padx = 10, pady = 10)
myframe = Frame(mycanvas)
mycanvas.create_window((0,0), window = myframe, anchor = "nw")
second_label = Label(myframe, text = "Please Input Loan Parameters", font = ("Helvetica", 18), pady = 10).pack()
# Creating Input Box for Original Balance Month 1
global og_bal_input
og_bal_input = Entry(myframe)
og_bal_input.pack()
# Creating Label for Original Balance Month 1
global og_bal_label
og_bal_label = Label(myframe, text = "Enter Original Balance for Month 1")
og_bal_label.pack()
# Creating Input Box for Original Balance Month 2
global og_bal_input2
og_bal_input2 = Entry(myframe)
og_bal_input2.pack()
# Creating Label for Original Balance Month 2
global og_bal_label2
og_bal_label2 = Label(myframe, text = "Enter Original Balance for Month 2")
og_bal_label2.pack()
# Creating Input Box for Original Balance Month 3
global og_bal_input3
og_bal_input3 = Entry(myframe)
og_bal_input3.pack()
# Creating Label for Original Balance Month 3
global og_bal_label3
og_bal_label3 = Label(myframe, text = "Enter Original Balance for Month 3")
og_bal_label3.pack()
for i in range(100):
Label(myframe, text = "test scroll " + str(i)).pack()
from tkinter import *
#Root
root = Tk()
root.title('A WINDOW')
#Frame
main_frame = Frame(root)
main_frame.pack(fill=BOTH, expand = True)
#Canvas
main_canvas = Canvas(main_frame)
main_canvas.pack(side = LEFT, fill = BOTH, expand = True)
#Scrollbar
scrollbar = Scrollbar(main_frame, orient = VERTICAL, command = main_canvas.yview)
scrollbar.pack(side = RIGHT, fill = 'y')
#Configure the Canvas
main_canvas.configure(yscrollcommand=scrollbar.set)
#Frame#2
Frame2 = Frame(main_canvas)
main_canvas.create_window((0, 0), window = Frame2, anchor = 'nw')
main_canvas.bind('<Configure>', lambda e :main_canvas.configure(scrollregion=main_canvas.bbox('all')))
for i in range(2000):
Button(Frame2, text = str(i)).grid(row = i, column = 0)
root.mainloop()
and when I execute the program, it works until the 1080th one then scrollbar moves but buttons don't go down anymore I hope you understood me and have an idea about what must I do...
This is a slight modification of your code. It demonstrates the limitation of the window object in tk.Canvas with a maximum height of 32768. It can be shown by changing variable offset to some arbitrary value and scrolling to the end of canvas. The last button will still be visible even though its position is 32768+offset.
Function bookmark finds frame2 in the window object and finds all objects in frame2 then changes background color. This makes it easy to confirm first and last buttons are visible.
import tkinter as tk
root = tk.Tk()
frame = tk.LabelFrame(root, labelanchor = tk.S, text = '0|0')
frame.grid(row = 0, column = 0, sticky = tk.NSEW)
for a in [root, frame]:
a.rowconfigure(0, weight = 1)
a.columnconfigure(0, weight = 1)
main_canvas = tk.Canvas(frame)
main_canvas.grid(row = 0, column = 0, sticky = tk.NSEW)
scrollbar = tk.Scrollbar(frame, orient = tk.VERTICAL, command = main_canvas.yview)
scrollbar.grid(row = 0, column = 1, sticky = tk.NS)
main_canvas.configure(yscrollcommand = scrollbar.set)
def location(event):
x, y = main_canvas.canvasx(event.x), main_canvas.canvasy(event.y)
frame['text'] = f'{x} | {y}'
# bind mouse
main_canvas.bind('<Motion>', location)
frame2 = tk.Frame(main_canvas)
# frame is positioned at offset
offset = 0
main_canvas.create_window(0, offset, window = frame2, anchor = tk.NW)
def bookends(x):
# Find reference to frame2
C = main_canvas.winfo_children()[0]
# find all objects in frame2
D = C.winfo_children()
print(len(D))
# change colors of first and last buttons
D[0]['background'] = 'red'
D[x]['background'] = 'red'
for i in range(2000):
tk.Button(
frame2,
text = str(i),
font = '{Courier New} 6',
padx=0, pady=0).grid(
row = i, column = 0, ipadx = 0, ipady = 0)
bookends(1999)
# main_canvas['scrollregion'] = f'0 0 40 {32768+offset}'
main_canvas.bind('<Configure>', lambda e :main_canvas.configure(scrollregion=main_canvas.bbox('all')))
root.mainloop()
I have several larger tkinter / python program which I would like to incorporate into one program which would clear a frame when another program is called; each program currently being inside a function (I probably should use classes eventually when I understand them) and each of these function being displayed on a form being cleared of widgets from the previous if any do exist.
The code below is just a small trial for me to understand how to do this, but it's not working.
When I invoke the widget.destroy() function, it removes the frame (DisplayFrame) and does not clear the widgets inside it and hence not displaying the new widgets.
here is the current trial code:
#!/usr/bin/env python3
import tkinter as tk
from tkinter import *
from tkinter import ttk
#import pandas as pd
import MultiTempsP3
import datetime, time
from tkinter import messagebox
import sqlite3
from tkinter import colorchooser
from configparser import ConfigParser
import os
import csv
if os.environ.get('DISPLAY','') == "":
print('no display found.Using :0.0')
os.environ.__setitem__('DISPLAY',':0.0')
root = tk.Tk()
root.title("Kombucha Program")
root.geometry("1400x800")
root.minsize(width=900, height=600)
#root.maxsize(width=1400, height = 900)
root.grid_rowconfigure(3, weight=1)
root.grid_columnconfigure(2, weight=1)
root.configure( bg = '#000080' )
DisplayFrame = tk.Frame(root, width=1200, height = 630, bg = 'yellow') #0059b3')
DisplayFrame.grid(column=0,row=1, sticky = N, in_ = root)
rightFrame = tk.Frame(root, width = 120, height = 390, bg = 'white') # #000080
rightFrame.grid(column = 1, row = 0, pady = 10, padx = 10)
lblFrame = tk.Frame(root, height = 70, width = 670, bg = 'black')
lblFrame.grid(column = 0, row = 0, sticky =N, in_ = root)
##'W' stands for West = WrightFrmae (west fframe on the right of screen
#WrightFrame = tk.Frame(rightFrame, width = 70, height = 300, bg = 'green') # #000080
#WrightFrame.grid(column = 0, row = 1)
WidgetFrame = tk.Frame(root, height = 300, width = 120, bg = 'red') # #000080
WidgetFrame.grid(column=0,row=2, pady = 30)
fromTemp = MultiTempsP3.temps("65cd6bd")
lblTemp = Label(rightFrame, text=fromTemp).grid(row=1,column=0,pady=0 )
#lblTemp.pack()
def clearDisplayFrame():
for widgets in DisplayFrame.winfo_children():
widgets.destroy()
###***### - This section is in the right top little frame = rightFrame
state = "yes" ## delete this row and use below state=GPIO when on an RPi
#state = GPIO.input(17)
if state:
state_17="GPIO_17 (HeatPad) is On "
else:
state_17="GPIO_17 (HeatPad) is Off "
lblHeatPad = Label(rightFrame, text=state).grid(row=3,column=0,pady=0 ) #shows as text in the window
#lblHeatPad.pack() #organizes widgets in blocks before placing them in the parent.
###***### End of rightFrame widgets
def func_quit():
root.destroy()
def openData():
clearDisplayFrame()
print("I am inside openData()")
lbltrial=tk.Label(DisplayFrame,text="trial").grid(row=3, column=2)
def func_Temps():
clearDisplayFrame()
print("I am inside func_Temps()")
#DisplayFrame = tk.Frame(root, width=1200, height = 630, bg = 'yellow') #0059b3')
#DisplayFrame.grid(column=0,row=1, sticky = N, in_ = root)
lblSomething = tk.Label(DisplayFrame, text = "Open Temps").grid(row=2,column=2)
###***### This section is top of left = lblFrame
exitButton = tk.Button(lblFrame, text="Quit the Program", width = 12, command=root.destroy, bg= "magenta")
exitButton.grid(row = 0, column = 0, columnspan = 1, pady = 5, padx = 5)
dataButton = Button(lblFrame, text="Open Dates Window", command=openData).grid(row=0, column=1) ## the open refers to the above function
tempsButton= Button(lblFrame, text="Open Temps Info", command=func_Temps).grid(row=0, column=2)
###***### End of top left widget in lblFrame
mainloop()
As an answer, here is an approach that uses 2 frame and switches between them in the click of the switch. This is the way usually switching between frame is implemented in procedural programming, AFAIK:
from tkinter import *
root = Tk()
def change(frame):
frame.tkraise() # Raising the passed frame
window1 = Frame(root)
window2 = Frame(root)
window1.grid(row=0,column=0) # Grid in the same location so one will cover/hide the other
window2.grid(row=0,column=0)
# Contents inside your frame...
Label(window1,text='This is page 1',font=(0,21)).pack()
Label(window2,text='This is page 2',font=(0,21)).pack()
# Buttons to switch between frame by passing the frame as an argument
Button(root,text='Page 1',command=lambda: change(window1)).grid(row=1,column=0,stick='w')
Button(root,text='Page 2',command=lambda: change(window2)).grid(row=1,column=0,stick='e')
root.mainloop()
So instead of destroying all the items inside your frame, you should just raise the other frame, as destroyed widgets cannot be brought back.
I followed some tutorial on attaching a scrollbar to a textbox. However, in the tutorial, the scrollbar is really a "bar". When I tried myself, I can only press the arrows to move up or down, the middle part is not movable. May I know what I did wrong?
import tkinter as tk
root = tk.Tk()
scroll = tk.Scrollbar(root)
scroll.grid(row = 0, column = 1)
message = tk.Text(root, yscrollcommand = scroll.set, height = 25, width = 60)
message.grid(row = 0, column = 0)
for i in range(50):
message.insert(tk.END, f'This is line {i}\n')
scroll.config(command = message.yview)
root.mainloop()
You just have to add sticky='nsew' to Scrollbar widget.
sticky='nsew' will make the Scrollbar widget to expand to fill up the entire cell (at grid position row=0 & column=1) at every side (n-north, s-south, e-east, w-west)
Here is the code:
import tkinter as tk
root = tk.Tk()
scroll = tk.Scrollbar(root)
# add sticky option to the Scrollbar widget
scroll.grid(row = 0, column = 1, sticky='nsew')
message = tk.Text(root, yscrollcommand = scroll.set, height = 25, width = 60)
message.grid(row = 0, column = 0)
for i in range(50):
message.insert(tk.END, f'This is line {i}\n')
scroll.config(command = message.yview)
root.mainloop()