What is the pythonic way to display a grid? - python

I am learning Python and I want to display buttons in a grid. The below code produces exactly what I want, but the code for displaying the buttons by incrementing x and y does not seem very Pythonic. (I come from a procedural language background) Is there a better way? Thanks for any help.
from tkinter import *
from tkinter import ttk
root = Tk()
numberButtonsFrame = ttk.Frame(root)
numberButtonsFrame.pack()
button=[0]
for i in range(1,10):
button.append (ttk.Button(numberButtonsFrame, text = i))
x=0
y=0
for i in range(1,10):
button[i].grid(row=x,column=y)
y=y+1
if y>2:
x=x+1
y=0
root.mainloop()

When you're dealing with a grid, the solution is very often to use nested loops:
for row in in range(nrows):
for col in range(ncolumns):
buttons[row][col].grid(row=row, column=col) # You could also calculate a linear index if that's what you want
Single loop
As I noted in a comment (along with another poster), there is a way of calculating the row and column based on i (and vice versa).
row, col = divmod(i, ncolumns)
You could do this at the same time as you create each button. You could also simplify the button creation with a list comprehension.
buttons = [ttk.Button(numberButtonsFrame, text = i) for i in range(1,10)]
I assume you added the 0 at the start of your button(s) list to shift the indices: you don't have to do that. Just add 1 to i in your calculations instead.
Lastly, I would recommend using well-named variables rather than literals (eg. ncolumns). And buttonsinstead of button for the list. I'll conclude with an example (// is floor division - the div in divmod):
for i, button in enumerate(buttons):
button.grid(row=i//ncolumns, column=i%ncolumns)

Use the divmod() function to calculate each row and column from the index.
buttons_per_row = 3
for i in range(9):
button = ttk.Button(numberButtonsFrame, text = i+1)
row, col = divmod(i, buttons_per_row)
button.grid(row=row, column=col)

A different method...
Using two nested loops like others have done, you can calculate the text from the row and column simply by:
(r * 3) + c + 1
Obviously, this will return an int so str() will have to be applied -leading to a more succinct solution of:
from tkinter import *
from tkinter import ttk
root = Tk()
numberButtonsFrame = ttk.Frame(root)
numberButtonsFrame.pack()
for r in range(3):
for c in range(3):
ttk.Button(numberButtonsFrame, text=(r * 3) + c + 1).grid(row=r, column=c)
root.mainloop()

Related

Displaying result in tkinter interface

I'm trying to use only tkinter interface to display the result, knowing I use the lists in my program.
Here is the code :
NM =int(input("give the value of NM \n"))
n = int(input("give the value of n \n"))
def function(NM,n):
Z=[]
for x in range(NM):
F=float(input("give the value of F"+str(x+1)+"\n"))
H=float(input("give the value of H"+str(x+1)+"\n"))
v=F/H
Z.append(v)
s=0
for i in range(len(Z)):
s+=float(Z[i])
return (1/n)*s`
If you are trying to show something with Tkiner maybe you can try this:
from tkinter import *
#creates your root widget.
root = Tk()
#creates a label so you can show your result, .path() method makes your label always stay in the center of your widget.
Label(root, text = your_result).pack()
#allows you to see your widget.
root.mainloop()

How to change height of only one row in a Treeview?

I'm creating a Tkinter-based GUI in Python, and I can't find out how to change the height of only one row.
I've tried this code, but it changes height of every row.
from tkinter import *
from tkinter.ttk import *
root = Tk()
Style(root).configure("Treeview", rowheight=40)
tree = Treeview(root, style="Treeview")
tree.pack()
for i in range(100):
tree.insert("", END, i, text=str(i) * i)
root.mainloop()
I've also tried to use the style for certain tags, but it doesn't work.
How can I do what I need?
How to change height of only one row in a Treeview?
I don't think you can change the height of a single row. The treeview was designed to have uniform rows.

Tkinter List of Variable Sizes

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()

Python tkinter create buttons with loop and edit their parameters

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)

How do you use the same function as the command for multiple buttons created in a loop?

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).

Categories

Resources