Tkinter. subframe of root does not show - python

Here is my code:
import tkinter as tk
import sys
class MapCreator:
def __init__(self, root):
#initialize the root window
self.root = root
self.root.geometry('800x600+0+0')
self.root.resizable(width = False, height = False)
self.menubar = None
self.mapFrame = tk.Frame(self.root,bd = 2,bg = 'white')
self.mapFrame.grid(column = 3, row = 3)
#self.mapFrame.pack()
self.__create_menubar()
def __create_menubar(self):
#Create the menubar
self.menubar = tk.Menu()
#config , which used to change the options
self.root.configure(menu = self.menubar)
#create a File menu and add it to the menubar
#add a new menu
file_menu = tk.Menu(self.menubar, tearoff=False)
#File cascade
self.menubar.add_cascade(label="File", menu=file_menu)
#New
file_menu.add_command(label="New", command=self.__create_new_map)
#Open
file_menu.add_command(label="Open", command=self.__open_new_map)
#Svae
file_menu.add_command(label="Save", command=self.__save_cur_map)
#Save as
file_menu.add_command(label="Save as", command=self.__saveas_cur_map)
def __create_new_map(self):
#if not saved , tell the editor
pass
def __open_new_map(self):
#if not saved , tell the editor
pass
def __save_cur_map(self):
#first time save or not
pass
def __saveas_cur_map(self):
pass
pass
if __name__ == "__main__":
root = tk.Tk()
app = MapCreator(root)
root.mainloop()
I want to have a subframe to show other things. But I cannot find my subframe.
By the way, I'm not familiar with the grid() method.
So can someone tell me the reason why I cannot make the subframe show?

In tkinter most of the widgets and frames take the default size when not specified. You can specify the size explicitly (give it a height and width) or have the frame or widgets stretch to dimensions.
Here is a brief introduction to tkinters grid and size. This is what I prefer, rather than specifying the height and weight.
Imagine you are splitting the Parent Frame (or any kind of container) into a grid. The grid attribute of the child allows you to position your child into the specific part of the Parent's grid. Like for example, you want your child to be in (0,0) of the parent. then you say. child.grid(row=0, column = 0).
Note: You are not specifying the width and height of the grid. It re-sizes itself to the default size of the child widget. Button has a specific size. If the child is another frame and that frame is empty. It will not be visible because the size is 1px wide and 1px height.
rowspan, columnspan allows you to adjust how many rows or columns the child widget occupies. This is a similar concept to merging cells in Excel table. So when you say child.grid(row=0, column = 0, rowspan=2, columnspan=2) You are specifying that the widget is placed in (0,0) and occupies 2 rows and 2 columns.
Now that you have that, how do you make it stretch? This is done using stickys and weights. For the child widget, you specify the sticky. With this you are defining which directions you would like to stretch. child.grid(row=0, column = 0, sticky='ew') specifies that the widget can stretch horizontally. child.grid(row=0, column = 0, sticky 'nsew') specifies full stretch. Specifying the sticky does not stretch the widget, it just dictates what direction is can stretch. You specify the weight of the column and row of the parent grid to specify different levels of stretch. parent.grid_columnconfigure(0, weight=1) adds a weight of 1 to column 0. So column 0 is stretched. If you have multiple columns you want to stretch, then weight specifies the ratio of increase between those different columns. parent.grid_rowconfigure(0, weight=1) does the same for rows.
Given all that background. If you add the grid_rowconfigure and grid_columnconfigure to your root and add the sticky to your mapFrame. You get this:
def __init__(self, root):
#initialize the root window
self.root = root
self.root.geometry('800x600+0+0')
self.root.resizable(width = False, height = False)
self.menubar = None
self.mapFrame = tk.Frame(self.root,bd = 2,bg = 'white')
self.mapFrame.grid(row = 0, sticky='nsew')
self.root.grid_columnconfigure(0, weight=1)
self.root.grid_rowconfigure(0, weight=1)
#self.mapFrame.pack()
self.__create_menubar()
And this should work :)
There are lots of other properties you can specify in your child.grid() statement. The other one I usually use are padx pady which is padding in the x,y direction and ipadx ipady which is internal padding. Refer to http://effbot.org/tkinterbook/grid.htm for more details

Your frame is showing up. However, because it doesn't have anything in it, it's natural size is only one pixel wide and one pixel tall, making it virtually invisible.
Put something in the frame, or give it an explicit width and height.
Your other option is to configure grid such that the frame "sticks" to the sides of the cell it is in, and the cell expands to fill any extra space in the window.
For example:
self.mapFrame.grid(column = 3, row = 3, sticky="nsew")
root.grid_rowconfigure(3, weight=1)
root.grid_columnconfigure(3, weight=1)

Related

How can I place a widget at the very bottom of a tkinter window when positioning with .grid()?

I am aware that you cannot use different types of geometry managers within the same Tkinter window, such as .grid() and .pack(). I have a window that has been laid out using .grid() and I am now trying to add a status bar that would be snapped to the bottom of the window. The only method I have found online for this is to use .pack(side = BOTTOM), which will not work since the rest of the window uses .grid().
Is there a way that I can select the bottom of the window to place widgets from when using .grid()?
from tkinter import *
from tkinter.ttk import *
import tkinter as tk
class sample(Frame):
def __init__(self,master=None):
Frame.__init__(self, master)
self.status = StringVar()
self.status.set("Initializing")
statusbar = Label(root,textvariable = self.status,relief = SUNKEN, anchor = W)
statusbar.pack(side = BOTTOM, fill = X)
self.parent1 = Frame()
self.parent1.pack(side = TOP)
self.createwidgets()
def createwidgets(self):
Label(self.parent1,text = "Grid 1,1").grid(row = 1, column = 1)
Label(self.parent1,text = "Grid 1,2").grid(row = 1, column = 2)
Label(self.parent1,text = "Grid 2,1").grid(row = 2, column = 1)
Label(self.parent1,text = "Grid 2,2").grid(row = 2, column = 2)
if __name__ == '__main__':
root = Tk()
app = sample(master=root)
app.mainloop()
So using labels since I was kinda lazy to do other stuff, you can do frames to ensure that each section of your window can be packed/grid as required. Frames will be a useful tool for you to use when trying to arrange your widgets. Note that using a class can make things a little easier when deciding your parents. So imagine each frame is a parent and their children can be packed as required. So I would recommend drawing out your desired GUI and see how you will arrange them. Also if you want to add another frame within a frame simply do:
self.level2 = Frame(self.parent1)
You can check out additional settings in the docs
http://effbot.org/tkinterbook/frame.htm
PS: I am using a class hence the self, if you don't want to use classes then its okay to just change it to be without a class. Classes make it nicer to read though
Just give it a row argument that is larger than any other row. Then, give a weight to at least one of the rows before it.
Even better is to use frames to organize your code. Pack the scrollbar on the bottom and a frame above it. Then, use grid for everything inside the frame.
Example:
# layout of the root window
main = tk.Frame(root)
statusbar = tk.Label(root, text="this is the statusbar", anchor="w")
statusbar.pack(side="bottom", fill="x")
main.pack(side="top", fill="both", expand=True)
# layout of the main window
for row in range(1, 10):
label = tk.Label(main, text=f"R{row}")
label.grid(row=row, sticky="nsew")
main.grid_rowconfigure(row, weight=1)
...

Pack a text widget on the side doesn't work

I'm kind of new to tkinter. I tried to create a Text widget on the left side at 0,0, but it appears in the middle, like a default pack().
Here is my code:
from Tkinter import *
# the ui of the main window
class Ui(object):
# the init of the client object
def __init__(self):
self.root = Tk()
self.mid_height = self.root.winfo_screenheight() / 2
self.mid_width = self.root.winfo_screenwidth() / 2
self.root.title("Journey-opening")
self.root.geometry("600x600+{}+{}".format(self.mid_width - 300, self.mid_height - 300))
self.root.resizable(width=0, height=0)
self.cyan = "#0990CB"
self.root["background"] = self.cyan
self.frame = Frame(self.root)
self.frame.pack()
self.chat_box = Text(self.frame, height=30, width=50)
self.chat_box.pack(side=LEFT)
def open(self):
self.root.mainloop()
wins = Ui()
wins.open()
I also tried with grid method but it did not change anything, and also created another widget because maybe it needs 2 widgets at least.
I guess its something with my frame but I follow a tutorial and everything seems fine.
"Pack a text widget on the side doesn't work"
That is incorrect the line self.chat_box.pack(side=LEFT) does pack the Text widget to side. It's just that it is done inside self.frame which allocates exactly as much space needed for the widgets it encapsulates(in this case that is only the text widget) by default. So in a way, the Text widget is packed, not just to left, but to all sides.
In order to have self.chat_box on the upper left corner, you should let frame to occupy more space than needed, in this case, it can simply occupy all space in the x-axis inside its parent(self.root). In order to do that, replace:
self.frame.pack()
with:
self.frame.pack(fill='x') # which is the same as self.frame.pack(fill=X)

how to add a left or right border to a tkinter Label

The follwing code
import Tkinter as tk
root = tk.Tk()
labelA = tk.Label(root, text="hello").grid(row=0, column=0)
labelB = tk.Label(root, text="world").grid(row=1, column=1)
root.mainloop()
produces
How can I add a partial border to the Label so that I have
I see a that borderwidth= is a possible option of Label but it handles the four borders.
NOTE: the question is not about padding the cell (which is the essence of the answer that was formerly linked in the duplication comment)
There's not an option or a real easy way to add a custom border, but what you can do is create a class that inherits from Tkinter's Frame class, which creates a Frame that holds a Label. You just have to color the Frame with the border color you want and keep it slightly bigger than the Label so it gives the appearance of a border.
Then, instead of calling the Label class when you need it, you call an instance of your custom Frame class and specify parameters that you set up in the class. Here's an example:
from Tkinter import *
class MyLabel(Frame):
'''inherit from Frame to make a label with customized border'''
def __init__(self, parent, myborderwidth=0, mybordercolor=None,
myborderplace='center', *args, **kwargs):
Frame.__init__(self, parent, bg=mybordercolor)
self.propagate(False) # prevent frame from auto-fitting to contents
self.label = Label(self, *args, **kwargs) # make the label
# pack label inside frame according to which side the border
# should be on. If it's not 'left' or 'right', center the label
# and multiply the border width by 2 to compensate
if myborderplace is 'left':
self.label.pack(side=RIGHT)
elif myborderplace is 'right':
self.label.pack(side=LEFT)
else:
self.label.pack()
myborderwidth = myborderwidth * 2
# set width and height of frame according to the req width
# and height of the label
self.config(width=self.label.winfo_reqwidth() + myborderwidth)
self.config(height=self.label.winfo_reqheight())
root=Tk()
MyLabel(root, text='Hello World', myborderwidth=4, mybordercolor='red',
myborderplace='left').pack()
root.mainloop()
You could simplify it some if you just need, for instance, a red border of 4 pixels on the right side, every time. Hope that helps.
I don't believe there's an easy way of just adding a left border. However, you can definitely fool by using a sunken label ;)
So for example:
root=Tk()
Label(root,text="hello").grid(row=1,column=1)
Label(root,text="world").grid(row=2,column=3)
Label(root,relief=SUNKEN,borderwidth=1,bg="red").grid(row=2,column=2)
Label(root).grid(row=2,column=1)
root.mainloop()
This will create a window like the one that you wanted to see.

python/tkinter - image viewer application

I'm currently trying to make simple image viewer, where you can adjust brightness, contrast, color and sharpness with scale. I'm doing it with tkinter and PIL. So far I have two classes, one for brightness and another for sharpness and I just can't find a way(I'm a beginner) to draw two scales in one window and one picture, which can be modified by this scales. All i could do was to draw scales, two pictures, where each scale modified their own picture.
Sending a code:
from tkinter import *
from PIL import Image, ImageTk, ImageEnhance
class Enhance(Frame):
def __init__(self, master, image, name, enhancer, lo, hi):
Frame.__init__(self, master)
self.tkim = ImageTk.PhotoImage(image.mode, image.size)
self.enhancer = enhancer(image)
self.update("1.0")
w = Label(self, image=self.tkim)
w.grid(row = 0, column = 0, padx = 0, pady = 0)
s = Scale(self, label=name, orient=VERTICAL,from_=hi, to=lo, resolution=0.01,command=self.update)
s.set(self.value)
s.grid(row = 1, column = 1, padx = 0, pady = 0)
def update(self, value):
self.value = eval(value)
self.tkim.paste(self.enhancer.enhance(self.value))
class Enhance1(Frame):
def __init__(self, master, image, name, enhancer, lo, hi):
Frame.__init__(self, master)
self.tkim = ImageTk.PhotoImage(image.mode, image.size)
self.enhancer = enhancer(image)
self.update("1.0")
w = Label(self, image=self.tkim).grid(row = 0, column = 0, padx = 0, pady = 0)
s = Scale(self, label=name, orient=VERTICAL,from_=hi, to=lo, resolution=0.01,command=self.update)
s.set(self.value)
s.grid(row = 0, column = 1, padx = 0, pady = 0)
def update(self, value):
self.value = eval(value)
self.tkim.paste(self.enhancer.enhance(self.value))
root = Tk()
im = Image.open("plant.jpg") #choose your image
Enhance(root, im, "Brightness", ImageEnhance.Brightness, 0.0, 3.0).grid(row = 0, column = 0, padx = 0, pady = 0)
Enhance1(root, im, "Sharpness", ImageEnhance.Sharpness, -1.0, 5.0).grid(row = 0, column = 0, padx = 0, pady = 0)
root.mainloop()
It's a design problem, not a Python one. You have to decouple the scales (possibly with a "ScaleDrawer" class) and enhancers from your frame and image, and the Frame class should take a list of enhancers and a list of "ScaleDrawer" objects.
You are putting both of the frames that hold the scales in the same spot -- row 0, column 0. If you want them to appear side-by-side you need to put them in different columns, or use pack.
Second, when using grid you should use the sticky argument, so that widgets expand to fill their containers. Also, you need to use the grid_rowconfigure and grid_columnconfigure methods to give at least one row and one column a weight of 1 (one) or more, so that your GUI has proper resize behavior. For simple layouts where everything is left-to-right or top-to-bottom, pack requires a few less lines of code.
Third, since you want both sliders to affect the same image, you don't want to create a label with the image in each of your classes Enhance and Enhance1. Instead, create a single image and put it in a single label. So, at the highest level you might create three widgets: a Label, an instance of Enhance, and an instance of Enhance1.
Finally, you seem to have an indentation error. The method update isn't indented, so it's not part of the class definition. Also, since this method is part of a class that subclasses Frame, you shouldn't call this method update, because that's a built-in method of all tkinter widgets.
My recommendation is to start over, rather than try to fix what you have. Begin with a blank slate, and first try to get the basic layout working before worrying about the sliders. This lets you focus on one thing instead of trying to get everything working at once.
For example, start with something like this to get the basic structure of your program (notice that I am not doing a global import, and I made the main program a class too, to help cut down on the need for global variables.
import Tkinter as tk
class ImageViewer(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master, background="green")
# for now, don't use images. This, so that we can get
# the basic structure working
self.im = None
self.tkim = None
# these three widgets make up our main layout
label = tk.Label(self, image=self.tkim, text="label")
e = Enhance(self, self.im)
e1 = Enhance1(self, self.im)
# grid works as well as pack in this case, but requires a
# little more code. For that reason I prefer pack for very
# simple layouts such as this.
label.pack(side="bottom", fill="both", expand=True)
e.pack(side="left", fill="both", expand=True)
e1.pack(side="right", fill="both", expand=True)
class Enhance(tk.Frame):
def __init__(self, master, image):
# we will be operating on this image, so save a
# reference to it
self.image = image
# width, height, and color are only temporary, they
# make it easy to see the frames before they have
# any content
self.image = image
tk.Frame.__init__(self, master, background="bisque", width=100, height=100)
class Enhance1(tk.Frame):
def __init__(self, master, image):
# we will be operating on this image, so save a
# reference to it
self.image = image
# width, height, and color are only temporary, they
# make it easy to see the frames before they have
# any content
tk.Frame.__init__(self, master, background="blue", width=100, height=100)
if __name__ == "__main__":
root = tk.Tk()
ImageViewer(root).pack(fill="both", expand=True)
root.mainloop()
Notice how the areas resize properly as the window is resized. Once we get that working, we can add widgets to the inner frames without having to worry about how they affect the main layout.
With this, you now have a good foundation for building up your GUI. Start by fleshing out just one of the Enhance classes, and make sure it's working exactly as you want. Then, do the same for the second one.

How do I create a Tiling layout / Flow layout in TkInter?

I want to to fill my window with, say, labels and I want them to wrap once the column would be bigger than the current window (or rather parent frame) size.
I've tried using the grid layout, but then I have to calculate the size of the content of each row myself, to know when to put the next element in the next row.
The reason I ask, is because I want to create some sort of tiled file icons.
Or asked differently, is there something like Swing's FlowLayout for TkInter?
What I do when I want something like this is use the text widget for a container. The text widget can have embedded widgets, and they wrap just like text. As long as your widgets are all the same height the effect is pretty nice.
For example (cut and pasted from the question at the author's request):
textwidget = tk.Text(master)
textwidget.pack(side=tk.LEFT, fill=tk.BOTH)
for f in os.listdir('/tmp'):
textwidget.window_create(tk.INSERT, window=tk.Label(textwidget, text=f))
Here is a way to make flow behavior inside a frame.
I wrote a function that will do this. Basically you pass a frame to the function (not root or top level) and the function will look at all the children of the frame, go through them measure their sizes and place them in the frame.
Here is the placement procedure
Place the first widget, and move x over an amount equal to its width.
Measure the next widget.
If placing the next widget would cause it to goes past the frame width, bump its x value to 0 and bump it down a y value equal to the largest widget in the current row (start a new row).
Reset the value of the largest widget since you are starting a new row.
Keep repeating until all widgets are placed.
Bind that procedure to the resizing of the frame event.
I used 3 functions to make this work:
The function that runs the procedure.
The function that binds the resizing of the frame to the function.
The function that unbinds the resizing of the frame.
Here are the functions:
from tkinter import *
def _reorganizeWidgetsWithPlace(frame):
widgetsFrame = frame
widgetDictionary = widgetsFrame.children
widgetKeys = [] # keys in key value pairs of the childwidgets
for key in widgetDictionary:
widgetKeys.append(key)
# initialization/priming loop
width = 0
i = 0
x = 0
y = 0
height = 0
maxheight = 0
# loop/algorithm for sorting
while i < len(widgetDictionary):
height = widgetDictionary[widgetKeys[i]].winfo_height()
if height > maxheight:
maxheight = height
width = width + widgetDictionary[widgetKeys[i]].winfo_width()
# always place first widget at 0,0
if i == 0:
x = 0
y = 0
width = widgetDictionary[widgetKeys[i]].winfo_width()
# if after adding width, this exceeds the frame width, bump
# widget down. Use maximimum height so far to bump down
# set x at 0 and start over with new row, reset maxheight
elif width > widgetsFrame.winfo_width():
y = y + maxheight
x = 0
width = widgetDictionary[widgetKeys[i]].winfo_width()
maxheight = height
# if after adding width, the widget row length does not exceed
# frame with, add the widget at the start of last widget's
# x value
else:
x = width-widgetDictionary[widgetKeys[i]].winfo_width()
# place the widget at the determined x value
widgetDictionary[widgetKeys[i]].place(x=x, y=y)
i += 1
widgetsFrame.update()
def organizeWidgetsWithPlace(frame):
_reorganizeWidgetsWithPlace(frame)
frame.bind("<Configure>", lambda event: _reorganizeWidgetsWithPlace(frame))
_reorganizeWidgetsWithPlace(frame)
def stopOrganizingWidgetsWithPlace(frame):
frame.unbind("<Configure>")
And here is an example of them in use:
def main():
root = Tk()
root.geometry("250x250")
myframe = Frame(root)
# make sure frame expands to fill parent window
myframe.pack(fill="both", expand=1)
buttonOrganize = Button(myframe, text='start organizing',
command=lambda: organizeWidgetsWithPlace(myframe))
buttonOrganize.pack()
buttonStopOrganize = Button(myframe, text='stop organizing',
command=lambda: stopOrganizingWidgetsWithPlace(myframe))
buttonStopOrganize.pack()
##### a bunch of widgets #####
button = Button(myframe, text="---a random Button---")
canvas = Canvas(myframe, width=80, height=20, bg="orange")
checkbutton = Checkbutton(myframe, text="---checkbutton----")
entry = Entry(myframe, text="entry")
label = Label(myframe, text="Label", height=4, width=20)
listbox = Listbox(myframe, height=3, width=20)
message = Message(myframe, text="hello from Message")
radioButton = Radiobutton(myframe, text="radio button")
scale_widget = Scale(myframe, from_=0, to=100, orient=HORIZONTAL)
scrollbar = Scrollbar(myframe)
textbox = Text(myframe, width=3, height=2)
textbox.insert(END, "Text Widget")
spinbox = Spinbox(myframe, from_=0, to=10)
root.mainloop()
main()
Notice:
That you do not need to grid, pack or place them. As long as you specify the frame, that will all be done at once when the function is called. So that is very convenient. And it can be annoying if you grid a widget, then try to pack another, then try to place another and you get that error that you can only use one geometry manager. I believe this will simply overwrite the previous choices and place them. I believe you can just drop this function in and it will take over management. So far that has always worked for me, but I think you should really not try to mix and match geometry managers.
Notice that initially the buttons are packed, but after pressing the button, they are placed.
I have added the "WithPlace" naming to the functions because I have a similar set of functions that do something very similar with the grid manager.

Categories

Resources