I'm trying to write a python class to display data in a tabular format. I'm sure there are classes out there already to do the same thing, however, I'm using this exercise as a way to teach myself Python and tkinter. For the most part, I have the class working the way I want it to, however I cannot get the header and data cells to fill their entire cell, while being aligned left. Here is what my class currently generates for a table:
I went ahead and changed the sticky on the cells to be (W,E) rather than just W, in order to show how I want the table to look, except each cell left justified. Below is what I'm shooting for:
Based on the research I've done, it would seem I need to be using the weight attribute of grid_columnconfigure and grid_rowconfigure, however every way I have tried using them I cannot, get it to work.
Here is the code for my class (I am using Python 3.4):
from tkinter import *
from tkinter import ttk
from tkinter import font
class TableData:
def __init__(self,parent,attributes,columns,data):
self.parent = parent
self.tableName = StringVar()
self.tableName.set(attributes['tableName'])
self.columns = columns
self.columnCount = 0
self.borderColor = attributes['borderColor']
self.titleBG = attributes['titleBG']
self.titleFG = attributes['titleFG']
self.titleFontSize = attributes['titleFontSize']
self.headerBG = attributes['headerBG']
self.headerFG = attributes['headerFG']
self.headerFontSize = attributes['headerFontSize']
self.dataRowColor1 = attributes['dataRowColor1']
self.dataRowColor2 = attributes['dataRowColor2']
self.dataRowFontSize = attributes['dataRowFontSize']
self.dataRowFG = attributes['dataRowFG']
self.data = data
self.tableDataFrame = ttk.Frame(self.parent)
self.tableDataFrame.grid(row=0,column=0)
self.initUI()
def countColumns(self):
cnt = 0
for i in self.columns:
cnt += 1
self.columnCount = cnt
def buildTableTitle(self):
tableTitleFont = font.Font(size=self.titleFontSize)
Label(self.tableDataFrame,textvariable=self.tableName,bg=self.titleBG,fg=self.titleFG,font=tableTitleFont, highlightbackground=self.borderColor,highlightthickness=2).grid(row=0,columnspan=self.columnCount,sticky=(W,E), ipady=3)
def buildHeaderRow(self):
colCount = 0
tableHeaderFont = font.Font(size=self.headerFontSize)
for col in self.columns:
Label(self.tableDataFrame,text=col,font=tableHeaderFont,bg=self.headerBG,fg=self.headerFG,highlightbackground=self.borderColor,highlightthickness=1).grid(row=1,column=colCount,sticky=W, ipady=2, ipadx=5)
colCount += 1
def buildDataRow(self):
tableDataFont = font.Font(size=self.dataRowFontSize)
rowCount = 2
for row in self.data:
if rowCount % 2 == 0:
rowColor = self.dataRowColor2
else:
rowColor = self.dataRowColor1
colCount = 0
for col in row:
Label(self.tableDataFrame,text=col,bg=rowColor,fg=self.dataRowFG,font=tableDataFont,highlightbackground=self.borderColor,highlightthickness=1).grid(row=rowCount,column=colCount,sticky=W,ipady=1, ipadx=5)
colCount += 1
rowCount += 1
def initUI(self):
self.countColumns()
self.buildTableTitle()
self.buildHeaderRow()
self.buildDataRow()
Here is a test file referencing the TableData class:
from tkinter import *
from tkinter import ttk
from tableData import TableData
import sqlite3
root = Tk()
root.geometry('1000x400')
mainframe = ttk.Frame(root).grid(row=0,column=0)
attributes = {}
attributes['tableName'] = 'Title'
attributes['borderColor'] = 'black'
attributes['titleBG'] = '#1975D1'
attributes['titleFG'] = 'white'
attributes['titleFontSize'] = 16
attributes['headerBG'] = 'white'
attributes['headerFG'] = 'black'
attributes['headerFontSize'] = 12
attributes['dataRowColor1'] = 'white'
attributes['dataRowColor2'] = 'grey'
attributes['dataRowFontSize'] = 10
attributes['dataRowFG'] = 'black'
columns = ['Col 1', 'Column 2', 'Column 3','Column 4']
results = [('1','Key','Desc','Attribute'),('2','Key Column','Description Column','AttributeColumn')]
table = TableData(mainframe,attributes,columns,results)
root.mainloop()
Thanks in advance for any insight. Please, let me know if there is any other info that would be helpful.
For any grid of geometry, add option sticky="W", for example,
self.tableDataFrame.grid(sticky="W", row=0, column=0)
If you want the text in a label to be left-aligned, use the anchor option. It takes a string representing a point on a compass (eg: "w" = "west", meaning the text is anchored to the left):
for col in row:
Label(..., anchor="w").grid(...)
You are not defining the width of your label so tkinter sets the width of Label equal to the width of the text. Thats why your labels do not occupy entire width of the cell. To make them occupy all the available width use stickey = E+W
Label(self.tableDataFrame,text=col,font=tableHeaderFont,bg=self.headerBG,fg=self.headerFG,highlightbackground=self.borderColor,highlightthickness=1).grid(row=1,column=colCount,sticky=W+E, ipady=2, ipadx=5)
Try to set the columnspan attribute when griding:
self.tableDataFrame.grid(row=0, column=0, columnspan=2)
2 may not be the correct size, try 'til you get it like you want it.
Related
I want to create buttons for every item in a list, but need to find out how to fit them all in the same frame. Ideally I would like to put 4 or 5 buttons in a row, then the next 4-5 buttons are displayed directly under. Here is what I have now:
from tkinter import ttk
from restaurantSoftware import restaurantInfo
class menuFrame:
def __init__(self, frame):
self.frame = frame
availableItems = restaurantInfo.readRestaurantInfo('availableItems.csv')
itemList = availableItems[0]
priceList = availableItems[1]
categoryList = availableItems[2]
for item in itemList:
button = ttk.Button(text=item)
button.pack(side='left', ipadx=25, ipady=25)
Here is a screenshot of what my tkinter layout looks like when I run the program.
You can use the built-in divmod function which returns the multiplier and remainder such that a, b = divmod(c, d) => a * d + b = c which allows to calculate the row and column where the widget should be placed:
import tkinter as tk
COLUMNS = 3
item_list = ['Cheese burger', 'BLT', 'Cheese Pizza', 'Chicken Fillet',
'Hot dog', 'Caesar Salad', 'Chicken Salad']
root = tk.Tk()
for i, item in enumerate(item_list):
lbl = tk.Button(root, text=item)
row, column = divmod(i, COLUMNS)
lbl.grid(row=row, column=column, sticky='news')
root.mainloop()
The easiest way would be to use grid instead of pack and a for-loop to place the buttons on the grid. Here's an example of how that may be done:
import tkinter as tk
root = tk.Tk()
item_list = ['Cheesburger','BLT','Cheese Pizza','Chicken Filet',
'Hotdog','Caesar Salad','Chicken Salad']
row_length = 4
for row in range(1000): # Any number bigger than anticipated number of rows
if row_length*row >= len(item_list):
break
for col in range(row_length):
if row_length*row + col >= len(item_list):
break
b = tk.Button(root, width=15, text=item_list[row_length*row + col])
b.grid(row=row, column=col)
root.mainloop()
Since I will be breaking out of the loops it doesn't really matter how big the range in the first loop is. Now this solution may definitively be improved upon, but it's a start.
im using tkinter with class abd im having trouble with adding a product
class Add_Page():
def __init__(self, child):
self.child = child
child.title = "Ajouter"
self.l1=Label(child,text="Ajouter produit :",bg="blue").grid(row=0,columnspan=2)
self.l2=Label(child,text="Matricule").grid(row=1,column=0)
self.vlrm = StringVar()
self.en2 = Entry(child, textvariable=self.vlrm, width=30).grid(row=1,column=1)
self.l3=Label(child,text="Nom").grid(row=2,column=0)
self.vlrn = StringVar()
self.en3 = Entry(child, textvariable=self.vlrn, width=30).grid(row=2,column=1)
self.l4=Label(child,text="Prix").grid(row=3,column=0)
self.vlrp = IntVar()
self.en4 = Entry(child, textvariable=self.vlrp, width=30).grid(row=3,column=1)
self.b2=Button(child,text="Valider",command=self.add_p).grid(row=4,columnspan=2)
#Add product function
def add_p(self):
print(self.vlrm.get())
print(self.vlrp.get())
the results are anempty chaine and 0
i dont seem to find the problem especially that i used the get method in users class and its working just fine
Heelp
You don't need create a variable to entry, only make this for radiobutton or checkbutton. And you can change your create of tkinter object, like that
change this
self.l1=Label(child,text="Ajouter produit :",bg="blue").grid(row=0,columnspan=2)
for this
self.l1 = Label(child, text = "Ajouter produit :", bg = "blue")
self.l1.grid(row = 0, columnspan = 2) # remove all variables StringVar() and IntVar()
if you need make some future change using .config or .get() you don't can make that in the first example. You can continue using variable, but i don't recommend that, if you make this change .get() will work now.
I maked a easy way to draw in tkinter, you can use or make change, is for python 2
from Tkinter import *
class Draw_tk():
Row, Column, List = 0, 0, []
def __init__(self, child):
self.child = child
child.title = "Ajouter"
def labelAndEntry(self, text): # def to create a entry and a label
self.l = Label(self.child, text = text) # create label
self.l.grid(row = Draw_tk.Row, column = Draw_tk.Column) # place label
Draw_tk.Column += 1 # add 1 in Column to place the entry
self.e = Entry(self.child, width = 30) # create entry
self.e.grid(row = Draw_tk.Row, column = Draw_tk.Column) # place entry
Draw_tk.List.append(self.e) # add the entry in a list
Draw_tk.Row, Draw_tk.Column = Draw_tk.Row + 1, 0
def label(self, text):
self.l = Label(self.child, text = text, bg = "blue") # def to create a simple label
self.l.grid(row = Draw_tk.Row, columnspan=2) # place the label
Draw_tk.Row += 1
def button(self, text, var): # create a simple button
self.b = Button(self.child, text = text, command = var) # create button
self.b.grid(row = Draw_tk.Row, column = Draw_tk.Column) # place the button
def valid():
for item in Draw_tk.List: # run a variable in your values list
print item.get() # get the value and print
root = Tk()
controller = Draw_tk(root) # create object Draw_tk
controller.label('Ajouter produit')
controller.labelAndEntry('Matricule')
controller.labelAndEntry('Nom')
controller.labelAndEntry('Prix')
controller.button('Valider', valid)
root.mainloop()
I am working on a Human machine interface design...
I have created an array of labels...I have used same label and properties for all the labels in the array, but when i executive my code, size of the labels do not match with each other....
This is my code....
from tkinter import *
import time
root = Tk()
class Clock:
def __init__(self):
self.time1 = ''
self.time2 = time.strftime('%H:%M:%S')
self.mFrame = Frame()
self.mFrame.pack(side=TOP,fill=X)
self.bottomFrame = Frame()
self.bottomFrame.pack(side=BOTTOM,fill=BOTH)
root.title('HUMAN MACHINE INTERFACE')
self.company_name=Label(self.mFrame, text='Host Company Name', font=('Arial',24,'bold','underline'),bg='white',relief=RAISED).pack(fill=X)
self.machine_name=Label(self.mFrame, text='Machine name', font=('Arial',24,'bold','underline'),bg='yellow',relief=RAISED).pack(fill=X)
self.my_company_name=Label(self.mFrame, text='SYNERGY AUTOMATION', font=('Arial',24,'bold','underline'),relief=SUNKEN).pack(side=LEFT)
self.watch = Label(self.mFrame, text=self.time2, font=('times',24,'bold'),relief=SUNKEN)
self.watch.pack(side=RIGHT)
self.clock() #first call it manually
num=['1','2','3','4','5','6','7','8','9','10','11','12','13','14','15','16',]
r=0
c=0
for i in num:
Label(self.bottomFrame, text=i,bg='red',fg='white',font=(34),padx=50,pady=20,relief=SUNKEN).grid(row=r,column=c)
c = c + 1
if c == 4 or c == 8:
c=0
r=r+1
def clock(self):
self.time2 = time.strftime('%H:%M:%S')
self.watch.configure(text=self.time2)
self.mFrame.after(200, self.clock) #it'll call itself continuously
obj1 = Clock()
root.mainloop()
You aren't using the sticky option when you call grid. Without it, the labels will only be big enough to contain the text. If you want them to fill the table cells, use something like sticky=N+S+E+W
Label(...).grid(... sticky=N+S+E+W)
By the way,
self.company_name=Label(...).pack(fill=X)
You are setting self.company_name to None because that's what pack returns. A tkinter best practice is to separate the creation of the widget from laying it out on the screen:
self.company_name=Label(...)
...
self.company_name.pack(fill=X)
I am writing a program which analyses a CSV file containing my credit card monthly statement. I am trying to create a gui with tkinter in which I have a row of label headings followed by rows which consist of 3 more labels and then a combobox in the 4th column which has a number of categories. I have written a class which defines each row of the table. I want to pass a value to the instance of the class which specifies the initial value for the combobox. This is different for each instance or row. Or at least should be....
My problem is that in passing in different numbers for each row/instance I always get all rows having the value specified for the first created instance.
My classes are as follows:
class CatFormApp:
def __init__(self,parent):
self.parent = parent
self.datelabel = Tkinter.Label(root, text='Date',font ='Verdana 12 bold',borderwidth=5).grid(row=0,column=0)
self.transactionlabel = Tkinter.Label(root, text='Transactions',font ='Verdana 12 bold',borderwidth=5).grid(row=0,column=1)
self.paidoutlabel = Tkinter.Label(root, text='Paid Out',font ='Verdana 12 bold',borderwidth=5).grid(row=0,column=2)
self.catlabel = Tkinter.Label(root, text='Category',font ='Verdana 12 bold',borderwidth=5).grid(row=0,column=3)
class RowFormat:
def __init__(self,parent,rownumbval,defaultval,dateval,transactionval,paidoutval,CatTupleVals,bkg):
self.parent = parent
self.datevallabel = Tkinter.Label(root, text=dateval, borderwidth=5).grid(row=rownumbval,column=0)
self.transactionvallabel = Tkinter.Label(root, text=transactionval, borderwidth=5, background = bkg).grid(row=rownumbval,column=1)
self.paidoutvallabel = Tkinter.Label(root, text=paidoutval, borderwidth=5).grid(row=rownumbval,column=2)
self.combo(rownumbval,defaultval,CatTupleVals)
def combo(self,rownumb,default,CatTuple):
self.box_value = '1'
self.box = ttk.Combobox(self.parent,textvariable = self.box_value)
self.box['values']=CatTuple
self.box.current(default)
self.box.grid(row=rownumb,column=3)
The relevant bit of my main code is:
root = Tkinter.Tk()
app = CatFormApp(root)
row = ['']*(len(CreditData['Transactions'])+1)
for r in range(1,len(CreditData['Transactions'])):
if CreditData['Paid out'][r-1] != '':
if noMatch[r-1] == True:
print 1
row[r] = RowFormat(root,r,1,CreditData['Date'][r-1],CreditData['Transactions'][r-1],CreditData['Paid out'][r-1][1:],tuple(CatHeadings[:-1]),bkg = 'red')
else:
print 2
row[r] = RowFormat(root,r,2,CreditData['Date'][r-1],CreditData['Transactions'][r-1],CreditData['Paid out'][r-1][1:],tuple(CatHeadings[:-1]),bkg = 'white smoke')
root.mainloop()
In the above example all comboboxes have the value associated with 1. Whereas I should get some with a 1 and some with a 2 depending on the if statement. I'm puzzled because I do something very similar for the other label values passed and these all come out differently as expected.
If someone could explain where I'm going wrong I'd be really grateful
This code is wrong:
self.box_value = '1'
self.box = ttk.Combobox(...,textvariable = self.box_value)
The value for the textvariable attribute must be an instance of the tkinter class StringVar (or one of the other special tkinter variables).
Use it like this:
self.box_value = Tkinter.StringVar()
self.box = ttk.Combobox(self.parent,textvariable = self.box_value)
To set values, use:
self.box['values'] = ('A', 'B', 'C')
To select the first item, use:
self.box.current(0)
I was wondering if there is an easy way to create a grid of checkboxes using Tkinter. I am trying to make a grid of 10 rows and columns (so 100 checkboxes) so that only two checkboxes can be selected per row.
Edit: I'm using python 2.7 with spyder
What I have so far:
from Tkinter import*
master = Tk()
master.title("Select Groups")
rows=10
columns=10
for x in range(rows):
for y in range(columns):
Label(master, text= "Group %s"%(y+1)).grid(row=0,column=y+1)
Label(master, text= "Test %s"%(x+1)).grid(row=x+1,column=0)
Checkbutton(master).grid(row=x+1, column=y+1)
mainloop()
I'm trying to use state='Disabled' to grey out a row once two checkboxes have been selected.
Here's a version that puts everything into a class so we don't need to use global variables. It also avoids the import * construction which is generally considered bad style in Python. True, lots of example code uses import * but it's not a good practice because it clutters up the global namespace with all the names from the imported module. So those names can clash with the names of your own variables, and they can also clash with the names of other modules you import using import *.
The program prints lists of the selected Groups for each Test row when the window closes.
#!/usr/bin/env python
''' Create a grid of Tkinter Checkbuttons
Each row permits a maximum of two selected buttons
From http://stackoverflow.com/q/31410640/4014959
Written by PM 2Ring 2015.07.15
'''
import Tkinter as tk
class CheckGrid(object):
''' A grid of Checkbuttons '''
def __init__(self, rows=10, columns=10):
master = tk.Tk()
master.title("Select Groups")
rowrange = range(rows)
colrange = range(columns)
#Create the grid labels
for x in colrange:
w = tk.Label(master, text="Group %s" % (x + 1))
w.grid(row=0, column=x+1)
for y in rowrange:
w = tk.Label(master, text="Test %s" % (y + 1))
w.grid(row=y+1, column=0)
#Create the Checkbuttons & save them for future reference
self.grid = []
for y in rowrange:
row = []
for x in colrange:
b = tk.Checkbutton(master)
#Store the button's position and value as attributes
b.pos = (y, x)
b.var = tk.IntVar()
#Create a callback bound to this button
func = lambda w=b: self.check_cb(w)
b.config(variable=b.var, command=func)
b.grid(row=y+1, column=x+1)
row.append(b)
self.grid.append(row)
#Track the number of on buttons in each row
self.rowstate = rows * [0]
master.mainloop()
def check_cb(self, button):
''' Checkbutton callback '''
state = button.var.get()
y, x = button.pos
#Get the row containing this button
row = self.grid[y]
if state == 1:
self.rowstate[y] += 1
if self.rowstate[y] == 2:
#Disable all currently off buttons in this row
for b in row:
if b.var.get() == 0:
b.config(state=tk.DISABLED)
else:
self.rowstate[y] -= 1
if self.rowstate[y] == 1:
#Enable all currently off buttons in this row
for b in row:
if b.var.get() == 0:
b.config(state=tk.NORMAL)
#print y, x, state, self.rowstate[y]
def get_checked(self):
''' Make a list of the selected Groups in each row'''
data = []
for row in self.grid:
data.append([x + 1 for x, b in enumerate(row) if b.var.get()])
return data
def main():
g = CheckGrid(rows=10, columns=10)
#Print selected Groups in each Test row when the window closes
data = g.get_checked()
for y, row in enumerate(data):
print "Test %2d: %s" % (y + 1, row)
if __name__ == '__main__':
main()
Here's an example using your provided 10x10 grid. It should give you the basic idea of how to implement this.
Just make sure you keep a reference to every Checkbutton (boxes in the example) as well as every IntVar (boxVars in the example).
Here's why:
-Checkbuttons are needed to call config(state = DISABLED/NORMAL).
-IntVars are needed to determine the value of each Checkbutton.
Aside from those crucial elements its basically just some 2D array processing.
Here's my example code (now based off of your provided code).
from Tkinter import *
master = Tk()
master.title("Select Groups")
rows=10
columns=10
boxes = []
boxVars = []
# Create all IntVars, set to 0
for i in range(rows):
boxVars.append([])
for j in range(columns):
boxVars[i].append(IntVar())
boxVars[i][j].set(0)
def checkRow(i):
global boxVars, boxes
row = boxVars[i]
deselected = []
# Loop through row that was changed, check which items were not selected
# (so that we know which indeces to disable in the event that 2 have been selected)
for j in range(len(row)):
if row[j].get() == 0:
deselected.append(j)
# Check if enough buttons have been selected. If so, disable the deselected indeces,
# Otherwise set all of them to active (in case we have previously disabled them).
if len(deselected) == (len(row) - 2):
for j in deselected:
boxes[i][j].config(state = DISABLED)
else:
for item in boxes[i]:
item.config(state = NORMAL)
def getSelected():
selected = {}
for i in range(len(boxVars)):
temp = []
for j in range(len(boxVars[i])):
if boxVars[i][j].get() == 1:
temp.append(j + 1)
if len(temp) > 1:
selected[i + 1] = temp
print selected
for x in range(rows):
boxes.append([])
for y in range(columns):
Label(master, text= "Group %s"%(y+1)).grid(row=0,column=y+1)
Label(master, text= "Test %s"%(x+1)).grid(row=x+1,column=0)
boxes[x].append(Checkbutton(master, variable = boxVars[x][y], command = lambda x = x: checkRow(x)))
boxes[x][y].grid(row=x+1, column=y+1)
b = Button(master, text = "Get", command = getSelected, width = 10)
b.grid(row = 12, column = 11)
mainloop()