Aligning buttons and labels with tkinter - python

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.

Related

How to use .after() with .grid() in tkinter python?

self.root.after(1000, self.label4.grid(row = 1, column = 0))
self.label3.grid(row = 0, column = 0)
self.label4 = Label(self.frame2, text = "")
self.saveButton = Button(self.frame2, text = "Save")
self.saveButton.grid(row = 20, column = 0)
I am using this line of code to display a Label after 1 second on a frame in tkinter. It has worked with .pack but not with .grid. I want a save button to appear underneath the label but the label to appear above it, hence why I am using the grid system. Here is what I'm getting:
Does anyone know how to fix this?
Thanks.

Insert Image in a Line of other Images, Tkinter

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.

tkinter Python: How to change Button colour after clicking other button?

I want the color of neighbouring buttons to change when I click a button. I am making a GUI in tkinter for a 19x19 boardgame. Imagine something like mindsweeper, so the entire board consists of Buttons. I am using python 3.7
Here is my code for a simple 1x2 playing field.
import tkinter as tk
def do_something(p):
p.configure(bg = 'blue')
def personal_do_something(p):
p.configure(bg = 'blue')
p1.configure(bg = 'blue')
window = tk.Tk()
frame = tk.Frame(master = window)
frame.pack()
p1 = tk.Button(master = frame, width = 10, height = 5, bg = 'orange', command = lambda:do_something(p1))
p2 = tk.Button(master = frame, width = 10, height = 5, bg = 'khaki', command = lambda:personal_do_something(p2))
p1.pack(side=tk.LEFT)
p2.pack(side=tk.LEFT)
window.mainloop()
starting situation:
click p1:
click p2:
When p1 is clicked, only p1 turns blue, but not its neighbour p2. when p2 is clicked both p1 and p2 turn blue. This however only works because p2 has a personal_do_something() function. In this function I explicitly tell p1 to turn blue. This might be a viable method for a small 1x2 board, but I can't write personal functions for every button when there are 19x19 buttons. Does anyone have any clue how I could write 1 general function that always turns the direct neighbours blue? I assume I would have to add some kind of attribute to a button, for example a coordinate, and then search all the buttons that match neighbouring coordinates or something like that. But I have no clue how.
Thanks in advance :)
You can store the references of those buttons in a 2D list and pass the row and col of the clicked button to do_something() function. Then you can update the background color of those buttons based on passed row and col.
Below is an example based on your code:
import tkinter as tk
def do_something(row, col):
buttons[row][col].config(bg="blue")
# change direct connected buttons (left, right, up, down)
for dir in (-1, +1):
if 0 <= row+dir < len(buttons):
buttons[row+dir][col].config(bg="blue")
if 0 <= col+dir < len(buttons[row]):
buttons[row][col+dir].config(bg="blue")
window = tk.Tk()
frame = tk.Frame(window)
frame.pack()
ROWS = 10
COLS = 10
buttons = [[None]*COLS for _ in range(ROWS)] # for storing references of buttons
for row in range(ROWS):
for col in range(COLS):
buttons[row][col] = tk.Button(frame, width=10, height=5, bg="orange", command=lambda r=row,c=col: do_something(r,c))
buttons[row][col].grid(row=row, column=col, sticky="nsew")
window.mainloop()

Need help completing code for password conversion

I'm very new at Python and need some help finishing the code. This is Tkiner related. I have an entry box, a button, and a lower frame for the output.
def loop_over_input(the_str=''):
master_list = []
for char in the_str:
tmp_char = passwordConversion[char]
master_list.append(tmp_char)
print("Master Pass List: ", master_list)
return master_list
This will work in command line with a couple of other lines. I'm not sure how tell it when I put text in the entry field and click the button to return the results in my lower frame. I have moved def loop_over_input to different parts of the code I think I may need to reference the test entry box and the button and the lower box.
I will post the complete code if requested to do so.
Firstly, you need to indent your code correctly. Everything that is in the function loop_over_input needs to be indented once more than the line def loop_over_input(the_str=''):
A few other notes. If you look up the documentation for the tkinter button, it will explain how to link a command to it. The piece of code you have supplied appears to be what you want the button command to be. Printing the list will do so in the shell, not in a frame below your entry field and button.
Here's some example code that should do what you want:
import tkinter as tk
# Creating tk window
window = tk.Tk()
# Master list
master_list = []
master_list_string = tk.StringVar()
# Frames
top_frame = tk.Frame(window)
top_frame.pack(expand = True, fill = 'x', pady = 10, padx = 10)
bottom_frame = tk.Frame(window)
bottom_frame.pack(expand = True, fill = 'both', padx = 10)
# Entry box
myEntry = tk.Entry(top_frame)
myEntry.pack(side = 'left',expand = True, fill = 'x', padx = 10)
# Label to display master list
myLabel = tk.Label(bottom_frame, textvariable = master_list_string)
myLabel.pack(expand = True, fill = 'both')
# Button to submit
def clicked():
master_list.append(myEntry.get())
myEntry.delete(0, 'end')
printed_list = ''
for password in master_list:
printed_list += "\n" + password
master_list_string.set(printed_list)
myButton = tk.Button(top_frame, text = "Submit", command = clicked)
myButton.pack(side = 'left', padx = 10)
# Mainloop
window.mainloop()
The two frames allow you to have the top section with your entry and button, while the bottom frame is for your output. However, you cannot just use a frame as your output, as your frame can't display text. Instead, use a Label widget linked to a StringVar which allows the text in the Label to update when the variable is changed.
The button command then takes the string entered into the entry, saves it to the master list and then sets the StringVar to the updated list, which automatically updates the Label.
I would highly recommend ready the documentation on Effbot, it's quite easy to understand with good examples. Link here

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