Change Geometry of TTK Frame? - python

So I have an App/TTK.Frame class, and I wish to change it once my program starts so that I can change the way it looks and turn it into a "Status: xxxx" kind of thing at the top of the screen.
However, I can't seem to be able to edit the geometry because this is a ttk.frame class and thus doing root.geometry('100x100') does nothing.
class App(ttk.Frame):
def __init__(self, parent):
ttk.Frame.__init__(self)
# Make the app responsive
for index in [0, 1, 2]:
self.columnconfigure(index=index, weight=1)
self.rowconfigure(index=index, weight=1)
# Create control variables
self.barbs = tk.BooleanVar(value=True)
self.buildings = tk.BooleanVar(value=True)
self.troops = tk.BooleanVar(value=True)
self.exploration = tk.BooleanVar(value=True)
self.gather = tk.StringVar()
# Create widgets :)
self.setup_widgets()
def setup_widgets(self):
#### Irrelevant code before this
self.button = ttk.Button(self.widgets_frame, text="Button", command=self.startbtn2)
self.button.grid(row=2, column=0, padx=0, pady=0, sticky="nsew")
def startbtn2(self):
self.changewindow()
#### Irrelevant code after this
def changewindow(self):
self.widgets_frame.destroy()
self.check_frame.destroy()
root.geometry('100x100')
Also thought about doing it a different way, instead of destroying all the widgets and changing the geometry of the current window, I can just open a new one once the button is clicked / startbtn2 function starts, however, I need to call root.lift() on a loop so that the window always stays at the top of all other windows, and I think the only way to do so is if that's the main window, not a newly created one.

Related

Tkinter window not updating after changing the window.columnconfigure()

I just started using python and I am trying to make a simple gui that consists of 3 frames. One on the left, one on the right and one in the middle. Later I'd like to add buttons and stuff to those frames but for now that is all. I want the left frame to disappear or appear again if I press the escape key. To do this I have written the following code:
from tkinter import Tk, Button, Label, Frame
class Main:
def __init__(self):
self.root = Tk()
self.init_gui()
def init_gui(self):
self.root.title("Gui Testing")
self.root.minsize(900,600)
self.root.bind("<Escape>", self.toggle_left_menu)
self.root.grid_columnconfigure(0, minsize=200)
self.root.grid_columnconfigure(1, weight=1)
self.root.grid_columnconfigure(2, minsize=250)
self.root.grid_rowconfigure(0, weight=1)
# main 3 panels
self.left_menu_active = True
self.left_menu = Frame(self.root, bg="#333")
self.left_menu.grid(row=0, column=0, sticky="nsew")
self.center = Frame(self.root, bg="white")
self.center.grid(row=0, column=1, sticky="nsew")
self.right_menu = Frame(self.root, bg="#888")
self.right_menu.grid(row=0, column=2, sticky="nsew")
self.toggle_left_menu()
def toggle_left_menu(self, event=None):
if self.left_menu_active == True:
self.left_menu_active = False
self.root.grid_columnconfigure(0, minsize=0)
self.left_menu.grid_forget()
else:
self.left_menu_active = True
self.root.grid_columnconfigure(0, minsize=200)
self.left_menu.grid(row=0, column=0, sticky="nsew")
def start(self):
self.root.mainloop()
Main().start()
The problem is that when I press escape, nothing happens. However, when I then move the window by clicking on it and dragging it, it updates all of a sudden and it shows the window the way I want it. So the code seems to be working but the window isn't updating for some reason.
I don't know what I can do about that. I found out that it does update the grid positions of the left and the center frame, but the grid_configure() doesn't seem to update without moving the window.
Is there a way to update the frame or to achieve the frame toggling in some other way?
Edit:
The problem has been solved by adding a button to each frame. Now the frames are not empty anymore it seems works. I also edited the toggle_left_menu() function a bit. This is what I changed:
Added Buttons:
self.test_button1 = Button(self.left_menu, text="left", padx=10, pady=5)
self.test_button1.grid(row=0, column=0)
self.test_button2 = Button(self.center, text="center", padx=10, pady=5)
self.test_button2.grid(row=0, column=0)
self.test_button3 = Button(self.right_menu, text="right", padx=10, pady=5)
self.test_button3.grid(row=0, column=0)
Edited toggle_left_menu():
def toggle_left_menu(self, event=None):
if self.left_menu.winfo_viewable():
self.root.grid_columnconfigure(0, minsize=0)
self.left_menu.grid_remove()
else:
self.root.grid_columnconfigure(0, minsize=200)
self.left_menu.grid()
This worked for me, thanks!
Extending Bryan Oakley's example ~ you have to toggle the minsize, as well. As an aside, I classed out all of your gui and made Main the root. All that self.root.this and self.root.that is unnecessary this way. Also, you would have had to do this anyway, unless you intended to dump your entire gui contents into your init_gui method. If your app is large that would be a nightmare to keep track of. As an added bonus, I made the whole toggle_menu method dynamic so it can toggle either menu. You can change the key-bindings, to whatever. I used Escape then l for left_menu and Escape then r for right_menu.
from tkinter import Tk, Button, Label, Frame
class LeftMenu(Frame):
#property
def minsize(self):
return 200
def __init__(self, master, row=0, column=0, **kwargs):
Frame.__init__(self, master, **kwargs)
self.grid(row=row, column=column, sticky='nswe')
class RightMenu(Frame):
#property
def minsize(self):
return 250
def __init__(self, master, row=0, column=0, **kwargs):
Frame.__init__(self, master, **kwargs)
self.grid(row=row, column=column, sticky='nswe')
class Center(Frame):
def __init__(self, master, row=0, column=0, **kwargs):
Frame.__init__(self, master, **kwargs)
self.grid(row=row, column=column, sticky='nswe')
class Main(Tk):
def __init__(self):
Tk.__init__(self)
self.bind("<Escape><l>", self.toggle_menu)
self.bind("<Escape><r>", self.toggle_menu)
self.grid_columnconfigure(0, minsize=200)
self.grid_columnconfigure(1, weight=1)
self.grid_columnconfigure(2, minsize=250)
self.grid_rowconfigure(0, weight=1)
# main 3 panels
self.left_menu = LeftMenu(self, 0, 0, bg="#333")
self.center = Center(self, 0, 1, bg="white")
self.right_menu = RightMenu(self, 0, 2, bg="#888")
self.toggle_menu(menu=self.left_menu)
def toggle_menu(self, event=None, menu=None):
if event and event.char in 'lr':
menu = self.left_menu if event.char == 'l' else self.right_menu
if menu:
if menu.winfo_viewable():
self.grid_columnconfigure(menu.grid_info()['column'], minsize=0)
menu.grid_remove()
else:
menu.grid()
self.grid_columnconfigure(menu.grid_info()['column'], minsize=menu.minsize)
self.after_idle(self.event_generate, '<Configure>')
if __name__ == "__main__":
root = Main()
root.title("Gui Testing")
root.minsize(900,600)
root.mainloop()
Part of the problem is that minsize only affects the minimum size. If the left frame is visible and is more than zero pixels wide, setting the minsize to zero isn't going to make the frame smaller. So, one step is to remove the minsize=200 option for column 0.
Since you are using grid, the best way to hide or show a frame is to use grid_remove to remove the widget, and then grid to restore it. grid_remove will remove the widget from the window but remember all of its settings. When you subsequently call .grid(), all of the previous settings will be used.
You can also just check if the window is visible or not without having to manage a boolean flag since your function is a toggle. That simplifies the code a bit.
Also, I think there's a bug on some versions of tk (the library upon which tkinter is built) that prevents the window from refreshing in this specific type of situation. What works for me is to synthetically generate a <Configure> event on the root window.
Rolling all of that together, this version of your toggle function works for me on OSX without any other modifications to your code.
def toggle_left_menu(self, event=None):
if self.left_menu.winfo_viewable():
self.left_menu.grid_remove()
self.root.after_idle(self.root.event_generate, '<Configure>')
else:
self.left_menu.grid()
self.root.after_idle(self.root.event_generate, '<Configure>')

Tkinter - I created a custom scrollable frame class. Is it possible for it to automatically call a function when a child is added to it?

I make a custom tkinter widget (ScrollFrame) to make a scrollable frame using the answer from this question: Tkinter scrollbar for frame
Everything is working fine but I have to call the "ConfigureCanvas" function every time new widget is added to ScrollFrame in order to resize the scroll area. Are there any event binds that I could use that would be called when a new widget is packed/grided/placed to the Scroll Frame?
eg:
class ScrollFrame(Frame):
'''
'''
self.packFrame.bind('<NewChildWidgetAdded>', self.ConfigureCanvas)
'''
'''
exampleLabel = Label(packFrame, text='Hello')
exampleLabel.pack() # activate the "NewChildWidgetAdded" event?
Here's working code (python 3+). I created a loop that creates 50 labels to give me something to scroll over.
import tkinter
from tkinter import Tk, Frame, filedialog, Button, Listbox, Label, Entry, Text, Canvas, Scrollbar, Radiobutton, Checkbutton, Menu, IntVar, StringVar, BooleanVar, Grid, OptionMenu, Toplevel, ALL, CURRENT, END
#imported more than i needed, copied from other code where this is all used.
class ScrollFrame(Frame):
def __init__(self, frame, *args, **kwargs):
Frame.__init__(self, frame, *args, **kwargs)
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.vScroll = Scrollbar(self, orient='vertical')
self.vScroll.grid(row=0, column=1, sticky='wens')
self.hScroll = Scrollbar(self, orient='horizontal')
self.hScroll.grid(row=1, column=0, sticky='wens')
self.canvas = Canvas(self, bd=0, highlightthickness=0, xscrollcommand=self.hScroll.set, yscrollcommand=self.vScroll.set, bg='green')
self.canvas.grid(row=0, column=0, sticky='wens')
self.vScroll.config(command=self.canvas.yview)
self.hScroll.config(command=self.canvas.xview)
self.packFrame = Frame(self.canvas, bg='blue')
self.packWindow = self.canvas.create_window(0,0, window=self.packFrame, anchor='nw')
def ConfigureCanvas(self):
self.packFrame.update_idletasks()
size = (self.packFrame.winfo_reqwidth(), self.packFrame.winfo_reqheight())
self.canvas.config(scrollregion=(0,0,size[0], size[1]))
mw = Tk()
scrollCavnas = ScrollFrame(mw)
scrollCavnas.pack(side='right', fill='both', expand='yes')
scrollFrame = scrollCavnas.packFrame
temp = 0
while temp < 50:
Label(scrollFrame, text=temp).grid(row=temp, column=temp)
temp += 1
scrollCavnas.ConfigureCanvas() # don't want to have to call this every time a new widget is added.
mw.mainloop()
I tried:
self.packFrame.bind('<map>', self.ConfigureCanvas)
But it looks like this only gets called when the ScrollFrame is created, not when I add a new child widget to the ScrollFrame.
I looked over the documentation (http://tcl.tk/man/tcl8.5/TkCmd/bind.htm#M13) but I didn't notice anything that could do what I wanted.
One solution is to use the Configure event. The downside is this gets called every time the widget is scrolled.
self.packFrame.bind('<Configure>', self.ConfigureCanvas)
def ConfigureCanvas(self, event=None):
self.packFrame.update_idletasks()
size = (self.packFrame.winfo_reqwidth(), self.packFrame.winfo_reqheight())
self.canvas.config(scrollregion=(0,0,size[0], size[1]))
I was able to get at least part of the function to only run when adding a new widget. This seems clunky and I believe it will only work if scrolling horizontally (it could be modified to do vertical or both though). This compares the required width to the current scroll region size and if they're different it will resize the scroll region.
def ConfigureCanvas(self, event=None):
scrollRegion = self.canvas['scrollregion'].split(' ')
try:
scrollRegion = int(scrollRegion[2])
except IndexError:
scrollRegion = None
if self.packFrame.winfo_reqwidth() != scrollRegion:
self.packFrame.update_idletasks()
size = (self.packFrame.winfo_reqwidth(), self.packFrame.winfo_reqheight())
self.canvas.config(scrollregion=(0,0,size[0], size[1]))

Specific issue using pack while creating a layout

I must use tkinter library to create GUI.
I have this code:
# -*- coding: UTF-8 -*-
import tkinter as tk
class Application(tk.Frame):
resx=1600
resy=900
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.pack(fill="both", expand=1)
self.createWidgets()
master.minsize(self.resx, self.resy)
master.maxsize(self.resx, self.resy)
def createWidgets(self):
self.hi_there = tk.Button(self)
self.hi_there["text"] = "Create new window"
self.hi_there["command"] = self.PlayMode
self.hi_there.pack(side="top")
def ShowMenu(self, master):
print("Here I need DELETE master, in my case PlayM")
def PlayMode(self):
PlayM = tk.Tk()
PlayM.minsize(self.resx, self.resy)
PlayM.maxsize(self.resx, self.resy)
PlayM.title("Game")
bf= tk.Frame(PlayM, bg="blue")
bf.pack(side="bottom", fill=tk.X, expand = 1)
lbTEST=tk.Label(bf)
lbTEST["text"] = "TESTING"
lbTEST.pack()
mf = tk.Frame(PlayM,bg="red")
mf.pack(side="right", fill=tk.Y, expand=1)
self.LogOut = tk.Button(mf)
self.LogOut["text"] = "LOGOUT"
self.LogOut.pack()
self.LogOut["command"] = self.ShowMenu(PlayM)
root = tk.Tk()
app = Application(master=root)
app.master.title("Useless think")
app.mainloop()
I need something like this picture:
I don't know why my code is not working. When I pack my bf (bottom frame) and set side = "bottom", but it appears in the middle of the screen. Why?
Same with side = "right" when I pack mf (menu frame)
And I have one more question. About logout button. I set command's method "ShowMenu".
When I run my code, this method is started automatically only once, but when I click to button nothing happens. Why?
First, you have a critical flaw in your code. You should not be creating more than one instance of Tk. If you need to create additional windows, create instances of Toplevel.
When I pack my bf (bottom frame) and set side = "bottom", but it appears in the middle of the screen. Why?
You set expand to 1 for both mf and mf so each will end up taking half of the available space in the window. If you simply set expand to 0 (zero) or False for bf, it will only take up as much space as necessary.
bf.pack(side="bottom", fill=tk.X, expand = 0)
As a general rule, you should only set a true value for expand on a single widget (the "hero" widget), unless you want to distribute extra space equally among widgets.
When I run my code, this method is started automatically only once, but when I click to button nothing happens. Why?
Because you're telling it to run. Take a look at this code:
self.LogOut["command"] = self.ShowMenu(PlayM)
It is exactly the same as this code:
result = self.ShowMenu(PlayM)
self.logOut["command"] = result
See the problem?
The command attribute requires a reference to a function. Roughly translated, that means you can't use (). If you need to pass in an argument to the command, the typical solution is to use functools.partial or lambda to create a reference to an anonymous function that will call the real function with the argument:
self.logOut["command"] = lambda arg=PlayM: self.ShowMenu(arg)
Is there any specific reason why you use pack method instead of grid?
You could configure your Application frame like:
self.grid_rowconfigure(0, weight=1) #make the first "upper" row expand
self.grid_rowconfigure(1, weight=0) #leave the second "lower" row and do not expand it
self.grid_columnconfigure(0, weight=0) # first column, do not expand
self.grid_columnconfigure(1, weight=1) # second columnd, DO expand
self.grid_columnconfigure(0, weight=0) # third column, do not expand
on your mainframe/application class and then call:
bf.grid(row=1, column=0, columnspan=3, sticky=tk.NW+tk.SE) # span over three columns
lf.grid(row=0, column=0, sticky=tk.NW+tk.SE) # default is to span over one column
rf.grid(row=0, column=2, sticky=tk.NW+tk.SE)
Edit
I am very sorry, i forgot to mention about the logout button.
You call the event handler at binding time, therefore it will be executed there.
If you want to have it pass values either use:
command=lambda parent=PlayM: self.ShowMenu(parent)
use a class object to store youre parent reference to at creation time self._Parent = PlayM and use this inside ShowMenu
I personally prefer storing objects for single values. if you have many windows to destroy, I would use the lambda.
Apart from the problems already mentioned by Bryan Oakley and the solution given by R4PH43L using grid, here's a possible solution for your layout only using pack.
The idea is that by using side="left" (or side="right") and then side="top" (or side="bottom") does not work the way you may be expecting.
Within a frame you should just be using values for side (when packing the widgets in that same frame) which are either vertical ("top" or "bottom") or horizontal ("right" or "left"), but not both. Thus to accomplish layouts like yours using only pack you need additional frames!
# -*- coding: UTF-8 -*-
import tkinter as tk
class Application(tk.Frame):
resx=400
resy=200
def __init__(self, master=None):
tk.Frame.__init__(self, master, bg="red")
self.pack(fill="both", expand=1)
self.create_widgets()
master.geometry("400x200+100+500")
def create_widgets(self):
self.hi_there = tk.Button(self)
self.hi_there["text"] = "Create new window"
self.hi_there["command"] = self.play_mode
self.hi_there.pack(side="top")
def show_menu(self, master):
print("Here I need DELETE master, in my case play_m")
def play_mode(self):
# Using a Toplevel instead of a Tk
# You should have just one Tk in your app
play_m = tk.Toplevel()
play_m.geometry("400x200+100+500")
play_m.title("Game")
top_frame = tk.Frame(play_m, bg="blue")
top_frame.pack(side="top", fill="both", expand=True)
left_top_frame = tk.Frame(top_frame, bg="white")
left_top_frame_label = tk.Label(left_top_frame, text="left top frame")
left_top_frame_label.pack()
left_top_frame.pack(side="left", fill="y")
middle_top_frame = tk.Frame(top_frame, bg="black")
middle_top_frame_button = tk.Button(middle_top_frame, text="Logout", command=play_m.destroy)
middle_top_frame_button.pack()
middle_top_frame.pack(side="left", fill="both", expand=True)
right_top_frame = tk.Frame(top_frame, bg="white")
right_top_frame_label = tk.Label(right_top_frame, text="right top frame")
right_top_frame_label.pack()
right_top_frame.pack(side="right", fill="y")
bottom_frame = tk.Frame(play_m, bg="yellow")
bottom_frame_label = tk.Label(bottom_frame, text="bottom frame")
bottom_frame_label.pack()
bottom_frame.pack(side="top", fill="both")
root = tk.Tk()
app = Application(master=root)
app.master.title("Useless think")
app.mainloop()
Here's the result of the main Tk window on a OS X (Sierra):
Second toplevel
I changed a little bit the sizes for the sake of exposition. I also renamed the methods to use _ and lower case letters.

How to delete Tkinter widgets from a window?

I have a list of tkinter widgets that I want to change dynamically.
How to delete the widgets from the window?
You can call pack_forget to remove a widget (if you use pack to add it to the window).
Example:
from tkinter import *
root = Tk()
b = Button(root, text="Delete me", command=lambda: b.pack_forget())
b.pack()
root.mainloop()
If you use pack_forget, you can later show the widget again calling pack again. If you want to permanently delete it, call destroy on the widget (then you won't be able to re-add it).
If you use the grid method, you can use grid_forget or grid_remove to hide the widget.
One way you can do it, is to get the slaves list from the frame that needs to be cleared and destroy or "hide" them according to your needs. To get a clear frame you can do it like this:
from tkinter import *
root = Tk()
def clear():
list = root.grid_slaves()
for l in list:
l.destroy()
Label(root,text='Hello World!').grid(row=0)
Button(root,text='Clear',command=clear).grid(row=1)
root.mainloop()
You should call grid_slaves(), pack_slaves() or slaves() depending on the method you used to add the widget to the frame.
You simply use the destroy() method to delete the specified widgets like this:
lbl = tk.Label(....)
btn = tk.Button(....., command=lambda: lbl.destroy())
Using this you can completely destroy the specific widgets.
You say that you have a list of widgets to change dynamically. Do you want to reuse and reconfigure existing widgets, or create all new widgets and delete the old ones? It affects the answer.
If you want to reuse the existing widgets, just reconfigure them. Or, if you just want to hide some of them temporarily, use the corresponding "forget" method to hide them. If you mapped them with pack() calls, you would hide with pack_forget() (or just forget()) calls. Accordingly, grid_forget() to hide gridded widgets, and place_forget() for placed widgets.
If you do not intend to reuse the widgets, you can destroy them with a straight destroy() call, like widget.destroy(), to free up resources.
clear_btm=Button(master,text="Clear") #this button will delete the widgets
clear_btm["command"] = lambda one = button1, two = text1, three = entry1: clear(one,two,three) #pass the widgets
clear_btm.pack()
def clear(*widgets):
for widget in widgets:
widget.destroy() #finally we are deleting the widgets.
Today I learn some simple and good click event handling using tkinter gui library in python3, which I would like to share inside this thread.
from tkinter import *
cnt = 0
def MsgClick(event):
children = root.winfo_children()
for child in children:
# print("type of widget is : " + str(type(child)))
if str(type(child)) == "<class 'tkinter.Message'>":
# print("Here Message widget will destroy")
child.destroy()
return
def MsgMotion(event):
print("Mouse position: (%s %s)" % (event.x, event.y))
return
def ButtonClick(event):
global cnt, msg
cnt += 1
msg = Message(root, text="you just clicked the button..." + str(cnt) + "...time...")
msg.config(bg='lightgreen', font=('times', 24, 'italic'))
msg.bind("<Button-1>", MsgClick)
msg.bind("<Motion>", MsgMotion)
msg.pack()
#print(type(msg)) tkinter.Message
def ButtonDoubleClick(event):
import sys; sys.exit()
root = Tk()
root.title("My First GUI App in Python")
root.minsize(width=300, height=300)
root.maxsize(width=400, height=350)
button = Button(
root, text="Click Me!", width=40, height=3
)
button.pack()
button.bind("<Button-1>", ButtonClick)
button.bind("<Double-1>", ButtonDoubleClick)
root.mainloop()
Hope it will help someone...
You can use forget method on the widget
from tkinter import *
root = Tk()
b = Button(root, text="Delete me", command=b.forget)
b.pack()
b['command'] = b.forget
root.mainloop()
I found that when the widget is part of a function and the grid_remove is part of another function it does not remove the label. In this example...
def somefunction(self):
Label(self, text=" ").grid(row = 0, column = 0)
self.text_ent = Entry(self)
self.text_ent.grid(row = 1, column = 0)
def someotherfunction(self):
somefunction.text_ent.grid_remove()
...there is no valid way of removing the Label.
The only solution I could find is to give the label a name and make it global:
def somefunction(self):
global label
label = Label(self, text=" ")
label.grid(row = 0, column = 0)
self.text_ent = Entry(self)
self.text_ent.grid(row = 1, column = 0)
def someotherfunction(self):
global label
somefunction.text_ent.grid_remove()
label.grid_remove()
When I ran into this problem there was a class involved, one function being in the class and one not, so I'm not sure the global label lines are really needed in the above.

In Tkinter is there any way to make a widget invisible?

Something like this, would make the widget appear normally:
Label(self, text = 'hello', visible ='yes')
While something like this, would make the widget not appear at all:
Label(self, text = 'hello', visible ='no')
You may be interested by the pack_forget and grid_forget methods of a widget. In the following example, the button disappear when clicked
from Tkinter import *
def hide_me(event):
event.widget.pack_forget()
root = Tk()
btn=Button(root, text="Click")
btn.bind('<Button-1>', hide_me)
btn.pack()
btn2=Button(root, text="Click too")
btn2.bind('<Button-1>', hide_me)
btn2.pack()
root.mainloop()
One option, as explained in another answer, is to use pack_forget or grid_forget. Another option is to use lift and lower. This changes the stacking order of widgets. The net effect is that you can hide widgets behind sibling widgets (or descendants of siblings). When you want them to be visible you lift them, and when you want them to be invisible you lower them.
The advantage (or disadvantage...) is that they still take up space in their master. If you "forget" a widget, the other widgets might readjust their size or orientation, but if you raise or lower them they will not.
Here is a simple example:
import Tkinter as tk
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.frame = tk.Frame(self)
self.frame.pack(side="top", fill="both", expand=True)
self.label = tk.Label(self, text="Hello, world")
button1 = tk.Button(self, text="Click to hide label",
command=self.hide_label)
button2 = tk.Button(self, text="Click to show label",
command=self.show_label)
self.label.pack(in_=self.frame)
button1.pack(in_=self.frame)
button2.pack(in_=self.frame)
def show_label(self, event=None):
self.label.lift(self.frame)
def hide_label(self, event=None):
self.label.lower(self.frame)
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
I know this is a couple of years late, but this is the 3rd Google response now for "Tkinter hide Label" as of 10/27/13... So if anyone like myself a few weeks ago is building a simple GUI and just wants some text to appear without swapping it out for another widget via "lower" or "lift" methods, I'd like to offer a workaround I use (Python2.7,Windows):
from Tkinter import *
class Top(Toplevel):
def __init__(self, parent, title = "How to Cheat and Hide Text"):
Toplevel.__init__(self,parent)
parent.geometry("250x250+100+150")
if title:
self.title(title)
parent.withdraw()
self.parent = parent
self.result = None
dialog = Frame(self)
self.initial_focus = self.dialog(dialog)
dialog.pack()
def dialog(self,parent):
self.parent = parent
self.L1 = Label(parent,text = "Hello, World!",state = DISABLED, disabledforeground = parent.cget('bg'))
self.L1.pack()
self.B1 = Button(parent, text = "Are You Alive???", command = self.hello)
self.B1.pack()
def hello(self):
self.L1['state']="normal"
if __name__ == '__main__':
root=Tk()
ds = Top(root)
root.mainloop()
The idea here is that you can set the color of the DISABLED text to the background ('bg') of the parent using ".cget('bg')" http://effbot.org/tkinterbook/widget.htm rendering it "invisible". The button callback resets the Label to the default foreground color and the text is once again visible.
Downsides here are that you still have to allocate the space for the text even though you can't read it, and at least on my computer, the text doesn't perfectly blend to the background. Maybe with some tweaking the color thing could be better and for compact GUIs, blank space allocation shouldn't be too much of a hassle for a short blurb.
See Default window colour Tkinter and hex colour codes for the info about how I found out about the color stuff.
I'm also extremely late to the party, but I'll leave my version of the answer here for others who may have gotten here, like I did, searching for how to hide something that was placed on the screen with the .place() function, and not .pack() neither .grid().
In short, you can hide a widget by setting the width and height to zero, like this:
widget.place(anchor="nw", x=0, y=0, width=0, height=0)
To give a bit of context so you can see what my requirement was and how I got here.
In my program, I have a window that needs to display several things that I've organized into 2 frames, something like this:
[WINDOW - app]
[FRAME 1 - hMainWndFrame]
[Buttons and other controls (widgets)]
[FRAME 2 - hJTensWndFrame]
[other Buttons and controls (widgets)]
Only one frame needs to be visible at a time, so on application initialisation, i have something like this:
hMainWndFrame = Frame(app, bg="#aababd")
hMainWndFrame.place(anchor="nw", x=0, y=0, width=480, height=320)
...
hJTensWndFrame = Frame(app, bg="#aababd")
I'm using .place() instead of .pack() or .grid() because i specifically want to set precise coordinates on the window for each widget. So, when i want to hide the main frame and display the other one (along with all the other controls), all i have to do is call the .place() function again, on each frame, but specifying zero for width and height for the one i want to hide and the necessary width and height for the one i want to show, such as:
hMainWndFrame.place(anchor="nw", x=0, y=0, width=0, height=0)
hJTensWndFrame.place(anchor="nw", x=0, y=0, width=480, height=320)
Now it's true, I only tested this on Frames, not on other widgets, but I guess it should work on everything.
For hiding a widget you can use function pack_forget() and to again show it you can use pack() function and implement them both in separate functions.
from Tkinter import *
root = Tk()
label=Label(root,text="I was Hidden")
def labelactive():
label.pack()
def labeldeactive():
label.pack_forget()
Button(root,text="Show",command=labelactive).pack()
Button(root,text="Hide",command=labeldeactive).pack()
root.mainloop()
I was not using grid or pack.
I used just place for my widgets as their size and positioning was fixed.
I wanted to implement hide/show functionality on frame.
Here is demo
from tkinter import *
window=Tk()
window.geometry("1366x768+1+1")
def toggle_graph_visibility():
graph_state_chosen=show_graph_checkbox_value.get()
if graph_state_chosen==0:
frame.place_forget()
else:
frame.place(x=1025,y=165)
score_pixel = PhotoImage(width=300, height=430)
show_graph_checkbox_value = IntVar(value=1)
frame=Frame(window,width=300,height=430)
graph_canvas = Canvas(frame, width = 300, height = 430,scrollregion=(0,0,300,300))
my_canvas=graph_canvas.create_image(20, 20, anchor=NW, image=score_pixel)
vbar=Scrollbar(frame,orient=VERTICAL)
vbar.config(command=graph_canvas.yview)
vbar.pack(side=RIGHT,fill=Y)
graph_canvas.config(yscrollcommand=vbar.set)
graph_canvas.pack(side=LEFT,expand=True,fill=BOTH)
frame.place(x=1025,y=165)
Checkbutton(window, text="show graph",variable=show_graph_checkbox_value,command=toggle_graph_visibility).place(x=900,y=165)
window.mainloop()
Note that in above example when 'show graph' is ticked then there is vertical scrollbar.
Graph disappears when checkbox is unselected.
I was fitting some bar graph in that area which I have not shown to keep example simple.
Most important thing to learn from above is the use of frame.place_forget() to hide and frame.place(x=x_pos,y=y_pos) to show back the content.
For someone who hate OOP like me (This is based on Bryan Oakley's answer)
import tkinter as tk
def show_label():
label1.lift()
def hide_label():
label1.lower()
root = tk.Tk()
frame1 = tk.Frame(root)
frame1.pack()
label1 = tk.Label(root, text="Hello, world")
label1.pack(in_=frame1)
button1 = tk.Button(root, text="Click to hide label",command=hide_label)
button2 = tk.Button(root, text="Click to show label", command=show_label)
button1.pack(in_=frame1)
button2.pack(in_=frame1)
root.mainloop()
import tkinter as tk
...
x = tk.Label(text='Hello', visible=True)
def visiblelabel(lb, visible):
lb.config(visible=visible)
visiblelabel(x, False) # Hide
visiblelabel(x, True) # Show
P.S. config can change any attribute:
x.config(text='Hello') # Text: Hello
x.config(text='Bye', font=('Arial', 20, 'bold')) # Text: Bye, Font: Arial Bold 20
x.config(bg='red', fg='white') # Background: red, Foreground: white
It's a bypass of StringVar, IntVar etc.

Categories