I'm new to Tkinter, and finding it a bit rough to get the hang of it. The point of this piece of code is to calculate time using the equation (Time = (Velocity - Initial Velocity) / Acceleration) But I need to take user input for the variables.
Here's what I have so far. It would be great, except for the fact that the labels don't line up with the text widgets. Is there any easy way to do what I need?
def timF():
timPanel = Toplevel()
timPanel.wm_title("Time")
timCont = PanedWindow(timPanel, orient=VERTICAL)
timCont.pack(fill=BOTH, expand=1)
# Top Paned Window and contents #
timTopCont = PanedWindow(timCont, orient=HORIZONTAL)
timCont.add(timTopCont)
# Velocity label
timFVelL = Label(timTopCont, text="Velocity")
timTopCont.add(timFVelL)
# Initial Velocity label
timFiveL = Label(timTopCont, text="Initial Velocity")
timTopCont.add(timFiveL)
# Acceleration label
timFaccL = Label(timTopCont, text="Acceleration")
timTopCont.add(timFaccL)
# Bottom Paned Window and contents #
timBotCont = PanedWindow(timCont, orient=HORIZONTAL)
timCont.add(timBotCont)
# Velocity entry
timFVelE = Entry(timBotCont)
timBotCont.add(timFVelE)
# Initial Velocity entry
timFiveE = Entry(timBotCont)
timBotCont.add(timFiveE)
# Acceleration entry
timFAccE = Entry(timBotCont)
timBotCont.add(timFAccE)
Just use grid() to place the widgets, instead of pack(). It is the easiest way to do so if you know the concrete row and column of the layout you want to place each widget:
timFVelL.grid(row=0, column=0)
timFVelE.grid(row=0, column=1)
timFiveL.grid(row=1, column=0)
timFiveE.grid(row=1, column=1)
# ...
Related
I was working on a trigonometry calculator and I wanted to make this button resize the window based on the resolution of the frame:
res_btn = Button(frame2,
text='⮞',
command= resolution)
res_btn.pack(side= LEFT, padx=2)
This calls the function:
def resolution():
frame.update()
print(f"{frame.winfo_reqwidth()} x {frame.winfo_reqheight()}")
customRes = f"{frame.winfo_reqwidth() + 15}x{frame.winfo_reqheight() + 15}"
However, when I set the original window geometry function to the variable customRes it does not change:
global customRes
customRes = '500x500'
root = Tk()
root.title('Sin Rule Calculator - Manpreet Singh')
root.geometry(customRes)
root.configure(bg='#686e70')
(Let me know if you need the whole source code)
I have a certain number of elements which I have saved as photos. These elements, and therefore the photos, all have different lengths and I want to keep them that way. (See pictures below)
The elements are all lined up in a certain order.
I would now like to use a dropdown menu (or something similar) to select the element and a second dropdown menu to determine the position where the image should be inserted. But the order of the other elements should not be changed by this.
Structure
This is the code, that i have by now:
from tkinter import *
from tkinter import ttk
from tkinter import messagebox
tkFenster = Tk()
tkFenster.title('Test')
tkFenster.geometry('2700x1000')
frameElement = Frame(master=tkFenster, bg='#FBD975')
frameElement.place(x=5, y=340, width=2010, height=70)
imageElement1 = PhotoImage(file='E1.gif')
imageElement2 = PhotoImage(file='E2.gif')
imageElement3 = PhotoImage(file='E3.gif')
imageElement4 = PhotoImage(file='E4.gif')
labelElement = Label(master=frameElement, borderwidth=0, image=imageElement1)
labelElement.pack( side = LEFT)
labelElement2 = Label(master=frameElement, borderwidth=0, image=imageElement2)
labelElement2.pack( side = LEFT)
labelElement3 = Label(master=frameElement, borderwidth=0, image=imageElement4)
labelElement3.pack( side = LEFT)
labelElement4 = Label(master=frameElement, borderwidth=0, image=imageElement3)
labelElement4.pack( side = LEFT)
labelElement5 = Label(master=frameElement, borderwidth=0, image=imageElement4)
labelElement5.pack( side = LEFT)
labelElement6 = Label(master=frameElement, borderwidth=0, image=imageElement2)
labelElement6.pack( side = LEFT)
tkFenster.mainloop()
Repositioning labels in tkinter is relatively easy since most of the work is performed by tkinter.
In order to control label positions it is better to use the grid manager.
This method uses point and click as the easiest control.
Step 1:
Create a tk.Label and put an image into it:
label = tk.Label(labelFrame, image = a, anchor = tk.NW)
label.grid(row = 0, column = c)
Create a binding for each label:
label.bind('<ButtonPress-1>', swapImage)
Do this for all tk.Labels with images.
Step 2:
Images can now be selected via a single mouse click on label.
Since you need two images to swap, 'swapImage' must accumulate both label references before any action can take place.
Function swapImage must:
Find the label reference via label = event.widget
Store label reference in a list
Once this has been done for BOTH labels, extract their grid data with '.grid_info()'.
This will return a dictionary that contains all grid management data.
Now with both label grids available simply swap them.
Here is one example:
def swapImage(event):
labelB = event.widget
if items:
labelA = items[0]
ag = labelA.grid_info()
bg = labelB.grid_info()
labelA.grid(**bg)
labelB.grid(**ag)
items.clear()
else:
items.append(labelB)
NOTE: I've used a list called 'items' to accumulate user label selections.
To add more label images just repeat Step 1.
I make a window with tkinter and I want to check if a Label is at the coordinates relx = 0.3 and rely = 0.63. Is there any function which I could use?
I already tried to use the nametowidget function, but there I have to give every widget I have a name.
So, the widget I try to get is moving in the window and if it reaches the coordinates I want to move it in another way, but I dont know any function which I could use
You can use winfo_x/y to get the label coordinates and winfo_width/height to get the dimension of the window to compute relative coordinates. I made a little example where the goal is to resize the window to get the right relative coordinates:
import tkinter as tk
def check_label_relpos():
relx = l.winfo_x()/parent.winfo_width()
rely = l.winfo_y()/parent.winfo_height()
if (abs(relx - 0.3) < 0.02) and (abs(rely - 0.63) < 0.02):
print("Ok")
else:
print(relx, rely, "Try again")
parent = tk.Tk()
l = tk.Label(parent, text="Label text", bg="red")
l.place(x=50, y=160)
tk.Button(parent, text="Check label relpos", command=check_label_relpos).place(relx=0.5, rely=1, anchor="s")
parent.mainloop()
I am having and issue where I have a frame in a game that displays the current progress of the game (let's call this frame; "results").
If the player chooses to start a new game all the widgets inside results get destroyed and the frame is forgotten to hide it until it is used again.
Now the issue I am having is; When results gets called back it is in-between two other frames. However, it has remained the size it was in the previous game when it has contained all the widgets, before the widgets were destroyed. The widgets are not shown in the frame but it's still the size it was when the widgets were there.
As soon as a new widget is placed in results the size is corrected but I can't figure out how to make the height = 0. I have tried results.config(height=0) but that hasn't worked.
Does anyone know how to "reset" the size of the frame to 0?
Sorry for the proverbial "wall-of-text" but I couldn't find a way to provide the code in a compact way.
Thanks
If I completely understand what you want, then this illustration is correct:
The blue is the results frame
The results removed, everything else resized:
And the corresponding code for this is something like:
import tkinter
RESULTS_WIDTH = 128
root = tkinter.Tk()
left_frame = tkinter.Frame(root, height=64, bg='#cc3399')
right_frame = tkinter.Frame(root, height=64, bg='#99cc33')
def rem_results(event):
# Remove widget
results.destroy()
# Resize other widthets
left_frame.config(width=128 + RESULTS_WIDTH/2)
right_frame.config(width=128 + RESULTS_WIDTH/2)
# Reposition other widgets
left_frame.grid(row=0, column=0)
right_frame.grid(row=0, column=1)
def add_results(event):
# Create results widget
global results
results = tkinter.Frame(root, width=RESULTS_WIDTH, height=64, bg='#3399cc')
results.grid(row=0, column=1)
# Resize other widgets
left_frame.config(width=128)
right_frame.config(width=128)
# Reposition other widgets
left_frame.grid(row=0, column=0)
right_frame.grid(row=0, column=2)
# Initialize results
add_results(None)
# Bind actions to <- and -> buttons
root.bind( '<Left>', rem_results )
root.bind( '<Right>', add_results )
#$ Enter eventloop
root.mainloop()
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.