Related
I have a class called Bones I have 5 Bones in my skeleton dictionary. However in my actual implementation there are 300+ bones, that's why I am asking this question today on stackoverflow.
Each Bone has:
ID: An int to identify a bone
w: w position (float between -1 and 1)
x: x position (float between -1 and 1)
y: y position (float between -1 and 1)
z: z position (float between -1 and 1)
Bone.py
INCREMENT = 0.01
class Bone:
def __init__(self, boneId, w, x, y, z):
self.id = boneId
self.w = w
self.x = x
self.y = y
self.z = z
def shouldChangePos(self, num):
if (num >= 1 or num <= -1):
return False
return True
def incrW(self):
if(self.shouldChangePos(self.w)):
self.w = self.w + INCREMENT
def decrW(self):
if(self.shouldChangePos(self.w)):
self.w = self.w - INCREMENT
def incrX(self):
if(self.shouldChangePos(self.x)):
self.x = self.x + INCREMENT
def decrX(self):
if(self.shouldChangePos(self.x)):
self.x = self.x - INCREMENT
def incrY(self):
if(self.shouldChangePos(self.y)):
self.y = self.y + INCREMENT
def decrY(self):
if(self.shouldChangePos(self.y)):
self.y = self.y - INCREMENT
def incrZ(self):
if(self.shouldChangePos(self.z)):
self.z = self.z + INCREMENT
def decrZ(self):
if(self.shouldChangePos(self.z)):
self.z = self.z - INCREMENT
Explanation of the problem
I am trying to make a tkinter GUI that looks something like this:
Legend:
Green - represents a Frame (just my annotation to explain)
Red - are attributes of the object (just my annotation to explain)
Black - are methods of the object (just my annotation to explain)
Blue - are text and buttons displayed to me
As you can see, it shows the ID, w, x, y, z. And under it, there is a + button and a - button. Each time these buttons get clicked, I want to decrease the corresponding value in the object and update the tkinter number displayed. I know how to do this manually, but as per my requirement, I have 300+ Bones. I cannot make these frames manually.
How can I create these frames in a loop and update the value displayed on the GUI and object when a + or - button is clicked?
main.py
from tkinter import *
from tkinter import ttk
from Bone import *
skeleton = {
1: Bone(-0.42, 0.1, 0.02, 0.002, 0.234),
4: Bone(4, 0.042, 0.32, 0.23, -0.32),
11: Bone(11, 1, -0.23, -0.42, 0.42),
95: Bone(95, -0.93, 0.32, 0.346, 0.31),
}
root = Tk()
root.geometry('400x600')
boneID = Label(root, text="ID: 1")
boneID.grid(row=1, column=1, sticky=W, padx=(0, 15))
w = Label(root, text="-0.42")
w.grid(row=1, column=2, sticky=W)
x = Label(root, text="0.02")
x.grid(row=1, column=4, sticky=W)
y = Label(root, text="0.002")
y.grid(row=1, column=6, sticky=W)
z = Label(root, text="0.234")
z.grid(row=1, column=8, sticky=W)
wPlusBtn = Button(root, text="+")
wPlusBtn.grid(row=2, column=2)
wMinusBtn = Button(root, text="-")
wMinusBtn.grid(row=2, column=3, padx=(0, 15))
xPlusBtn = Button(root, text="+")
xPlusBtn.grid(row=2, column=4)
xMinusBtn = Button(root, text="-")
xMinusBtn.grid(row=2, column=5, padx=(0, 15))
yPlusBtn = Button(root, text="+")
yPlusBtn.grid(row=2, column=6)
yMinusBtn = Button(root, text="-")
yMinusBtn.grid(row=2, column=7, padx=(0, 15))
zPlusBtn = Button(root, text="+")
zPlusBtn.grid(row=2, column=8)
zMinusBtn = Button(root, text="-")
zMinusBtn.grid(row=2, column=9, padx=(0, 15))
root.mainloop()
TL;DR - break your one big problem into several smaller problems, and then solve each problem separately.
The main window
Start by looking at the overall design of the UI. You have two sections: a panel holding bones, and a panel holding random text. So the first thing I would do is create these panels as frames:
root = tk.Tk()
bonePanel = tk.Frame(root, background="forestgreen", bd=2, relief="groove")
textPanel = tk.Frame(root, background="forestgreen", bd=2, relief="groove")
Of course, you also need to use pack or grid to lay them out on the window. I recommend pack since there are only two frames and they are side-by-side.
Displaying bones
For the bone panel, you appear to have a single row for each bone. So, I recommend creating a class to represent each row. It can inherit from Frame, and be responsible for everything that goes on inside that row. By inheriting from Frame, you can treat it just like a custom widget with respect to laying it out on the screen.
The goal is for your UI code to look something like this:
bones = (
Bone(boneId=1, w=-0.42, x=0.02, y=0.002, z=0.234),
Bone(boneId=4, w=0.042, x=0.32, y=0.23, z=-0.32),
Bone(boneId=11, w=1, x=-0.23, y=-0.42, z=0.42),
...
)
bonePanel = tk.Frame(root)
for bone in bones:
bf = BoneFrame(bonePanel, bone)
bf.pack(side="top", fill="x", expand=True)
Again, you can use grid if you want, but pack seems like the natural choice since the rows are stacked top-to-bottom.
Displaying a single bone
Now, we need to tackle what each BoneFrame does. It appears to be made up of five sections: a section to display the id, and then four nearly identical sections for the attributes. Since the only difference between these sections is the attribute they represent, it makes sense to represent each section as an instance of a class. Again, if the class inherits from Frame we can treat it like it was a custom widget.
This time, we should pass in the bone, and perhaps a string telling it which id to update.
So, it might start out looking something like this:
class BoneFrame(tk.Frame):
def __init__(self, master, bone):
tk.Frame.__init__(self, master)
self.bone = bone
idlabel = tk.Label(self, text="ID: {}".format(bone.id))
attr_w = BoneAttribute(self, self.bone, "w")
attr_x = BoneAttribute(self, self.bone, "x")
attr_y = BoneAttribute(self, self.bone, "y")
attr_z = BoneAttribute(self, self.bone, "z")
pack is a good choice here since these sections are all lined up left-to-right, but you could use grid if you prefer. The only real difference is that using grid takes a couple more lines of code to configure row and column weights.
Widgets for the attribute buttons and labels
Finally, we have to tackle the BoneAttribute class. This is where we finally add the buttons.
It's pretty straight-forward and follows the same pattern: create the widgets, then lay them out. There's a bit more, though. We need to hook up the buttons to update the bone, and we also need to update the label whenever the bone changes.
I won't go into all of the details. All you need to do is to create a label, a couple of buttons, and functions for the buttons to call. Plus, we want a function to update the label when the value changes.
Let's start with tha function to update the label. Since we know the name of the attribute, we can do a simple lookup to get the current value and change the label:
class BoneAttribute(tk.Frame):
...
def refresh(self):
value = "{0:.4f}".format(getattr(self.bone, self.attr))
self.value.configure(text=value)
With that, we can update the label whenever we want.
Now it's just a matter of defining what the buttons do. There are better ways to do it, but a simple, straight-forward way is to just have some if statements. Here's what the increment function might look like:
...
plus_button = tk.Button(self, text="+", command=self.do_incr)
...
def do_incr(self):
if self.attr == "w":
self.bone.incrW()
elif self.attr == "x":
self.bone.incrX()
elif self.attr == "y":
self.bone.incrY()
elif self.attr == "z":
self.bone.incrZ()
self.refresh()
The do_decr function is identical, except that it calls once of the decrement functions.
And that's about it. The key point here is to break down your larger problem into smaller problems, and then tackle each smaller problem one at a time. Whether you have three bones or 300, the only extra code you have to write is where you initially create the bone objects. The UI code stays exactly the same.
There are two issues here: creating the frames in a loop, and updating the values upon a press on the +/- buttons.
To handle the frame issue, I suggest that you create a BoneFrame class that holds all the widgets (buttons and labels) related to one Bone instance.
There, you can also bind the buttons to the Bone methods so as to act on the values.
Something like that - I'm sure you'll know how to complete this with the other variables and the grid coordinates you want
class BoneFrame(tk.Frame):
def __init__(self, parent, bone):
super().__init__(parent)
# Create your widgets
self.x_label = tk.Label(self, text=bone.x)
self.x_decr_button = tk.Button(self, text="-", action=bone.decr_x)
self.x_incr_button = tk.Button(self, text="+", action=bone.incr_x)
...
# Then grid all the widgets as you want
self.x_label.grid()
...
Then you can easily iterate over your dict of Bones, instantiate BoneFrame every time, and pack or grid that instance to a parent container.
Maybe you'll want to add a bone_id to the parameters of BoneFrame.__init__ and pass it in the loop.
# In your main script
for bone_id, bone in skeleton.items():
frame = BoneFrame(root, bone)
frame.pack()
For now, the values in the label never update.
That's because we just set their text once, and then we never update them.
Rather than binding the buttons directly to methods of Bone, we can define more complex methods in BoneFrame that achieve more logic, including updating the values, and also refreshing the widgets.
Here's one way to do it:
class BoneFrame(tk.Frame):
def __init__(self, parent, bone):
super().__init__(parent)
# Store the bone to update it later on
self.bone = bone
# Instantiate a StringVar in order to be able to update the label's text
self.x_var = tk.StringVar()
self.x_var.set(self.bone.x)
self.x_label = tk.Label(self, textvariable=self.x_var)
self.x_incr_button = tk.Button(self, text="+", action=self.incr_x)
...
def incr_x(self):
self.bone.incr_x()
self.x_var.set(self.bone.x)
So we need a StringVar to update the content of the label.
To sum it up, instead of binding the button to bone.incr_x, we bind it to self.incr_x, which allows us to do whatever we want upon a button press, that is 1. change the value in the Bone instance, and 2. update the value displayed by the label.
A usual way to address this kind of problem is to create functions (or class methods) to perform the repetitious bits of the code (i.e. the DRY principle of software engineering).
Ironically, doing this can itself be a little tedious as I quickly discovered trying to refactor your existing code to be that way — but below is the result which should give you a good idea of how it can be done.
Besides reducing the amount of code you have to write, it also simplifies making changes or adding enhancements because they only have be done in one spot. Often the trickiest thing is determining what arguments to pass the functions so they can do what it needs to be done in a generic way and avoiding hardcoded values.
from tkinter import *
from tkinter import ttk
from Bone import *
skeleton = {
1: Bone(1, -0.42, 0.02, 0.002, 0.234),
4: Bone(4, 0.042, 0.32, 0.23, -0.32),
11: Bone(11, 1, -0.23, -0.42, 0.42),
95: Bone(95, -0.93, 0.32, 0.346, 0.31),
}
def make_widget_group(parent, col, bone, attr_name, variable, incr_cmd, decr_cmd):
label = Label(parent, textvariable=variable)
label.grid(row=1, column=col, sticky=W)
def incr_callback():
incr_cmd()
value = round(getattr(bone, attr_name), 3)
variable.set(value)
plus_btn = Button(parent, text='+', command=incr_callback)
plus_btn.grid(row=2, column=col)
def decr_callback():
decr_cmd()
value = round(getattr(bone, attr_name), 3)
variable.set(value)
minus_btn = Button(parent, text='-', command=decr_callback)
minus_btn.grid(row=2, column=col+1, padx=(0, 15))
def make_frame(parent, bone):
container = Frame(parent)
boneID = Label(container, text='ID: {}'.format(bone.id))
boneID.grid(row=1, column=1, sticky=W, padx=(0, 15))
parent.varW = DoubleVar(value=bone.w)
make_widget_group(container, 2, bone, 'w', parent.varW, bone.incrW, bone.decrW)
parent.varX = DoubleVar(value=bone.x)
make_widget_group(container, 4, bone, 'x', parent.varX, bone.incrX, bone.decrX)
parent.varY = DoubleVar(value=bone.y)
make_widget_group(container, 6, bone, 'y', parent.varY, bone.incrY, bone.decrY)
parent.varZ = DoubleVar(value=bone.z)
make_widget_group(container, 8, bone, 'z', parent.varZ, bone.incrZ, bone.decrZ)
container.pack()
if __name__ == '__main__':
root = Tk()
root.geometry('400x600')
for bone in skeleton.values():
make_frame(root, bone)
root.mainloop()
Screenshot of it running:
BTW, I noticed a lot of repetition in the Bone.py module's code, which could probably be reduced in a similar manner.
I'm making a very simple python programming using tkinter. I want to draw some rectangles on a canvas and then when one clicks on a certain rectangle, show the tags of that rectangle. I can't get it to work. The problem seems to be that wherever I click on the canvas, the function get_closest returns 1. Any help is appreciated. This is my first time working with tkinter (and python for that matter), so any remarks about my code that aren't linked to the problems itself, are welcome as well!
import tkinter as tk
myrecs = [[None for j in range(4)] for i in range(4)]
class application:
def __init__(self, parent):
self.parent = parent
self.frame = tk.Frame(self.parent)
self.frame.grid(row=0)
self.quitbutton = tk.Button(self.frame, text = "Quit", command = lambda:quit())
self.quitbutton.grid(row=0, column = 0, sticky=tk.W + tk.E)
self.canvas = tk.Canvas(self.frame, width=200, height=200, bg = "blue")
self.canvas.bind("<ButtonPress-1>", self.buttonclick)
self.canvas.grid(row=1, columnspan = 2)
self.tag = self.canvas.create_text(10, 150, text="", anchor="nw")
self.makebutton = tk.Button(self.frame, text = "Make nice canvas", command = self.makecanvas)
self.makebutton.grid(row=0, column = 1, sticky = tk.W + tk.E)
def makecanvas(self):
for i in range(4):
for j in range(4):
myrecs[i][j] = self.canvas.create_rectangle(20*i, 20*j, 20*(i+1), 20*(j+1), tags=("rectangle", "i"+str(i), "j"+str(j)))
def buttonclick(self, event):
cnv = self.canvas
item = cnv.find_closest(cnv.canvasx(event.x), cnv.canvasy(event.y))[0]
tags = cnv.gettags(item)
cnv.itemconfigure(self.tag, text=tags[0])
if __name__ == "__main__":
root = tk.Tk()
root.title("Test")
app = application(root)
root.mainloop()
find_closest returns 1 means it's finding the very first element you created within the canvas, which in this case is the create_text.
Oddly enough, when you create_text with text="" it seems to be overtaking all your other elements. With a simple fix of text=" " it will now locate the closet rectangles on clicks.
With the other elements, when you assign option="" it actually disables (as far as I know) the option so instead of using its default values, you are actively telling tcl interpreter to not use it. This can be observed in other elements like create_rectangle(..., outline="") in which the default outline="black" will no longer apply, and you won't even get an outline. I have a feeling text="" yield a similar effect and for some reason basically covers the entire canvas area, so it causes find_closest to always return that element. Perhaps if you're lucky #BryanOakley (a tcl expert) can chime in on the backend reasoning.
In fact, if you tried find_above(item) you will notice that the text is consistently below your other elements drawn afterwards.
In short:
# Change this:
self.tag = self.canvas.create_text(10, 150, text="", anchor="nw")
# To this:
self.tag = self.canvas.create_text(10, 150, text=" ", anchor="nw")
I am wanting to create a grid layout, with a grid that fills the first row until it runs out of space in the window, and will dynamically move items to the row below (like text line-wrapping). As the window width is adjusted, the grid adjusts to fit. The boxes resizing is not desired. I intend to maintain each small box's size, but change where the layout puts each box.
I imagine this functionality is possible by measuring the width of the frame, and if the (number of boxes)*(width of each box) exceeds the width, move to the next row. I was just wondering if there was a better way built in that I'm not understanding.
If the above is the only option, what is the best way to update that? Do I have to set an event on window resize or something? It seems like I shouldn't have to rework a layout manager, which is what that feels like. I just want to check if similar functionality is already built in. Grid seems like a powerful layout manager, but I have not been able to find that option.
The below pics describes the behavior I want using the same set of 6 boxes on a single frame using grid layout.
Window is wide enough to hold all 6 boxes, so they all fit on row 1. They then adjust as window size changes.
If you plan on forcing each box to be a uniform size, the simplest solution is to use the text widget as the container since it has the built-in ability to wrap.
Here is a working example. Click on the "add" button to add additional boxes. Resize the window to see that they automatically wrap as the window grows and shrinks.
import Tkinter as tk
import random
class DynamicGrid(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.text = tk.Text(self, wrap="char", borderwidth=0, highlightthickness=0,
state="disabled")
self.text.pack(fill="both", expand=True)
self.boxes = []
def add_box(self, color=None):
bg = color if color else random.choice(("red", "orange", "green", "blue", "violet"))
box = tk.Frame(self.text, bd=1, relief="sunken", background=bg,
width=100, height=100)
self.boxes.append(box)
self.text.configure(state="normal")
self.text.window_create("end", window=box)
self.text.configure(state="disabled")
class Example(object):
def __init__(self):
self.root = tk.Tk()
self.dg = DynamicGrid(self.root, width=500, height=200)
add_button = tk.Button(self.root, text="Add", command=self.dg.add_box)
add_button.pack()
self.dg.pack(side="top", fill="both", expand=True)
# add a few boxes to start
for i in range(10):
self.dg.add_box()
def start(self):
self.root.mainloop()
Example().start()
Here's a working example:
import Tkinter as tk
class AutoGrid(tk.Frame):
def __init__(self, master=None, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
self.columns = None
self.bind('<Configure>', self.regrid)
def regrid(self, event=None):
width = self.winfo_width()
slaves = self.grid_slaves()
max_width = max(slave.winfo_width() for slave in slaves)
cols = width // max_width
if cols == self.columns: # if the column number has not changed, abort
return
for i, slave in enumerate(slaves):
slave.grid_forget()
slave.grid(row=i//cols, column=i%cols)
self.columns = cols
class TestFrame(tk.Frame):
def __init__(self, master=None, **kwargs):
tk.Frame.__init__(self, master, bd=5, relief=tk.RAISED, **kwargs)
tk.Label(self, text="name").pack(pady=10)
tk.Label(self, text=" info ........ info ").pack(pady=10)
tk.Label(self, text="data\n"*5).pack(pady=10)
def main():
root = tk.Tk()
frame = AutoGrid(root)
frame.pack(fill=tk.BOTH, expand=True)
TestFrame(frame).grid() # use normal grid parameters to set up initial layout
TestFrame(frame).grid(column=1)
TestFrame(frame).grid(column=2)
TestFrame(frame).grid()
TestFrame(frame).grid()
TestFrame(frame).grid()
root.mainloop()
if __name__ == '__main__':
main()
Note this will ruin the rowspan and columnspan features of the grid manager.
Here's a streamlined version of Bryan's answer without classes and a few extra comments for anyone who is confused and is trying to implement this quickly into their own project.
from tkinter import *
import tkinter as tk
#Create main window
root = tk.Tk()
#Create WidgetWrapper
widgetWrapper = tk.Text(root, wrap="char", borderwidth=0,highlightthickness=0,state="disabled", cursor="arrow")
#state = "disabled" is to disable text from being input by user
#cursor = "arrow" is to ensure when user hovers, the "I" beam cursor (text cursor) is not displayed
widgetWrapper.pack(fill="both", expand=True)
def additem():
item = Label(bd = 5, relief="solid", text="O", bg="red") #Create the actual widgets
widgetWrapper.window_create("end", window=item) #Put it inside the widget wrapper (the text)
# add a few boxes to start
for i in range(10):
additem()
#Not needed to implement in other code, just an add button
add_button = tk.Button(root, text="Add", command=additem)
add_button.pack()
This is the Tkinter window when calling addFilterList(list)
I called this function like so:
tkWindow = TkWindow()
tkWindow.addFilterList(['A','B','C','D','E','F','G','H','I','J','K','L'])
tkwindow.runwindow()
I have this TKinker class. I am stuck on ways to make this more dynamic. First the scroll bar, buttons, and listbox are hard coded to be in specific places in the window. Is there a way to get this format no matter where on the Tkinter window it appears. For example, If I have a bunch of buttons on top, I would like the this feature to appear in this format without having to go back to the code and change its row or column location.
Second: The way I set it up, there can only be one addFilterList per TkWindow because of the return value. Can someone point me in the right directions in how to alter the code so that I can return the values of multiple Listbox in one Tkinter window.
class TkWindow(object):
def __init__(self):
self.top = tk.Tk()
def addFilterList(self, list_box):
self.list_box = list_box
self.value = []
self.text_field = tk.StringVar()
self.entry = tk.Entry(self.top, textvariable=self.text_field, width=60)
self.listbox = tk.Listbox(self.top, width=40, selectmode=tk.MULTIPLE)
self.entry.grid()
self.listbox.grid(row=7)
self.text_field.trace("w", lambda name, index, mode: self.update_list())
self.button_show = tk.Button(self.top, text="Select",
command=self.selected_item)
self.button_clear = tk.Button(self.top, text="Clear",
command=self.clear)
self.scrollbar = tk.Scrollbar(self.top)
self.show_list = tk.Listbox(self.top, width=60, height=4)
self.scrollbar.grid(row=7, sticky=tk.N + tk.S + tk.E, padx=(10, 50))
self.button_show.grid(row=8, padx=(10, 300))
self.button_clear.grid(row=8, sticky=tk.E, padx=(10, 100))
self.show_list.grid()
# Add scrollbar
self.listbox.config(yscrollcommand=self.scrollbar.set)
self.scrollbar.config(command=self.listbox.yview)
self.update_list()
def update_list(self):
# Used in addFilterList()
search_term = self.text_field.get()
self.listbox.delete(0, tk.END)
for item in self.list_box:
if search_term.lower() in item.lower():
self.listbox.insert(tk.END, item)
def selected_list(self):
# Used in addFilterList()
self.show_list.delete(0, tk.END)
for item in self.value:
self.show_list.insert(tk.END, item)
self.selected = self.listbox.selection_clear(0, tk.END)
def selected_item(self):
# Used in addFilterList()
self.selected = self.listbox.curselection()
for i in self.selected:
self.value.append(self.listbox.get(i))
self.selected_list()
def clear(self):
# Used in addFilterList()
self.value = []
self.show_list.delete(0, tk.END)
def return_value(self):
return self.value
def runWindow(self):
self.top.mainloop()
I'm not sure I understand your question but I will try to offer some advice. I think you are trying to do too many things in the function addFilterList. Your code is hard to read and modify as a result. You have three distinct things to be done:
Initializing the widgets
Laying out the widgets
Populating the widgets with values
I usually do #1 in the constructor. So your constructor would be, in outline:
def __init__(self):
self.top = tk.Tk()
self.entry = tk.Entry(...)
self.listbox = tk.ListBox(...)
Then I do the layout in a separate function, call it doLayout():
def doLayout(self):
self.entry.grid(...)
self.listbox.grid(...)
Now your function addFilterList can be concerned ONLY with adding a list of items to your listbox. You can change the layout without changing this function. You can add additional widgets to the window without changing this function.
If you want to have more than one FilterList, you might consider making a subclass of tk.Listbox. The functions here would set the list contents, clear the list contents, handle list selection events and so forth. Then if you decide you want two lists instead of just one, you can instantiate another instance of this class and add that to your window.
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.