I have a Frame in the root window (the root window size is set via .geometry()) in which I have two Label which fill the Frame. I would like to know the current size of the Label in order to adapt the font size of its text.
I tried .winfo_reqheight() but I cannot make any sense of the values which are returned. The example below exemplifies the problem I face (the three questions are in bold):
import Tkinter as tk
root = tk.Tk()
root.geometry("200x200")
# top level frame, containing both labels below. Expands to fill root
f = tk.Frame(root)
f.pack(expand=True, fill=tk.BOTH)
# the condition for the two cases mentioned in the text below
if True:
text = ""
else:
text = "hello\nhello\nhello\nhello"
# two labels, the top one's txt will be changed
l1 = tk.Label(f, text=text, font=('Arial', 40), background="green")
l1.pack(expand=True, fill=tk.BOTH)
l2 = tk.Label(f, text="hello", font=('Arial', 15), background="blue")
l2.pack(expand=True, fill=tk.BOTH)
# just in case, thank you https://stackoverflow.com/users/2225682/falsetru
for i in [f, l1, l2]:
i.update_idletasks()
print("top label: reqheight = {0} height = {1}, bottom label: reqheight = {2} height = {3}".format(
l1.winfo_reqheight(), l1.winfo_height(),
l2.winfo_reqheight(), l2.winfo_height()
))
root.mainloop()
Case 1: condition set to True (empty text string)
and the output
top label: reqheight = 66 height = 1, bottom label: reqheight = 29 height = 1
All the widgets are set to expand, so how come their total height is 66+29=95 while the window is 200 px high?
How can I get the height of the i) empty, ii) filled both ways and iii) expanded Label -- which I would keep as the reference (if the Label grows I will know that it must not exceed that reference)?
Case 2: condition is False (multi-line text string)
and
top label: reqheight = 246 height = 1, bottom label: reqheight = 29 height = 1
Why has the top Label crushed the bottom one? Is there a mechanism which says 'expand as much as you can but be vary of the other widgets?'
reqheight is the request height -- the height that the label wants to be regardless of how it is put in the window. It is the size it requests when the geometry manager asks "how much space do you need?". This value won't change based on how you use pack, place or grid, or how big the containing window is.
If you want the actual height, use winfo_height.
Related
Very new to tkinter and came across my first major roadbloack. I have a list of labels that is follows around a parent label. I have a function that displays all of these labels based off the coordinates of this parent. Right now the coordinates for the parent label are set at 0,0 no matter where I place it.
Here is the initialization and call of DisplayUnits:
#operations
OperationsChildren = []
templabel1 = Label(MyCanvas, text = "Operations", font=("Arial Bold", 12))
templabel1.place(x = 30, y = 0,)
Operations = CreateSection("Operations", OperationsChildren, None, OperationsList)
Operations.HeaderLabel = ObjectLabel("Operations", templabel1, Operations, MouseObject)
Operations.ChildLabels = DisplayUnits(Operations, MyCanvas, MouseObject)
#medical
MedicalChildren = []
templabel2 = Label(MyCanvas, text = "Medical", font=("Arial Bold", 12))
templabel2.place(x =200, y = 50,)
Medical = CreateSection("Medical", MedicalChildren, None, MedicalList)
Medical.HeaderLabel = ObjectLabel("Medical", templabel2, Medical, MouseObject)
Medical.ChildLabels = DisplayUnits(Medical, MyCanvas, MouseObject)
And here is my display function
def DisplayUnits(Section, Frame, DebugLabel):
#remove labels inside of sections current label list
for x in Section.ChildLabels:
x.label.destroy()
#initialize label list that will be populated by the new labels
LabelList = []
#get initial x and y coordinates
startX = Section.HeaderLabel.label.winfo_x()
startY = Section.HeaderLabel.label.winfo_y()
print("start values are " + str(startX) + ", " + str(startY))
#set an iterator which will determine how far apart each label is spaced
SpaceIterator = startY + 20
for index, x in enumerate(Section.SecUnitList): #indexing not needed or used yet
tempName = x.name
tempLabel = Label(Frame, text = tempName, font=("Arial Bold", 12))
#set the location equal to the startx and the iterator which is the advancing Y value
tempLabel.place(x=startX,y=SpaceIterator)
#create ObjectLabel class using templabel,
UnitLabel = ObjectLabel(tempName, tempLabel, None, DebugLabel)
LabelList.append(UnitLabel)
print("Display Iterator is at " + str(SpaceIterator) + " for the unit "+ tempName)
SpaceIterator = SpaceIterator+20 #create label one row below last row
return LabelList
this is what I am seeing
this is what I want and am expecting to happen
changing to pack and grid based for the parent label. Adding debugs that show the header label location is considered to be at 0,0 even though it is not. I think it might have to do with local vs global positioning but don't know where to even start on checking that.
While I don't normally recommend using place, you can achieve what you want by place's ability to do relative placement. Since we can't reproduce your problem from the code snippets you've presented, here's a simple example that illustrates the concept.
The key is using the in_ parameter which defines what the relative coordinates are relative to, and the relx (relative x) and rely (relative y) parameters. It also sets bordermode to "outside", which means that the relative coordinates are relative to the widget edges rather than the interior portions of the widget.
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root, background="blue")
canvas.pack(fill="both", expand=True)
medical_label = tk.Label(canvas, text="Medical")
r2_label = tk.Label(canvas, text="R2")
r3_label = tk.Label(canvas, text="R3")
medical_label.place(x=100, y=100)
r2_label.place(in_=medical_label, relx=0.0, rely=1.0, bordermode="outside")
r3_label.place(in_=r2_label, relx=0.0, rely=1.0, bordermode="outside")
root.mainloop()
I was working on a small python script lately when I came across this problem. I tried to create a canvas in a strip shape and write text into it expecting that the text would auto adjust itself to the boundaries of canvas(similar to how a text box works in word processing software). But the text is apparently going out of boundaries.
ScreenShot
Code
from tkinter import *
top = Tk()
top.geometry("130x370")
c = Canvas(top,bg = "pink",height = "370")
c.create_text(30,30,fill="darkblue",font="Times 20 italic bold",text="Hey There!")
c.pack()
top.mainloop()
Firstly, the .create_text() method of the Canvas has a width option which sets the maximum width of the text beyond which it is wrapped. To get a dynamical effect when resizing the window, this width option can be changed in a function bound to the <Configure> event (the resize() function in the example below).
Secondly, to check that the text fits vertically in the canvas, I use the .bbox(item_id) method of the Canvas to get the coordinates of the bounding box of the text. Then, I decrement the fontsize as long as the bottom of the text is lower than the bottom of the canvas.
Here is the example:
import tkinter as tk
top = tk.Tk()
top.geometry("130x370")
def resize(event):
font = "Times %i italic bold"
fontsize = 20
x0 = c.bbox(text_id)[0] # x-coordinate of the left side of the text
c.itemconfigure(text_id, width=c.winfo_width() - x0, font=font % fontsize)
# shrink to fit
height = c.winfo_height() # canvas height
y1 = c.bbox(text_id)[3] # y-coordinate of the bottom of the text
while y1 > height and fontsize > 1:
fontsize -= 1
c.itemconfigure(text_id, font=font % fontsize)
y1 = c.bbox(text_id)[3]
c = tk.Canvas(top, bg="pink", height="370")
text_id = c.create_text(30, 30, anchor="nw", fill="darkblue", font="Times 20 italic bold", text="Hey There!")
c.pack(fill="both", expand=True)
c.bind("<Configure>", resize)
top.mainloop()
Also note that I set the anchor of the text to north west in .create_text() so that (30, 30) are the coordinates of the top-left corner of the text and not of the center to ensure that the start of the text is visible.
I'm making a program to display user data in a GUI from the game called 'osu!' to practice making GUIs and accessing APIs (and handling data between databases and modules but that's not relevant here).
I've got a for loop to place a back button in the top left of all the frames that aren't the menu. These buttons are placed in column 0 and row 0.
Now that I've started to fill in the contents of the frames I've noticed that the columns width is the same as the width of the widest row, the problem now being that anything in column 0 that makes the column wider than the button, it moves toward the left.
Essentially I'm asking how to justify a button in it's column.
Also, the labels below it is also centred in this column. I'm hoping that whatever I do to the button, I can do to the label and fix both.
Here's what I'm looking at:
Here's the code for creating the back buttons:
self.backButtons = {}
for frame in self.frames:
if frame != 'Menu':
self.backButtons[frame] = tk.Button(self.frames[frame], command = lambda: self.raiseFrame('Menu'), text = '< - - -', bg='black', fg = 'white', width=20, height=5)
self.backButtons[frame].grid(row = 0, column = 0)
And here's where the labels are made:
self.tableColumns = {
'Username':self.handle,
'Performace Points':'{0}pp'.format(round(float(self.stats['pp_raw']),2)),
'Global Rank':'#{0}'.format(self.stats['pp_rank']),
'Country Rank':'#{0}'.format(self.stats['pp_country_rank']),
'Accuracy':'{0}%'.format(round(float(self.stats['accuracy']),2),'%'),
'Highest PP Play':'{0}pp'.format(round(float(self.topPlay),2)),
'Playcount':self.stats['playcount'],
'Time Since Joined':secondConversion(self.stats['join_date'])}
index = 0
for i in self.tableColumns:
self.label = tk.Label(self.statFrame, text = ('{0}: {1}'.format(i, self.tableColumns[i])), fg = 'white', bg = 'gray', font=("Verdana", 16))
self.label.grid(row = index+3,column = 0)
index += 1
I've tried adding justify = 'left' and anchor = 'w' to the creation line of the buttons and labels which is what's commonly suggested but it didn't work. Maybe I'm adding it in the wrong place or doing it wrong.
I can't figure out how to control the width of the widget
I can't make it display only 2 columns
from Tkinter import *
from ttk import Treeview
root = Tk()
tree = Treeview(root, height = 10, columns = 2)
tree['columns'] = ('one','two')
tree.column('one', width = 50)
tree.column('two', width = 50)
tree.heading('one', text = 'UserName', anchor = 'center')
tree.heading('two', text = 'ID', anchor = 'centeenter code herer')
tree.grid(row = 3, column = 0)
root.mainloop()
That first column is the tree. You can turn it off by using the show attribute. The value must be a list with zero or more values. The valid values are headings to show the column headings, and tree to show the tree. The default value is ['tree', 'headings'].
Here's how to have the treeview show the column headings but not the tree:
tree = Treeview(root, height=10, columns=2, show=["headings"])
If you want to see the tree, but you want to control its width, you can do that too. The tree column can always be identified with '#0'. You can use the column method to set the width:
tree.column('#0', width=50)
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.