Related
I am new to python and I have been learning tkinter recently. So I thought with myself that using the grid_forget() function I can remove a widget and redefine it. I thought of this animation that changes the padding of a label so it would create space (kind of like moving the label but not exactly). However, the animation does not work at all. The program freezes until the label reaches the last value of the padding. How can I fix this? Or is there a better way to animate a label moving in the screen?
Here is my code:
from tkinter import *
import time
root = Tk()
lbl = Label(root, text='------')
lbl.grid(row=0, column=0)
def animation():
padding = 0
while padding < 31:
lbl.grid_forget()
padding += 1
lbl.grid(row=0, column=0, padx=padding)
time.sleep(0.2)
# alternative: root.after(200, lambda: lbl.grid(row=0, column=0, padx=padding))
btn = Button(root, text='Animate', command=animation)
btn.grid(row=1, column=1)
root.mainloop()
You need to update the screen for changes to be shown.
Here is a working version using the .update() method:
from tkinter import *
import time
root = Tk()
lbl = Label(root, text='------')
lbl.grid(row=0, column=0)
def animation():
padding = 0
while padding < 31:
lbl.grid_forget()
padding += 1
lbl.grid(row=0, column=0, padx=padding)
root.update()
time.sleep(0.2)
# alternative: root.after(200, lambda: lbl.grid(row=0, column=0, padx=padding))
btn = Button(root, text='Animate', command=animation)
btn.grid(row=1, column=1)
root.mainloop()
Here is a way I also use to animate stuff on the screen, I am not able to understand what you were trying to achieve with your code snippet above, I tried making some changes to it but I feel this way is much better and let's you get more control of your window.
This uses the widely used Canvas widget in the tkinter library.
The Canvas is a general purpose widget, You can use it for a lot of things. Visit the hyper link for more clarity
Here is a short example of how you would create text on the screen.
from tkinter import *
root = Tk()
root.title("My animation")
c = Canvas(root)
x = 20
y = 20 #Instead of using row and column, you simply use x and y co-ordinates
#We will use these co-ordinates to show where the text is in the starting
my_text = c.create_text(x,y,text = '-----')
c.pack()
# This is all you need to create this text on your screen!
root.mainloop()
The idea is that you put your canvas up on your window , and then place whatever you want on it.
There are a lot more attributes that you can add to make your text look even better. Here is an in-depth tutorial on it.
Now that we have made your text widget, It is now time to move it around. Let us move it to 90,20 From our initial position which is 20,20
Here is how we will do it. If we simply move to text object to 90,90, We won't see any animations, it will just directly have it there. So what we will do is first create it at 21,20. Then 22,20. And so on...
We do this really fast till we reach 90,20
This looks like we are moving the text
from tkinter import *
import time
root = Tk()
root.title("My animation")
c = Canvas(root)
x = 20
y = 20 #Instead of using row and column, you simply use x and y co-ordinates
#We will use these co-ordinates to show where the text is in the starting
my_text = c.create_text(x,y,text = 'weee')
c.pack()
def animation():
y = 0.1
x = 0
for _ in range(1000):
c.move(my_text,x,y)
root.update()
anlabel = Button(root,text = 'Animate!',command = animation).pack()
root.mainloop()
This is not only applicable to text, but everything (like other images)that is there on the canvas. The canvas also has Events which will let you use mouse-clicks and other keys on the computer too.
I have made some changes from the previous code, But it is executable and you can try it for yourself to see how it works. increasing the value in time.sleep() makes the animation slower, the lesser the value, the faster.
Are you sure you aren't trying to do something more like the below example? Animating the padding on one of your widgets is going to screw up the rest of your display.
from tkinter import *
import time
root = Tk()
lbl = Label(root, text='')
lbl.grid(row=0, column=0)
def animation(step=12):
step = 12 if step < 0 else step
lbl['text'] = ' ------ '[step:step+6]
root.after(200, lambda: animation(step-1))
Button(root, text='Animate', command=animation).grid(row=1, column=0, sticky='w')
root.mainloop()
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 trying to make a Tkinter widget that contains a number of tables, which are currently frames with entries filled using the .grid method, which can be switched between by pressing buttons. My current attempt at a solution uses the following code:
from tkinter import *
def dot(root, num):
root.subframe.destroy()
root.subframe = TFrame(root, num)
root = Tk()
vscrollbar = Scrollbar(root,orient='vertical')
vscrollbar.grid(row=1,column=2,sticky=N+E+W+S)
root.defaultframe = MainFrame(root)
root.canvas = Canvas(root, yscrollcommand=vscrollbar.set)
root.subframe = Frame(root.canvas)
vscrollbar.config(command=root.canvas.yview)
root.canvas.grid(row=1,column=0)
root.subframe.grid(row=0,column=0)
where MainFrame has the following structure:
class MainFrame(Frame):
def __init__(self, root):
Frame.__init__(self, root)
self.grid(row=0,column=0)
b1 = Button(self, text='table 1', command=lambda: dot(root, 0))
b2 = Button(self, text='table 2', command=lambda: dot(root, 1))
b1.grid(row=0, column=0, sticky=N+E+W+S)
b2.grid(row=0, column=1, sticky=N+E+W+S)
and TFrame:
class TFrame(Frame):
def __init__(self, foor, num):
Frame.__init__(self, root.canvas)
for i in range(12):
self.grid_columnconfigure(i, minsize=50)
for x in range(12):
for y in range(20):
label = Label(self, text=num)
label.grid(row=y,column=x,sticky=N+E+W+S)
root.canvas.create_window((0,0),window=self,anchor='nw')
root.canvas.configure(scrollregion=root.canvas.bbox('all'))
When I run the code, pressing the buttons loads the tables, which scroll in the vertical as expected. But only the first 8 columns or so are visible, no matter how the window is resized. Changing the width of the MainFrame by adding empty labels and the like does not affect the size of the TFrame created, even if it is several times wider than the 8 columns the TFrame ends up being. While I could have a somewhat tolerable solution by adding a horizontal scroll bar as well as the vertical, my experiences so far with scrolling in tkinter in general have been negative enough that I hope to avoid using it by any possible means.
Okay, found a solution. It turns out there weren't columns being cut off, the whole canvas was being cut off, and all my test cases just happened to have exactly the right number of columns vs column width that it looked like the columns after the first 8 were being cut off.
Changing:
root.canvas.grid(row=1,column=0)
to
root.canvas.grid(row=1,column=0,sticky=N+E+W+S)
fixed the problem.
I'm trying to have it so that multiple objects on a canvas in Tkinter can be resized/repositioned using a spinbox, with the value in the spinbox being used as a multiplier to the original coordinates. To make matters slightly more complicated, the spinbox is not visible by default, it's in a Toplevel window that can be opened when a button is pressed.
To summarise:
I need to alter the coordinates of objects on a canvas using a spinbox value as a multiplier (or otherwise) which itself is in a Toplevel window, and have these alterations displayed in 'real time' on the canvas.
For context, I've included the key peripheral code responsible for setting up the objects etc.
Essential Parts of UI module:
import Canvas_1 (module for drawing shapes)
root=Tk()
#root geometry, title set up
#UI then commands set up
canvasBlank=Canvas(root, width... etc) #Blank canvas that is drawn at start
canvasBlank.grid(row... etc)
canvasBlank.bind('Button-3', rightclickcanvas) #Right click function that opens a popup for canvas options
#Other misc commands, I'm using a menubar with drop down options over actual Tk.Buttons
#'New' option in menubar has Command to create objects in UI like:
def createObject():
Objects=MyObjects(root, width... etc)
Objects.grid(row... etc) #Same as layout for canvasBlank
Objects.bind('<Button-3>', rightclickcanvas)
Objectslist.append(Objects) #Stop garbage disposal and makes sure the canvas displays
-The MyObjects Class (in seperate module) has a form similar to:
from Coordinate_Generator import * #imports coordinate arrays
class MyObjects(tk.Canvas)
def __init__(self, master, **kw)
tk.Canvas.__init__(self, master, **kw)
self.create_oval(coordinates[0], dimensions[0], fill... etc)
self.create_oval(coordinates[1], dimensions[1], fill... etc)
#A series of bindings relating to moving objects under mouse clicks
The coordinates are determined using 'a', an arbitrary value. I try to multiply:
scaler=[]
a=70*scaler[-1]
This method doesn't seem to work either, and if it did, it also means potentially drawing a very large number of canvases over one another which I would like to avoid. I'm hoping this demonstrates the method I need to try and use more clearly. I have written a bit of code using the advice given, and while it may be useful for another part of the program I'm planning, it doesn't quite achieve what I am after. So I've cobbled together this 'Demonstration'to maybe illustrate what it is I'm trying to do.
Working Code (SOLUTION)
from Tkinter import *
from numpy import *
import Tkinter as tk
scale=1
class Demonstrator:
def __init__(self, master=None):
global full_coordinates, dimensions, scale
self.master=master
self.master.title( "Demonstrator 2")
self.master.grid()
self.master.rowconfigure(0, weight=1)
self.master.columnconfigure(0, weight=1)
self.canvas = Canvas(self.master, width=300, height=300, bg='grey')
self.canvas.grid(row=0, rowspan=3, column=0)
self.canvas.create_rectangle(full_coordinates[0],dimensions[0], activefill='blue', fill='red')
self.canvas.create_rectangle(full_coordinates[1],dimensions[1], activefill='blue', fill='red')
self.canvas.create_line(full_coordinates[0],full_coordinates[1], fill='red')
a=9*scale
Originx=10
Originy=35
coordinates1=[]
coordinates2=[]
x,y,i=Originx,Originy,1
x1,y1,i=Originx,Originy,1
while len(coordinates1)<=25:
coordinates1.append((x,y))
coordinates2.append((x1,y1))
i+=1
if i % 2 == 0:
x,y=x+a,y
x1,y1=x1,y1+a
else:
x,y=x,y+a
x1,y1=x1+a,y1
full_coordinates=list(set(coordinates1+coordinates2))
b=array(full_coordinates)
k=b+10
dimensions=k.tolist()
class Settings:
def __init__(self, parent):
top = self.top = tk.Toplevel(parent)
self.top.title('Settings')
self.spinbox_Label= tk.Label(top, text='Change Scale Factor?')
self.spinbox_Label.grid(row=0, column=0, columnspan=2)
self.spinbox_Label= tk.Label(top, width=30, text='Scale factor:')
self.spinbox_Label.grid(row=1, column=0)
self.spinbox= tk.Spinbox(top, from_=1, to=10, increment=0.1, command=self.change)
self.spinbox.grid(row=1, column=1)
def change(self):
global scale
scale=float(self.spinbox.get())
MG=Demonstrator(root) #This just generates a new Demonstrator with original coordinates
def onClick():
inputDialog = Settings(root)
root.wait_window(inputDialog.top)
def onClick2():
print scale
class coords:
global full_coordinates, dimensions, scale
print scale
a=9*scale
Originx=10
Originy=35
coordinates1=[]
coordinates2=[]
x,y,i=Originx,Originy,1
x1,y1,i=Originx,Originy,1
while len(coordinates1)<=25:
coordinates1.append((x,y))
coordinates2.append((x1,y1))
i+=1
if i % 2 == 0:
x,y=x+a,y
x1,y1=x1,y1+a
else:
x,y=x,y+a
x1,y1=x1+a,y1
full_coordinates=list(set(coordinates1+coordinates2))
b=array(full_coordinates)
k=b+10
dimensions=k.tolist()
root=Tk()
root.minsize=(700,700)
root.geometry=('600x600')
MG=Demonstrator(root)
mainButton2 = tk.Button(root, width=20, text='Print "scale"', command=onClick2)
mainButton2.grid(row=1, column=1)
mainButton = tk.Button(root, width=20, text='Settings', command=onClick)
mainButton.grid(row=2, column=1)
root.mainloop()
mainButton2.grid(row=1, column=1)
mainButton = tk.Button(root, width=20, text='Settings', command=onClick)
mainButton.grid(row=2, column=1)
root.mainloop()
The Question:
What is the best way to go about changing the size (by altering the coordinates) of the objects on the canvas using a spinbox?
I hope this is enough to info, of course I can supply more if necessary. I also apologise in advance for the formatting of this question, I'm new to this :)
(Solution added)
Any help would be awesome. Cheers.
Mark
There's nothing special about the solution. You simply need to define a callback for the spinbox that adjusts the coordinates of the canvas items (which can be done with the coords method of the canvas).
First, you might want to create a dict to contain the base width and height of each item. The keys to this dictionary could also be tags associated with canvas items. For example:
self.base_dimensions = {
"obj1": (10,10),
"obj2": (20,20),
...
}
Next, create items on a canvas using those keys as tags. For example:
...
self.canvas.create_rectangle(..., tags=("obj1",))
self.canvas.create_rectangle(..., tags=("obj2",))
...
Finally, you can save the spinbox widgets in a dictionary using the same keys (so you can associate a spinbox with a canvas object), and assign the spinbox a callback to do the resizing. For example:
self.spinbox = {
"obj1": tk.Spinbox(..., command=lambda self.do_resize("obj1")),
"obj2": tk.Spinbox(..., command=lambda self.do_resize("obj2")),
...
}
Given a tag, your callback can use that to get the reference to the spinbox widget and get it's value, and then use the tag to tell the canvas object which item(s) to resize. For example:
def do_scale(self, tag):
factor = int(self.spinbox[tag].get())
(width, height) = self.default[tag]
(x0,y0,x1,y1) = self.canvas.coords(tag)
width = factor * width
height = factor * height
x1 = x0 + width
y1 = y0 + height
self.canvas.coords(tag, x0,y0,x1,y1)
Of course, there are endless ways to organize your data; what I've shown isn't the best way nor the only way. It might not even work for how you have your code organized. Whatever you choose, it boils down to being able to get the value out of the spinbox and using it to adjust the coordinates of the canvas items.
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.