Some outputs create too much text that cannot be viewed without adding a scrollbar in the new window that appears.
For example, in this code below, if there are many files to be returned in the window, you will not be able to view all the text and therefore a scrollbar is needed. How can this be done?
import os
import tkinter as tk
def get_filenames():
filenames = sorted(os.listdir('.'))
text = "\n".join(filenames)
label['text'] = text # change text in label
root = tk.Tk()
# create the text widget
text = tk.Text(root, height=10)
text.grid(row=0, column=0, sticky=tk.EW)
text.grid_columnconfigure(0, weight=1)
text.grid_rowconfigure(0, weight=1)
tk.Button(root, text="OK", command=get_filenames).grid()
# create a scrollbar widget and set its command to the text widget
scrollbar = ttk.Scrollbar(root, orient='vertical', command=text.yview)
scrollbar.grid(row=0, column=1, sticky=tk.NS)
# communicate back to the scrollbar
text['yscrollcommand'] = scrollbar.set
label = tk.Label(root) # empty label
label.grid()
root.mainloop()
How can the scrollbar work as expected?
Scrollbar works correctly but you put text in wrong widget.
def get_filenames():
filenames = sorted(os.listdir('.'))
#text.delete(0, 'end') # remove previous content
text.insert('end', "\n".join(filenames))
Related
I'm using .place() in my tkinter program, so I wanted to remove references to grid(). So far my program works but for some reason there's a single .grid() line that makes my whole program turn blank if it's removed. This shouldn't happen, since I'm entirely using .place(). Here is that line:
AllFrames.grid(row=0, column=0, sticky='nsew')
And here is my full code:
from tkinter import *
root = Tk()
root.title("Account Signup")
DarkBlue = "#2460A7"
LightBlue = "#B3C7D6"
root.geometry('350x230')
Menu = Frame()
loginPage = Frame()
registerPage = Frame()
for AllFrames in (Menu, loginPage, registerPage):
AllFrames.grid(row=0, column=0, sticky='nsew')
AllFrames.configure(bg=LightBlue)
def show_frame(frame):
frame.tkraise()
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
show_frame(Menu)
# ============= Menu Page =========
menuTitle = Label(Menu, text="Menu", font=("Arial", 25), bg=LightBlue)
menuTitle.place(x=130, y=25)
loginButton1 = Button(Menu, width=25, text="Login", command=lambda: show_frame(loginPage))
loginButton1.place(x=85, y=85)
registerButton1 = Button(Menu, width=25, text="Register", command=lambda: show_frame(registerPage))
registerButton1.place(x=85, y=115)
# ======== Login Page ===========
loginUsernameL = Label(loginPage, text='Username').place(x=30, y=60)
loginUsernameE = Entry(loginPage).place(x=120, y=60)
loginPasswordL = Label(loginPage, text='Password').place(x=30, y=90)
loginPasswordE = Entry(loginPage).place(x=120, y=90)
backButton = Button(loginPage, text='Back', command=lambda: show_frame(Menu)).place(x=0, y=0)
loginButton = Button(loginPage, text='Login', width=20).place(x=100, y=150)
# ======== Register Page ===========
root.mainloop()
I've also noticed that changing anything in the parentheses causes the same result. For example, if I change sticky='nsew' to sticky='n' or row=0 to row=1 it will show a blank page.
How do I remove .grid() from my program without it turning blank?
The place() manager does not reserve any space, unless you tell it directly.
The grid(sticky='nsew') makes the widget expand to fill the entire available space, in this case the containing widget. The widgets inside all use place() which will not take any space. When you change to grid(sticky='n') you place the zero size widget at the top of the containing widget.
But, for your current problem you can assign a size to the widgets:
AllFrames.place(relwidth=1, relheight=1) # w/h relative to size of master
I would recommend using the grid() geometry manager if you are going to make any more complicated layouts.
For more info have a look at effbot on archive.org
I am trying to get my scrollable canvas to work. It works when I pack the elements using .pack, however when I insert the elements via .place, the scrollbar stops working. Here is a minimal reproducable example of my code.
startup.py file:
import frame as f
import placeWidgetsOnFrame as p
p.populate3()
f.window.mainloop()
frame.py file:
#Creates widnow
window = customtkinter.CTk()
window.geometry("1900x980")
customtkinter.set_appearance_mode("dark")
window.resizable(False, False)
#Creates Frame for GUI
mainFrame = customtkinter.CTkFrame(window, width=1900, height=980, corner_radius=0)
mainFrame.pack(expand=True, fill=tk.BOTH)
mainFrame.pack_propagate(False)
topFrame = customtkinter.CTkFrame(master=mainFrame, width=1865, height=140, corner_radius=10)
topFrame.grid(columnspan=2, padx=15, pady=15)
topFrame.pack_propagate(0)
leftFrame = customtkinter.CTkFrame(master=mainFrame, width=380, height=530, corner_radius=10)
leftFrame.grid(row=1, column=0, padx=15, pady=10)
leftFrame.pack_propagate(False)
rightFrame = customtkinter.CTkFrame(master=mainFrame, width=1450, height=775, corner_radius=10)
rightFrame.grid(row=1, column=1, padx=15, pady=10, rowspan=2)
rightFrame.pack_propagate(False)
bottomLeftFrame = customtkinter.CTkFrame(mainFrame, width=380, height=220, corner_radius=10)
bottomLeftFrame.grid(row=2, column=0, padx=15, pady=10)
bottomLeftFrame.pack_propagate(False)
#Creates Scrollbar for right Frame
#Creates a canvas for the right Frame
canvas2=tk.Canvas(rightFrame, bg="#000000", highlightthickness=0, relief="flat")
canvas2.pack(side="left", fill="both", expand=True)
#Creates a scroll bar for the right Frame
scrollbar = customtkinter.CTkScrollbar(master=rightFrame, orientation="vertical", command=canvas2.yview, corner_radius=10)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
#Configures scrollbar to canvas
canvas2.configure(yscrollcommand=scrollbar.set)
canvas2.bind("<Configure>", lambda *args, **kwargs: canvas2.configure(scrollregion=canvas2.bbox("all")))
#Creates a scrollable frame to place widgets on
scrollableFrame = customtkinter.CTkFrame(canvas2, fg_color=("#C0C2C5", "#343638"), corner_radius=10)
canvasFrame = canvas2.create_window((0,0), window=scrollableFrame, anchor="nw", tags=("cf"))
#TO DO - resize canvas and to fit all widgets
def handleResize(event):
c = event.widget
cFrame = c.nametowidget(c.itemcget("cf", "window"))
minWidth = cFrame.winfo_reqwidth()
minHeight = cFrame.winfo_reqheight()
print (event.width)
print (event.height)
if minWidth < event.width:
c.itemconfigure("cf", width=event.width)
if minHeight < event.height:
c.itemconfigure("cf", height=event.height)
print (event.width)
print (event.height)
c.configure(scrollregion=c.bbox("all"))
canvas2.bind('<Configure>', handleResize)
def onMousewheel(event):
canvas2.yview_scroll(-1 * round(event.delta / 120), "units")
canvas2.bind_all("<MouseWheel>", onMousewheel)
canvas2.bind("<Destroy>", lambda *args, **kwargs: canvas2.unbind_all("<MouseWheel>"))
placeWidgetsOnFrame.py file:
import tkinter
import customtkinter
import frame as f
rightFrame = f.scrollableFrame
def populate2():
for i in range(30):
emailLabel = customtkinter.CTkLabel(master=rightFrame, text="Please enter your email:")
emailLabel.pack(padx=10, pady=10)
def populate3():
x=50
for i in range(30):
emailLabel = customtkinter.CTkLabel(master=rightFrame, text="Please enter your email:")
emailLabel.place(x=40, y=x)
x=x+50
Here is the output when populate3() is run:
Here
Here is the output when populate2() is run
Here
Does anyone know why this is? I can always go back and change the way I insert widgets to .pack rather than .place, however I would rather use .place as I find it easier to place widgets where I want to.
The reason is because pack by default will cause the containing frame to grow or shrink to fit all of the child widgets, but place does not. If your frame starts out as 1x1 and you use place to add widgets to it, the size will remain 1x1. When you use place, it is your responsibility to make the containing widget large enough to contain its children.
This single feature is one of the most compelling reasons to choose grid or pack over place - these other geometry managers do a lot of work for you so that you can think about the layout logically without getting bogged down in the details of the layout.
I'm trying to center text in the frame using LabelFrame function. My code is below.
import tkinter as tk
window = tk.Tk()
for i in range(9):
for j in range(9):
frame = tk.LabelFrame(
master=window,
relief=tk.RAISED,
borderwidth=5,
width=50,
height=50,
text=i+j,
labelanchor = 'n'
)
frame.grid(row=i, column=j)
window.geometry("500x500")
window.mainloop()
Argument labelanchor that specify position gives only options on the edge of the frame. Is there any simple way to center text inside of the frame using LabelFrame?
Is there any simple way to center text inside of the frame using LabelFrame?
The text of the labelframe can only appear along the edges of the frame. If you wish for text to appear inside the frame, you must create a Label and add it to the frame.
Using a frame and a label
If you wish to put other widgets in the frame and don't want this text to interfere with the other widgets, this is a perfect opportunity to use place.
The following example adds the label "Hello, world" to appear in the center of the widget. A button is placed in the frame just to show that its placement is not affected by the label, or vise versa. The screenshots show what the frame looks like naturally and when the window is resized.
import tkinter as tk
root = tk.Tk()
lf = tk.Frame(root, bd=2, relief="groove")
lf_label = tk.Label(lf, text="Hello, world")
lf_label.place(relx=.5, rely=.5, anchor="c")
lf.pack(padx=20, pady=20, fill="both", expand=True)
b = tk.Button(lf, text="Click me")
b.pack(padx=10, pady=10)
root.mainloop()
Using only a label
You can add widgets inside any other widget. So, instead of a frame you could just use a label. By default, the text will be centered, and just like with the previous example it won't affect the layout of other widgets.
I haven't included screenshots because the results are identical to the previous example.
import tkinter as tk
root = tk.Tk()
lf = tk.Label(text="Hello, world", bd=2, relief="groove")
lf.pack(padx=20, pady=20, fill="both", expand=True)
b = tk.Button(lf, text="Click me")
b.grid(row=0, column=0)
root.mainloop()
I am trying to create a GUI where left hand side is a Listbox (contained inside a frame f2) that displays employee ID's and right side is another frame second_frame (contained inside canvas and outer frame f3) that shows transaction details of each selected employee in the form of labels.
Each employee can have multiple transactions. So, The number of labels had to be dynamic, i.e. for first selected item in listbox, there could be two labels and for second selected item in listbox, it could be hundred. For every selection, I am calling two functions to destroy old labels and create new labels. While the code works fine, I am having trouble resizing the scrollbar according to the selected listbox entry. I am new to Tkinter, Please advise. Below is my code.
Also note, the test() function when called from outside any function displays the scroll bar, but does not display anything when called from within any function.
# -*- coding: utf-8 -*-
from tkinter import *
'''def test():
for i in range(0,50):
for j in range (0,7):
Label(second_frame, text=f'{i}{j}', width=20).grid(row=i, column=j, pady=5,padx=5)
'''
# --- function ---
def destroy_frame():
#f1.grid_forget()
print("destroying frame")
for label in second_frame.winfo_children():
label.destroy()
def create_frame(val):
print("creating new frame")
for i in range(0,val):
for j in range (5):
Label(second_frame, text=f'{i} {j} ', relief=GROOVE, width=10).grid(row=i, column=j, pady=5,padx=5)
def on_selection(event):
# here you can get selected element
print('previous:', listbox.get('active'))
print(' current:', listbox.get(listbox.curselection()))
# or using `event`
print('(event) previous:', event.widget.get('active'))
print('(event) current:', event.widget.get(event.widget.curselection()))
print (listbox.get(listbox.curselection()))
if (listbox.get(listbox.curselection()) == "Eid 1"):
destroy_frame()
create_frame(100)
elif (listbox.get(listbox.curselection()) == "Eid 2"):
destroy_frame()
create_frame(200)
print('---')
root = Tk()
root.geometry('800x500')
#Create base Frames
f1 = Frame(width=800, height=50, bg="yellow", colormap="new")
f1.grid(row=0, columnspan=2)
f1.grid_propagate(False)
f2 = Frame(width=200, height=425, bg="light blue", colormap="new")
f2.grid(row=1, column=0)
f2.grid_propagate(False)
f3 = Frame(width=600, height=425, bg="light green", colormap="new")
f3.grid(row=1, column=1)
f3.grid_propagate(False)
#Create header Label
l1_f1 = Label(f1, text="Employee Purchase Entries:", bg="yellow")
l1_f1.grid(row=0, column=0)
#Create Listbox
listbox = Listbox(f2, bg="light blue", width=40, height=400)
listbox.grid(row=0, column=0)
#Add Scrollbar to ListBox
list_scrollbar = Scrollbar(f2)
list_scrollbar.grid(row=0, column=1, sticky=NSEW)
#Enter Listbox Data
listbox.insert(1, 'Eid 1')
listbox.insert(2, 'Eid 2')
listbox.bind('<<ListboxSelect>>', on_selection)
#configure the Listbox and Scrollbar
listbox.config(yscrollcommand = list_scrollbar.set)
list_scrollbar.config(command = listbox.yview)
#Create a Canvas
my_canvas = Canvas(f3, width=580, height=425, bg="light green")
#my_canvas.pack(side=LEFT, fill=BOTH, expand=1)
my_canvas.grid(row=0, column=0)
#Add a Scrollbar to the canvas
my_scrollbar = Scrollbar(f3, orient=VERTICAL, command=my_canvas.yview)
my_scrollbar.grid(row=0, column=1, sticky=NSEW)
#configure the canvas
my_canvas.configure(yscrollcommand=my_scrollbar.set)
my_canvas.bind('<Configure>', lambda e : my_canvas.configure(scrollregion = my_canvas.bbox("all")))
#Create another frame inside the canvas
second_frame = Frame(my_canvas)
#Add the new frame to a window in the canvas
my_canvas.create_window((0,0), window=second_frame, anchor="nw")
#test()
root.mainloop()
Your canvas isnt trigger the configure event when you add widgets to your frame. Instead your frame is been triggerd.
So you need to put this line:
second_frame.bind('<Configure>', lambda e : my_canvas.configure(scrollregion = my_canvas.bbox("all")))
after creating second_frame
Example
(mimics the relevant parts of the layout in my real code)
import Tkinter as tk
import ttk
# set up root
root = tk.Tk()
root.minsize(300, 50)
frame = ttk.Frame(root)
frame.grid(row=0, column=0, sticky=tk.EW)
# set up buttons that insert a short or a long string
textvar = tk.StringVar(value='foo')
def insert_short():
textvar.set('foo')
def insert_long():
textvar.set('foo'*30)
button_short = ttk.Button(frame, text='short', command=insert_short)
button_short.grid(row=0, column=0)
button_long = ttk.Button(frame, text='long', command=insert_long)
button_short.grid(row=0, column=0)
button_long.grid(row=0, column=1)
# set up label
# border for label to see its size
style = ttk.Style()
style.configure(
'Bordered.TLabel', foreground='black', borderwidth=1, relief='solid')
# make label extend to the right
frame.columnconfigure(2, weight=1)
# place label
label = ttk.Label(frame, textvariable=textvar, style='Bordered.TLabel')
label.grid(row=0, column=2, sticky=tk.EW)
# place some other widget under label to mimic my real code
ttk.Button(frame, text='some other widget').grid(row=1, column=2)
# TRIED, NOT WORKING:
#root.resizable(False, False)
#frame.propagate(False)
#frame.grid_propagate(False)
#label.propagate(False)
#label.grid_propagate(False)
root.mainloop()
Output
Question
How do I prevent label from extending the main window?
(Bonus question, but not important: is there a way to make the label scrollable if it gets too long?)
Attempts
I tried the following commands:
root.resizable(False, False)
frame.propagate(False)
frame.grid_propagate(False)
label.propagate(False)
label.grid_propagate(False)
You can create a scrollable label using an Entry in a read-only state and by using scrolling it will prevent the widget from extending the main window.
Try replacing your label definition with the following code:
child_frm = ttk.Frame(frame)
label = ttk.Entry(child_frm, textvariable=textvar, style='Bordered.TLabel', state='readonly')
scroll = ttk.Scrollbar(child_frm, orient='horizontal', command=label.xview)
label.config(xscrollcommand=scroll.set)
label.grid(row=0, sticky=tk.EW)
scroll.grid(row=1, sticky=tk.EW)
child_frm.grid(row=0, column=2)
By default, the width of a Label is calculated based on its contents. You can override this behavior by specifying a value for width when creating the Label.
label = ttk.Label(frame, textvariable=textvar, style='Bordered.TLabel', width=1)
Much to my surprise, when I update your code with this, the label doesn't shrink to a size suitable for displaying exactly one character. It appears that the sticky=tk.EW argument of your grid call ensures that the label stays as wide as the widest element in the column.