I'm trying to make a menu of buttons using Tkinter. I have a randomly generated list of files with their attributes as tuples (Name, Size, Link), and need to arrange them in a grid. I can't set each button to the grid since the number of buttons changes. Here is how I tried to make it:
def selected_button(file):
print(f"File: {file[0]}, {file[1]}, Link: {file[2]}")
for file in files:
fileButton = Button(root, text= file[0],command= lambda: selected_button(file).grid()`
Problems:
The variable "file" that gets passed on at the button click is always the last generated button's.
The buttons arrange themselves as a long list in one column - I need a nice square/rectangle.
If I left out any info, comment and I'll answer promptly.
Problem #1
To get the lambda to store the current value in the iteration it needs to be called out in the lambda function such as command= lambda f = file: selected_button(f).
Problem #2
The method I would usually use for making a grid of buttons is pick a width you want, possibly 3 wide, then increment the column until it reaches that point. Once that width is reached, reset the column and increment the row.
import tkinter as tk
# Testing
files = []
for x in range(12):
files.append((f"Test{x}", f"stuff{x}", f"other{x}"))
# /Testing
def selected_button(file):
print(f"File: {file[0]}, {file[1]}, Link: {file[2]}")
root = tk.Tk()
r, c = (0,0) # Set the row and column to 0.
c_limit = 3 # Set a limit for how wide the buttons should go.
for file in files:
tk.Button(root,
text= file[0],
width = 20,
# This will store what is currently in file to f
# then assign it to the button command.
command= lambda f=file: selected_button(f)
).grid(row = r, column = c)
c += 1 # increment the column by 1
if c == c_limit:
c = 0 # reset the column to 0
r += 1 # increment the row by 1
root.mainloop()
Related
I'm trying to code in Python my lot size based on a positive order signal.
Below is the expected output which was done in excel.
The logic is:
Lot_size = IF (Order_Signal=1, then Prior_Period_Portfolio_Value * Risk_pct / Stop_gap,
elif(Active=0, 0, prior period lot size)) risk_pct=0.02
I'm having a having a hard time to reproduce this in excel, specifically the last component where it refers to the prior period lot size.
It depends on how you store these data in Python. But for simplicity I'll assume each variable is in its own list.
order_signal = [0,0,...-1,0]
stop_gap = [0.44,1.13,...1.94,9.06]
prior_period_portfolio_value = [10000,10000,...9900,9807.5]
active = [0,0,...1,0]
lot_size = [0] * len(order_signal)
risk_pct = 0.02
for i = 1:len(order_signal):
if order_signal[i] == 1:
lot_size[i] = prior_period_portfolio_value[i] * risk_pct / stop_gap[i]
elif active[i] == 0:
lot_size[i] = 0
else:
# just need to be sure this doesn't happen on the first iteration
lost_size[i] = lot_size[i-1]
What I learn it from basic app developing courses (PhP) I was use itand on game dev fro sorting data in tables. So the trick here will be to create first the columns and then to add rows as childrens.
On tkinter I will do it like that:
1 - I will create a frame extended vertical as parent
frame = Frame()
frame.pack(expand=True, fill="y", side="left")
2 - I will add entrys extended horizontal as childrens
entry = Entry(frame)
entry.pack(expand=True, fill="x", side="top")
In this case all childrens will have same width.
If you want to use labels just for showing data for every child I will make a frame (same like entry example) and inside of that I will add label (same like entry but with side="right") and all text will have same width, and will be aligned on right.
Hy All,
I'm creating a layout for a database, and made a big canvas which are the lines, spawning smaller canvas inside them (as cells) to contain labels for the data. It looks nice, but the problem is, that due to this "mass-creation" of canvas and label widgets, none of them stays uniqly addressable, they are all named after the same variable when created in a for loop. Any idea how to tag/address them during the creation so I can edit them later?
for f in range(15)
z = z+1
f = Label(someFrame, width = 45 if z < 4 else 12, text = f, borderwidth=2, relief="groove", bg = "#E5E5E5" if Color == True else "#B2B2B2" )
f.pack(side = LEFT)
It may look a bit messy, but you have a picture at least how the widgets are being created and what is my issue.
You can store your widgets in a dictionary. Something like this:
widget_dict = {}
for idx in range(10):
widget_dict['abc' + str(idx)] = label(root, ...)
Then you can access each widget through its dictionary key:
widget_dict[abc2].config(text='Banana')
Before your for loop create a list. Then inside of the for loop just append every label to the list. Or you can use a dictionary to store them, depending on how you want to deal with that.
I'm making a program with Tkinter where I'd need to use a "for i in range" loop, in order to create 81 Text Widgets named like :
Text1
Text2
Text3
...
and dispose them in a square (actually to make a grill for a sudoku game of a 9*9 size). After I created these 81 Text Widgets I need to place them (using .place() ) and entering their position parameters.
After this, I will need to collect the values that the user entered in these Text Widgets.
I'm a newbie and I don't really know how to code this.
Here is my actual code but the problem is I can't modify the parameters once the dictionnary is created and i don't know how to access to the Text Widgets parameters. Maybe using a dictionnary is not the appropriate solution to do what I want.
d = {}
Ypos = 100
for i in range(9):
Xpos = 100
for j in range(9):
d["Text{0}".format(i)]= Text(window, height=1, width=1,relief = FLAT,font=("Calibri",16))
d["Text{0}".format(i)].place(x = Xpos+,y = Ypos)
Xpos += 35
yPos += 35
Thanks for helping
Don't use a complex key for the dictionary, it makes the code more complex without adding any benefit.
Since you're creating a grid, use row and column rather than i and j. This will make your code easier to understand. Also, don't use place if you're creating a grid. Tkinter has a geometry manager specifically for creating a grid.
Since you're creating a text widget with a height of one line, it makes more sense to use an entry widget.
Here's an example:
import Tkinter as tk
root = tk.Tk()
d = {}
window = tk.Frame(root, borderwidth=2, relief="groove")
window.pack(fill="both", expand=True)
for row in range(9):
for column in range(9):
entry = tk.Entry(window, width=1, relief="flat", font=("Calibri",16))
entry.grid(row=row, column=column, sticky="nsew")
d[(row,column)] = entry
root.mainloop()
Whenever you need to access the data in a cell, you can use the row and column easily:
value = d[(3,4)].get()
print("row 3, column 4 = %s" % value)
I am creating a tic-tac-toe board of buttons. Each button, when clicked, should switch to an X or an O on an alternating basis. I have created the buttons inside for loops, and would, if possible, like to avoid initializing them each separately.
I have tried passing the button to the function by using lambda, and I have tried eliminating the problem of parameters in commands by calling the buttons self.bttn (as this is all taking place within my modified Frame class). However, in both cases, I come across the problem that, regardless of which of the 9 buttons I click on, the final button created by the for loops responds.
def __build_board(self):
""" Creates tic-tac-toe board of 9 blank buttons. """
for row_num in range(3):
for col_num in range(3):
self.bttn = Button(self, text = " ", command = self.__change_button)
self.bttn.grid(row = row_num + 1, column = col_num, sticky = W)
That's the way that I tried it with self.bttn. self.__change_button merely switches the text within self.bttn to an X or O.
And this is the way I tried it with lambda...
def __build_board(self):
""" Creates tic-tac-toe board of 9 blank buttons. """
for row_num in range(3):
for col_num in range(3):
bttn = Button(self, text = " ", command = lambda: self.__change_button(bttn))
bttn.grid(row = row_num + 1, column = col_num, sticky = W)
I think I understand why these ways of going about it do not work. Seemingly, I have commanded all the buttons to change the text of the final button, but I'm at a loss as to how I should get them to each change their own text the way I want. Any help would be greatly appreciated.
This is a tricky problem...
the reason is that the lambda will be tied to the bttn variable, not to its value and therefore all the buttons will act on the last button (because that will be the value of the variable after the loop).
To see this consider:
x = 1
y = lambda : x
print(y()) # will print 1
x = 2
print(y()) # will print 2; lambda captures variables, not values
A possible work-around is:
def __build_board(self):
""" Creates tic-tac-toe board of 9 blank buttons. """
for row_num in range(3):
for col_num in range(3):
def mkbutton():
b = Button(self, text = " ",
command = lambda: self.__change_button(b))
return b
bttn = mkbutton()
bttn.grid(row = row_num + 1, column = col_num, sticky = W)
this way each lambda will be tied to its own separate variable b.
Normally this kind of issue can be solved more easily with the frequent pattern:
x = lambda y=y: ...
where the value y is copied as default for an optional parameter of the lambda.
In your case however this approach is not possible because at the time the lambda is evaluated the Button instance doesn't exist yet (so you cannot store it as a default parameter value in the lambda itself).
My function isn't giving me the right output, and it doesn't want to work. I keep getting this error:
TypeError: list indices must be integers, not str
This is my code:
def showShop(level = level, cash = cash):
top = Tkinter.Tk()
shop = ["$100 & level 2 - Shotgun", "$250 & level 3 - 5 Grenades", "$500 & level 5 - Rocket Launcher"]
buttons = []
for i in shop:
temp = shop[i]
temp = Tkinter.Button(top, height=10, width=100, text = temp, command = shopping(i))
temp.pack()
buttons.append(temp)
top.mainloop()
I want it to display what is in the shop list based on what button it is...
Remove temp = shop[i] from the code
for i in shop:
temp = Tkinter.Button(top, height=10, width=100, text = temp, command = shopping(i))
temp.pack()
buttons.append(temp)
The for loop iterates over the elements in the list and not the indices!. The python docs make it more clear
The for statement in Python differs a bit from what you may be used to in C or Pascal. Rather than always iterating over an arithmetic progression of numbers (like in Pascal), or giving the user the ability to define both the iteration step and halting condition (as C), Python’s for statement iterates over the items of any sequence (a list or a string), in the order that they appear in the sequence.
Also note that the command argument in the Button constructor takes a function as an argument. So you maybe better off by writing command = shopping there instead of the call command = shopping(i).
Change for i in shop to for i in xrange(shop).
You have to use something like partial to pass arguments to the function called by the button press. Note that you have declared the variable "temp" as 2 different things. The only reason it works is because the second declaration is after you use the first. Also note that the "buttons" list can not be used outside of the function showShop() because it is created in/local to that function. The following is working code based on what you posted. Also, please do not use "i", "l" or "O" as single digit variable names as they can look like numbers.
import Tkinter
from functools import partial
def shopping(btn_num):
print "button number %d pressed" % (btn_num)
buttons[btn_num]["bg"]="lightblue"
def showShop(buttons):
top = Tkinter.Tk()
shop = ["$100 & level 2 - Shotgun", "$250 & level 3 - 5 Grenades",
"$500 & level 5 - Rocket Launcher"]
##buttons = []
for ctr in range(len(shop)):
temp = Tkinter.Button(top, height=10, width=100, text = shop[ctr],
command = partial(shopping, ctr))
temp.pack()
buttons.append(temp)
top.mainloop()
## lists are mutable
buttons=[] ## not local to the function
showShop(buttons)