Tkinter Resizable Objects Python Canvas - python

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.

Related

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]))

Scrollbar in Tkinter inside Text widget

I have problem with set in Scrollbar inside Text widget in Tkinter. I know, that it's preferable to use grid to locate widgets but I want to set my widget in absolute location (x,y - red dot on GUI picture) with specified height and width.
My code:
from Tkinter import *
from ttk import *
class NotebookDemo(Frame):
def __init__(self):
Frame.__init__(self)
self.pack(expand=1, fill=BOTH)
self.master.title('Sample')
self.master.geometry("650x550+100+50")
self._initUI()
def _initUI(self):
self._createPanel()
def _createPanel(self):
# create frame inside top level frame
panel = Frame(self)
panel.pack(side=TOP, fill=BOTH, expand=1)
# create the notebook
nb = Notebook(panel)
nb.pack(fill=BOTH, expand=1, padx=2, pady=3)
self._FirstTab(nb)
def _FirstTab(self, nb):
# frame to hold content
frame = Frame(nb)
#textbox
txtOutput = Text(frame, wrap = NONE, height = 17, width = 70)
txtOutput.place(x=10, y=75)
#button
btnStart = Button(frame, text = 'Start', underline=0)
btnStart.place(x=220, y=380)
#scrollbar
#vscroll = Scrollbar(frame, orient=VERTICAL, command=txtOutput.yview)
#txtOutput['yscroll'] = vscroll.set
#vscroll.pack(side=RIGHT, fill=Y)
#txtOutput.pack(fill=BOTH, expand=Y)
#add to notebook (underline = index for short-cut character)
nb.add(frame, text='TAB 1', underline=0, padding=2)
if __name__ == '__main__':
app = NotebookDemo()
app.mainloop()
If I uncomment this part of code (set Scrollbar):
vscroll = Scrollbar(frame, orient=VERTICAL, command=txtOutput.yview)
txtOutput['yscroll'] = vscroll.set
vscroll.pack(side=RIGHT, fill=Y)
My Scrollbar is located inside all window, not inside Text box:
But of course I want to have the Scrollbar inside the Text box widget (black border).
If I use pack function to textbox:
txtOutput.pack(fill=BOTH, expand=Y)
text widget fill in the whole window...:
I really don't know how fix this problem.
Any help will be appreciated.
Thank you!
EDIT:
Of course I can use place method with Scrollbar too, but I can't change length of them, because it hasn't attribute length.
vscroll.place(x=573, y=75)
While I rarely recommend place, it is quite powerful when you take advantage of the configuration options. For example, you can use in_ to specify a widget that this widget is to be placed relative to. You can use relx to specify a relative x coordinate, and you can use relheight to specify a height.
In your case you can try something like this:
vscroll.place(in_=txtOutput, relx=1.0, relheight=1.0, bordermode="outside")
If you want the illusion that the scrollbar is embedded inside the text widget as is (or used to be) common on some platforms, I recommend placing the text widget and scrollbar in a frame.You can use pack to put the widgets in the frame, and continue to use place to place the combination anywhere you want.
For example:
txtFrame = Frame(frame, borderwidth=1, relief="sunken")
txtOutput = Text(txtFrame, wrap = NONE, height = 17, width = 70, borderwidth=0)
vscroll = Scrollbar(txtFrame, orient=VERTICAL, command=txtOutput.yview)
txtOutput['yscroll'] = vscroll.set
vscroll.pack(side="right", fill="y")
txtOutput.pack(side="left", fill="both", expand=True)
txtFrame.place(x=10, y=75)
Different geometry managers like place and pack don't mix so well. I see four options for you:
Use a parent frame
Create a new Frame that you place at the exact same position as you did with the text box. In this frame, you can use another geometry manager (I'd prefer pack) to make the layout appear as you want.
Use ScrolledText
Use the ScrolledText Tkinter module to have the solution above in a premade form. Note that this widget doesn't use ttk so the scrollbar style does not really adapt to the OS' look. Just use import ScrolledText and replace the Text creation in your code with ScrolledText.ScrolledText(...).
Use place for the scrollbar
If you are using place for the text widget, use place for the scrollbar too. place has options that allow you to place a widget relative to another widget both in location and size (ie: you can place the scrollbar along the right edge of the text widget, and cause it to be exactly as tall as the text widget). See Bryan's answer.
Don't use place
Simple as that. Use grid or pack instead, unless you really need to use place.

Python+Tkinter: Redrawing a rectangle in a canvas based on some % value

I have spent an embarassing amount of hours looking for a way to do this... Its for a project I'm working on that has over one hundred canvas items that need to update from a text file. Here is a simple version of that:
I would like to update a rectangle drawn in a canvas item when I push a button. I found a real bad hack way to do it that involves a crazy amount of code but I know there has to be some better way.
from tkinter import *
class MyGUI:
def __init__(self, root):
frame = Frame(root)
frame.pack()
self.BoxFillPercent = 0 # the canvas items get their % fill from this value
self.changeButton = Button(frame, text='SB', command=self.changeRange)
self.changeButton.grid(row=1, column=1)
self.hAA = Canvas(frame, width=35, height=35, bg='light blue')
self.hAA.grid(row=2, column=2)
self.hAA.create_rectangle(0,0,self.BoxFillPercent*35,35, fill="pink")
self.hAA.create_text(15, 15, anchor='center', text='AA')
def changeRange(self):
self.BoxFillPercent = 0.5
# When I push the button change the fill amount to 0.5
? What do I need to add here to make this work ?
root = Tk()
b = MyGUI(root)
root.mainloop()
I have tried to use update and update_idletasks among a bunch of other things but I must be missing something.
Every item on a canvas has an id. You can use the itemconfig method of the canvas to change the item.
rect = self.hAA.create_rectangle(...)
...
self.hAA.itemconfig(rect, ...)
If you need to apply the same change to multiple objects, you can give those objects a common tag and then use the tag in place of the id:
rect1 = self.hAA.create_rectangle(..., tags=("special",))
rect2 = self.hAA.create_rectangle(..., tags=("special",))
...
self.hAA.itemconfigure("special", ...)

How to stop Tkinter Text widget resize on font change?

I'm trying to create a simple word processor for starters to learn Python a bit better.
I'm using the Tkinter Text widget for the main editing program, the only problem is the height and width are defined by characters.
This creates a problem when I change fonts, for not all fonts are the same width.
Every time the font is changed, the Text widget re-sizes, although technically it is the same width and height. This looks ridiculous when trying to type up something, I'm trying to make the word processor as nice as possible.
Is there a way to define the width and height in pixels?
the .grid_propagate(False) is not useful for the size is technically not changing, only the character width.
I'm trying to stay away from wxPython for now, since everything I've done up to this point has been in Tkinter.
I have done endless hours of extensive googling but have found no solutions.
You are wrong when you say you can't use grid_propagate(False), because you can. grid_propagate is related to the actual size, not the size attribute. Also, if you simply give your application a fixed size using wm_geometry, font changes won't affect the size of the window.
Here's an example using grid_propagate, which sets the container to a fixed size in pixels:
import Tkinter as tk
import tkFont
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self._textFont = tkFont.Font(name="TextFont")
self._textFont.configure(**tkFont.nametofont("TkDefaultFont").configure())
toolbar = tk.Frame(self, borderwidth=0)
container = tk.Frame(self, borderwidth=1, relief="sunken",
width=600, height=600)
container.grid_propagate(False)
toolbar.pack(side="top", fill="x")
container.pack(side="bottom", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
text = tk.Text(container, font="TextFont")
text.grid(row=0, column=0, sticky="nsew")
zoomin = tk.Button(toolbar, text="+", command=self.zoom_in)
zoomout = tk.Button(toolbar, text="-", command=self.zoom_out)
zoomin.pack(side="left")
zoomout.pack(side="left")
text.insert("end", '''Press te + and - buttons to increase or decrease the font size''')
def zoom_in(self):
font = tkFont.nametofont("TextFont")
size = font.actual()["size"]+2
font.configure(size=size)
def zoom_out(self):
font = tkFont.nametofont("TextFont")
size = font.actual()["size"]-2
font.configure(size=max(size, 8))
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
..tested this: this works on the frame size, but not on the (scrolled)text-widgets size.
my setup is a bit more complicated, with a masterframe for 2 sites, mainframes for each site and frames with various content, i.a. a scrolledtext widget.
adding grid_propagate, *_row- and *_columnconfigure warded the masterframe from resizing, even with adding grid options to main- and contentframes, what results in warding the mainframe to resize.. when changing the font, the widgetsize also changes - and, in the last example, the scrolledtext-widget disappears behind the frame on its right side (containing other widgets)..
Use the pack geometry manager to pack the widgets.
I was creating a notepad and wanted the font sample area to display the font size.
So I created a label frame and added a label in it - i.e. AabYyZz - to display the font sample. 
When I increased the font size the size of the label frame also increased, so I've tried the .pack_propagate method and it worked.

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