Tkinter: Placing widgets on top of other Widgets - python

I'm trying to place an Entry widget on top of a frame but i'm having some trouble with it
(I'm using customtk but i suppose there's no big difference)
import customtkinter
app = customtkinter.CTk()
app.geometry(500x380)
random_frame = customtkinter.CTkFrame(master=app, width=480, height=50)
Box1 = customtkinter.CTkEntry(master=app, width=30, height=30)
random_frame.grid(row=3, column=0, padx=10, pady=10)
Box1.grid(row=3, column=1, padx=10, pady=10)
I am expecting the Box1 widget to be on top of the random_frame widget, however in the gui i can't see the Box1. I'm unsure if it's under the frame, but that's what i think is happening.
I tried using the lift() method but it doesn't seem to do anything

Related

Tkinter scrollbar removes textbox and doesnt sticky to the right

My textbox spans over 5 rows and 4 columns and I want it to have a scrollbar, so I added one but it removes the textbox and doesn't stick.
My textbox looks like this:
and its code like this
# Textbox
self.textbox = Text(self)
self.textbox.grid(row=10, column=1, rowspan=5, columnspan=4, padx=10, pady=10)
self.vsb = Scrollbar(self.textbox, orient='vertical', command=self.textbox.yview)
self.vsb.grid(row=10, column=4, rowspan=5, sticky='ns')
self.textbox.configure(yscrollcommand=self.vsb.set)
after running it my textbox just vanishes
I have no clue what caused this, I usually have no problems with scrollbars.
Maybe you'll have better luck with the ScrolledText widget. See here for docs
from tkinter.scrolledtext import ScrolledText
self.textbox = ScrolledText(self)

Is there a way to refresh a tkinter frame in which widgets have been reconfigured?

I'd like to allow the user to configure the appearance of the app (background color, font, font size and color, button color, etc). I've got all of the default settings saved into a config file, which the program interprets and saves into variables within a class for easy access between functions. Now, I can save the changes the user made into the config file and those changes will be reflected when the user closes and reopens the app, but I'd like the changes to be instantaneous, so I tried something like this:
import tkinter as tk
class SetColor:
def __init__(self, color):
self.color = 'green'
current = SetColor('green')
root = tk.Tk()
lbl_color = tk.Label(root, text='Choose button color')
lbl_color.grid(row=0, column=0, pady=5, padx=2)
btn_red = tk.Button(root, text='Red', bg=current.color, command=lambda:update_color('red'))
btn_red.grid(row=0, column=1, pady=5, padx=2)
btn_green = tk.Button(root, text='Green', bg=current.color, command=lambda:update_color('green'))
btn_green.grid(row=0, column=2, pady=5, padx=2)
btn_blue = tk.Button(root, text='Blue', bg=current.color, command=lambda:update_color('blue'))
btn_blue.grid(row=0, column=3, pady=5, padx=2)
def update_color(color):
current.color = color
#THIS is where I'm wondering if there's a way to refresh without individually updating each widget as I've done below
btn_red.config(bg=current.color)
btn_green.config(bg=current.color)
btn_blue.config(bg=current.color)
root.mainloop()
This does work, but in my actual app there are a loooooot more widgets that would need updating than in this^ example. So I have a feeling I'm missing something or going about this in the wrong way. Any help greatly appreciated :)
Your best bet is to store the buttons in a list and loop over that list. This way you can seperate different buttons. But if you are sure you want to change the color of every singlle button you can do: for widget in root.winfo_children():
if isinstance(widget, tk.Button):
widget.config(bg=current.color)
#Maarten's answer is perfect for the tkinter button.
There is another option using ttk.Button can be used in such scenario
create the button object with a custom style
btn_green = ttk.Button(root, text='Green', style="color.TButton", command=lambda: update_color('green'))
create style object
style = ttk.Style()
style.theme_use("default")
set the style
style.configure('color.TButton', background=current.color)
# Activate is when you mouse over the button.
style.map('color.TButton', background=[('active', current.color)])
Full example:
import tkinter as tk
from tkinter import ttk
class SetColor:
def __init__(self, color):
self.color = 'green'
def update_color(color):
current.color = color
# Let's set the style
# naming that style variable as color.TButton
# NOTE: .TButton is important, you can add any other pretix though
style.configure('color.TButton', background=current.color)
# Activate is when you mouse over the button.
style.map('color.TButton', background=[('active', current.color)])
current = SetColor('green')
root = tk.Tk()
# Create style Object
style = ttk.Style()
# Setting theme to default (built in themes can be found https://wiki.tcl-lang.org/page/List+of+ttk+Themes)
style.theme_use("default")
lbl_color = ttk.Label(root, text='Choose button color')
lbl_color.grid(row=0, column=0, pady=5, padx=2)
btn_red = ttk.Button(root, text='Red', style="color.TButton", command=lambda: update_color('red'))
btn_red.grid(row=0, column=1, pady=5, padx=2)
btn_green = ttk.Button(root, text='Green', style="color.TButton", command=lambda: update_color('green'))
btn_green.grid(row=0, column=2, pady=5, padx=2)
btn_blue = ttk.Button(root, text='Blue', style="color.TButton", command=lambda: update_color('blue'))
btn_blue.grid(row=0, column=3, pady=5, padx=2)
update_color(current.color)
root.mainloop()
There are a lot more options with ttk style to play around.
Have a look at
Python ttk Style
ttk Themes
I did this and works perfectly. I hope it works for you.
def groups1(): # This function is to place the widgets for Groups.
# Clean widgets immediately after you call the button inside
# the button function.
for widget in frameleft.winfo_children():
widget.destroy()
groups = tkinter.ttk.Label(frameleft, text='Grupos', font=
('URW Gothic', 20))
groups.place(x=20, y=30)

How can i attach my scroll bar to my text box on tkinter?

Im trying to use a vertical scrollbar for my text box but am coming across some problems:
I cant get the scroll bar to be directly touching the right side of the text box (so they are connected)
It seems the scroll bar wont affect my text box
I looked through some solutions but none seemed to work.
Heres my code:
from tkinter import *
writtenQ = Tk()
writtenQ.title("Written Response Question")
writtenQ.resizable(0,0)
header = LabelFrame(writtenQ, bg="white")
content = LabelFrame(writtenQ, bg="white")
header.columnconfigure(0, weight=1) # Forces column to expand to fill all available space
homeButton=Button(content,width=50,height=50)
try:
homeIcon=PhotoImage(file="yes.png")
homeButton.config(image=homeIcon)
homeButton.image = homeIcon
except TclError:
print("Home")
homeButton.grid(row=1, sticky="w", padx=15, pady=2)
#the image of the question will be put here
titleHeader = Label(content, text="Question Image here",pady=15, padx=20, bg="white", font=("Ariel",20, "bold"), anchor="w", relief="solid", borderwidth=1)
titleHeader.grid(row=2, column=0, columnspan=3, padx=15, pady=5, ipadx=370, ipady=150)
#this will allow the user to input their written response
answerInput = Text(content, width = 60, borderwidth=5, font=("HelvLight", 18))
answerInput.grid(row=3, column=0, ipady = 10, sticky="w", padx=(20,0), pady=20)
answerScrollBar= Scrollbar(content, command=answerInput.yview, orient="vertical")
answerScrollBar.grid(row=3, column=1, sticky="w")
submitButton = Button(content, borderwidth=1, font=("Ariel", 22), text="Submit", bg="#12a8e3", fg="black", activebackground="#12a8e3", relief="solid")
submitButton.grid(row=3, column=2, ipady=50, ipadx=70, sticky="nw", pady=20)
header.grid(row=0, sticky='NSEW')
content.grid(row=1, sticky='NSEW')
Configuring a scrollbar requires a two-way connection: the scrollbar needs to call the yview or xview method of the widget, and the widget needs to call the set method of the scrollbar.
Usually, this is done in three steps like in the following example:
answerInput = Text(...)
answerScrollBar= Scrollbar(..., command=answerInput.yview)
answerInput.configure(yscrollcommand=answerScrollBar.set)
You are forgetting the final step.
Unrelated to an actual functioning scrollbar, you're going to want to be able to see the scrollbar. You need to use sticky="ns" for the scrollbar so that it stretches in the Y direction. Otherwise it will only be a couple dozen pixels tall.
answerScrollBar.grid(row=3, column=1, sticky="ns")
Have you tried the solution here?
Let's say that the text widget is called text. Your code could be (excluding the setup of the window):
import tkinter
import tkinter.ttk as ttk
scrollb = ttk.Scrollbar(self, command=text.yview)
scrollb.grid(row=0, column=1, sticky='nsew')
text['yscrollcommand'] = scrollb.set
I have picked out what I think will be ueful for you from Honest Abe's answer. Hope it helped. Remember to set up your window before using the code...

How do I align something to the bottom left in Tkinter using either .grid() or .pack()?

I am currently trying to make a game in Tkinter which uses multiple different windows.
However, as I am trying to create the layout of a secondary window, I can't seem to get my Return to Menu button underneath the list box, and aligned to the left. I have tried it using .pack() and .grid() methods, but they don't make sense to me.
I've tried using .pack():
header = Frame(wn).pack()
title = Label(header, text='Single-Player',font=('Arial bold',20),bg=bgcolor).pack(anchor='center')
footer = Frame(wn).pack(side=BOTTOM)
return_to_menu = Button(footer, text='Return to Main Menu',font=('Arial',16),bg=bgcolor,command=lambda: menu()).pack(side=BOTTOM,padx=20,pady=250)
# body frame (left side)
bodyL = Frame(wn).pack(side=LEFT)
#output box
output = Listbox(bodyL, width=50, font=("Arial", 20)).pack(side=LEFT,padx=15)`
And I've tried using .grid():
header = Frame(wn).grid(sticky=N)
title = Label(header, text='Single-Player',font=('Arial bold',20),bg=bgcolor).grid(sticky=N+E+W,row=0,column=0)
footer = Frame(wn).grid(sticky=S)
return_to_menu = Button(footer, text='Return to Main Menu',font=('Arial',16),bg=bgcolor,command=lambda: menu()).grid(sticky=S,padx=20,row=0,column=0)
# body frame (left side)
bodyL = Frame(wn).grid(sticky=W)
#output box
output = Listbox(bodyL, width=50, font=("Arial", 20)).grid(sticky=W,padx=15, )`
However using .grid() doesn't align my title to the center of the screen anymore.
Is there a way to center it more efficiently - I didn't like using padx=450 to get it centered.
What happens with the button and the list box, is the button appears to the left of the list box, instead of on the bottom. I have attempted several other methods, such as incrementing row numbers, but my script doesn't appear to do what I anticipated.
Here is an example of how you can set up the weight of specific columns and row to get widgets to stick to a specific location on the screen.
With the use of grid() we need to use columnconfigure() and rowconfigure() to make this work.
These 2 methods are used to define at what ratio the column or row will expand in relation to the columns or rows around it as the container grows or shrinks.
See below example and let me know if you have any questions:
import tkinter as tk
root = tk.Tk()
for i in range(3):
root.columnconfigure(i, weight=1)
root.rowconfigure(1, weight=1)
tk.Label(root, text='Top left').grid(row=0, column=0, sticky='w')
tk.Label(root, text='Top center').grid(row=0, column=1)
tk.Label(root, text='Top right').grid(row=0, column=2, sticky='e')
tk.Label(root, text='center').grid(row=1, column=1)
tk.Label(root, text='Bottom left').grid(row=2, column=0, sticky='w')
tk.Label(root, text='Bottom center').grid(row=2, column=1)
tk.Label(root, text='Bottom right').grid(row=2, column=2, sticky='e')
root.mainloop()
Example:
Here is another example but this time I have a title label outside of a frame so that we can make it easier to manage the title being centered and then working with all the other content of the frame is separate from the title label.
import tkinter as tk
root = tk.Tk()
root.columnconfigure(0, weight=1)
root.rowconfigure(1, weight=1)
frame = tk.Frame(root)
frame.grid(row=1, column=0, sticky='nsew')
for i in range(3):
frame.columnconfigure(i, weight=1)
frame.rowconfigure(1, weight=1)
tk.Label(root, text='Title centered').grid(row=0, column=0)
tk.Label(frame, text='Top left').grid(row=0, column=0, sticky='w')
tk.Label(frame, text='Top center').grid(row=0, column=1)
tk.Label(frame, text='Top right').grid(row=0, column=2, sticky='e')
tk.Label(frame, text='Center').grid(row=1, column=1)
tk.Label(frame, text='Bottom left').grid(row=2, column=0, sticky='w')
tk.Label(frame, text='Bottom center').grid(row=2, column=1)
tk.Label(frame, text='Bottom right').grid(row=2, column=2, sticky='e')
tk.Label(root, text='Footer centered').grid(row=2, column=0)
root.mainloop()
Example:
To address your problem in the comments you cannot use grid() or any other geometry manager for that matter on the same line you create your container. This will always cause the variable for that frame to return None as the geometry managers return None when called.
See this image as to what happens when you use grid() on the same line you create your container.
Now if you delete the grid() part on the row that your container is created and then write it on the next line as the commented out section of the above images shows it will work as expected. See below image of proper use of grid() for containers.
To address your 2nd question in the comments you can add this line to provide a button at the bottom left.
tk.Button(root, text='Bottom left button').grid(row=3, column=0, sticky='w')
Example:

Is their a grid_remember()? Reversible grid_forget()?

I am confused on the documentation surrounding the tkinter "grid_forget()"
I know that this function does not permanently delete the widget ascribed to it, however I do not know how to call it again. Further, if the widget is forgotten in a frame, can it be called back to the same the frame?
You can call grid() with no parameters to reverse the effects of grid_remove().
In the following example there is a label that is placed at the top of the window with grid. There is a toggle button that will alternate between calling grid and grid_remove to show that calling grid with no parameters will restore the message exactly as it was.
Notice, for example, that both the row, column, and columnspan attributes are remembered when the message reappears.
import tkinter as tk
class Example():
def __init__(self):
self.root = tk.Tk()
self.root.grid_rowconfigure(2, weight=1)
self.root.grid_columnconfigure(1, weight=1)
self.toolbar = tk.Frame(self.root)
self.toggle = tk.Button(self.toolbar, text="Toggle the message",
command=self.toggle_message)
self.toggle.pack(side="left")
# simulate a typical app with a navigation area on the left and a main
# working area on the right
self.navpanel = tk.Frame(self.root, background="bisque", width=100, height=200)
self.main = tk.Frame(self.root, background="white", width=300, height=200, bd=1, relief='sunken')
self.message = tk.Label(self.root, text="Hello, world!")
self.toolbar.grid(row=0, column=0, columnspan=2)
self.message.grid(row=1, column=0, columnspan=2)
self.navpanel.grid(row=2, column=0, sticky="nsew")
self.main.grid(row=2, column=1, sticky="nsew")
def start(self):
self.root.mainloop()
def toggle_message(self):
if self.message.winfo_viewable():
self.message.grid_remove()
else:
self.message.grid()
if __name__ == "__main__":
Example().start()
If you change the code from using grid_remove to using grid_forget, restoring the label will not put it back in the same place or with the same options. That is the main distinction between grid_remove and grid_forget -- grid_forget literally forgets the grid options whereas grid_remove removes the widget but remembers the settings.
Here is a simple example to illustrate what is happening when you remove a widget from the grid then re-grid it. You simply need to re apply the grid the same way you would have done in the first place. You can even chose a different grid location if you like. Though I am not sure if you can change the container it was originally assigned to. If not then it will only be able to be re-added to the original container the widget was assigned to.
import tkinter as tk
root = tk.Tk()
some_label = tk.Label(root, text="IM HERE!")
some_label.grid(row=0, column=0, columnspan=2)
def forget_label():
some_label.grid_forget()
def return_label():
some_label.grid(row=0, column=0, columnspan=2)
tk.Button(root, text="Forget Label", command=forget_label).grid(row=1, column=0)
tk.Button(root, text="Return Label", command=return_label).grid(row=1, column=1)
root.mainloop()

Categories