Hi I am trying to create a table using Tktable and the wrapper i found online : http://tkinter.unpythonic.net/wiki/TkTableWrapper
I am having trouble understanding how to add rows. I have been modifying the sample_test method included in the file with no success.
My end goal is to have a table in my GUI and then write a row every time i read in data from a serial connection. I therefore would not know how many rows i will need. I could just pre-assign a very large table but this seems slow. I am expecting to read off a very large number of datapackets (potentially in the tens of thousands).
I have tried the insert_rows method but I don't think I fully understand how it works and the documentation is a bit sparse.
Also, I could not get the scroll bar to work. while i could add it easily by adding the lines below before the test.pack method call, they wouldn't scroll with the mouse. I think the binding is off:
scroll = Tkinter.Scrollbar(root)
test.configure(yscrollcommand=scroll.set)
scroll.pack(side=Tkinter.RIGHT,fill=Tkinter.Y)
Any help would be appreciated.
Thanks.
As it stands, sample_test creates a table with the configuration option
state='disabled' (this is why you can't add more rows).
Try setting it to state='normal', either at creation, or whenever you need
to make changes, whichever suits best.
def sample_test():
from Tkinter import Tk, Scrollbar, Button
def test_cmd(event):
print 'command'
def browsecmd(event):
print 'browsecmd'
#print event.__dict__
root = Tk()
quit = Button(root, text="QUIT", command=root.destroy)
quit.pack(side = 'bottom')
numrows, numcols = 10, 10
var = ArrayVar(root)
for y in range(numrows):
for x in range(numcols):
index = "%i,%i" % (y, x)
var[index] = index
test = Table(root,
rows=numrows+1,
cols=numcols+1,
state='normal',
width=6,
height=6,
titlerows=1,
titlecols=1,
roworigin=-1,
colorigin=-1,
selectmode='extended',
selecttype='row',
rowstretch='unset',
colstretch='last',
browsecmd=browsecmd,
flashmode='on',
variable=var,
usecommand=0,
command=test_cmd)
# http://effbot.org/zone/tkinter-scrollbar-patterns.htm
s = Scrollbar(root, orient='vertical', command=test.yview_scroll)
test.config(yscrollcommand=s.set)
s.pack(side='right', fill='y')
test.pack(expand=1, fill='both')
test.tag_configure('sel', background = 'yellow')
test.tag_configure('active', background = 'blue')
test.tag_configure('title', anchor='w', bg='red', relief='sunken')
data = ('py','t','h','o','n','','+','','Tk','')
def add_new_data(*args):
#test.config(state='normal')
test.insert_rows('end', 1)
r = test.index('end').split(',')[0] #get row number <str>
args = (r,) + args
idx = r + ',-1'
test.set('row', idx, *args)
test.see(idx)
#test.config(state='disabled')
root.after(3000, add_new_data, *data)
root.after(4000, add_new_data, *data)
root.mainloop()
Related
I am trying to create a game level editor. The program reads a binary file, associate the byte to a image and then, with a loop, it shows all the images in canvas (Canvas B). The images are Radiobutton. This part works properly.
Below (Canvas A) I have a set of images that are radiobuttons too. Here I choose the image I want to use by clicking on it. This image will be used to redesign the Canvas B Then I go to the Canvas B, decide which tile I want to change and click on it. And it works: the radiobutton has now the chosen image.
In fact it works every time but only the first time. If I change my mind and want to change a radiobutton already changed nothing happens.
I tried to understand what the problem is by printing the variable of the radiobutton with .get()and I see that it stored the value of the last rabiobutton clicked. I tried to reset this value even with del but it doesn't work.
Here's the code (canvas B)
img_list=[]
n_row = 0
n_col = 0
index = 0
x = IntVar()
for f in os.listdir(path):
img_list.append(ImageTk.PhotoImage(Image.open(os.path.join(path,f))))
n_col +=1
index +=1
if n_col > 21:
n_row +=1
n_col = 1
tile = Radiobutton(A, image=img_list[index-1], indicatoron=0, bd=2, variable = x, value = index, selectcolor="red", command=several)
tile.grid(row=n_row, column = n_col)
And here's Canvas A
def erase():
global val_t_e_x
del val_t_e_x
val_t_e_x=t_e_x.get()
print(val_t_e_x)
img_qui=[]
for f in os.listdir(path):
img_qui.append(ImageTk.PhotoImage(Image.open(os.path.join(path,f))))
def several_editor():
global codice_bb
global val_x
global val_t_e_x
val_t_e_x=t_e_x.get()
print(val_t_e_x)
row_qui=row_list[val_t_e_x-1]
col_qui=col_list[val_t_e_x-1]
tile_editor=Radiobutton(B, image=img_qui[val_x-1], variable = val_t_e_x, value = rev, indicatoron=0, bd=0, selectcolor="blue",
highlightbackground="black", highlightcolor="black", command=erase)
tile_editor.grid(row=row_qui, column=col_qui)
col_b=0
row_b=9
l_editor=[]
row_list=[]
col_list=[]
rev=0
t_e_x = IntVar()
for x, y in zip(line[::2], line[1::2]):
a= ("./gfx/"+(x+y)+".png")
row_b-=1
rev+=1
if row_b<1:
col_b+=1
row_b=8
im = Image.open(a)
ph = ImageTk.PhotoImage(im)
l_editor.append(ph)
tile_editor = Radiobutton(B, image=l_editor[rev-1], variable = t_e_x, value = rev, indicatoron=0, bd=0, selectcolor="blue",
highlightbackground="black", highlightcolor="black", command=several_editor)
tile_editor.grid(row=row_b, column=col_b)
row_list.append(row_b)
col_list.append(col_b)
I suppose that the problem is in the function def several_editor()
tile_editor=Radiobutton(B, image=img_qui[val_x-1], variable = val_t_e_x, value = rev,
indicatoron=0, bd=0, selectcolor="blue", highlightbackground="black",
highlightcolor="black", command=erase)
and that I am not handling the val_t_e_x variable properly.
Hope you can help me.
The reason your function isn't returning is because you don't know what the input is for several_editor(). You need to anonymize it with lambda.
I'm going to provide a simple example of why this is necessary:
import tkinter, os, sys
import tkinter.ttk as ttk
class T():
def __init__(self):
labelstr=["ascii"]
top = tkinter.Tk()
top.geometry('200x50')
top.title('lambda')
for label in labelstr:
self.lab = tkinter.Label(top, text=label, width=len(label)).grid(row=labelstr.index(label)+1, column=0)
self.ent = tkinter.Entry(top, width=10).grid(row=labelstr.index(label)+1, column=1)
but = tkinter.Button(top, text="submit", command=lambda: self.submit(top))
but.grid(row=labelstr.index(label)+1, column=3)
top.mainloop()
def submit(self, frame):
for i in frame.winfo_children():
if i.winfo_class() == "Entry":
i.delete(0, tkinter.END)
i.insert(0, "HelloWorld!")
I can't really test the code where I am at the moment, but this should work.
The above is to provide a functional example of proper use of a lambda function within your code and demonstrate why you'd need it. Try removing the lambda and see that the function runs only once and will no longer run properly. This symptom should look familiar to you as it's exactly as you described it. Add the lambda back and the function runs again each time you hit the button.
I am trying to create an SQLite database interface using python and Tkinter to store the results of tests. Basically, the user first selects an option from a drop-down menu (Tkinter menubar) which generates a form with 6 predefined tests for collecting input. The input from these entry boxes is then stored in SQLite, where each row/test is stored in a different table. Different materials will be tested but the tests are the same for each. I want to compare the same test results for multiple materials at a later stage to model trends etc.
So, my idea is that the menu-bar calls a function (which determines the test category, board_strength for example) to generate a form with the date and project number and which does the layout. A class is used to generate the entry boxes, get the input, calculate the average, and store the values to SQLite after each row is filled out. If the user requires an additional row for the same test, a button should add it as needed. Then, if the user moves onto the next test (maybe via a button..), they again have the option to add another row for this test, and so on. Here I've only shown 1 test, ECT, but there are 5 more.
I've tried making a button to generate a new row but it doesn't seem to work properly. I've read a few posts on this site and googled, and although I'm quite new to working with classes, it seemed the easiest way to do what I want.
from tkinter import *
import tkinter as tk
date = datetime.date.today()
class create_widgets:
'''create widgets for input data, collect input, determine average, and store'''
def __init__(self, window, test_name, units, row):
self.window = root
self.test_name = ''
self.units = ''
self.row = 0
self.add_entry(window, test_name, units, row)
def add_entry(self, window, test_name, units, row):
self.entries = []
lbl_of_test = Label(window, text = f'{test_name} ({units}):', font = ('Arial', 10))
lbl_of_test.grid(row = row, column = 0)
for i in range(3):
self.entry = Entry(window, width=8)
self.entry.grid(row=row, column=i+1)
self.entries.append(self.entry)
def board_strength():
''' Add project details and date and generate test labels'''
lbl1 = Label(top_frame, text = 'Board Strength Properties', font = ('Arial Bold', 12))
lbl1.pack(side = 'top')
display_date = Label(top_frame, text = f'Date: {date}', font = ('Arial', 10))
display_date.pack(anchor = 'w')
sample_lable = Label(top_frame, text = 'Project Number:', font = ('Arial Bold', 10))
sample_lable.pack(anchor = 'sw', side = 'left')
sample_entry = Entry(top_frame, width = 15)
sample_entry.pack(anchor = 'sw', side = 'left')
for i in range(3):
lbl = Label(btm_frame2, text = f'Test: {i+1}', font = ('Arial', 10))
lbl.grid(row=0, column=i+1)
#First test
ect_widgets = create_widgets(btm_frame2, test_name = 'ECT', units = 'kN/m', row = 1)
ect_button = Button(btm_frame2,text='Add ECT', command = ect_widgets.add_entry)
ect_button.grid(row=12,column= 1, pady = 20)
# Next test
# fct_widgets = create_widgets(btm_frame2, test_name = 'FCT', units = 'kPa', row = 1)
# fct_button = Button(btm_frame2,text="FCT", command = fct_widgets.add_entry)
# fct_button.grid(row=12,column= 2, pady = 20)
# Next: Add FCT
menubar = Menu(root)
filemenu = Menu(menubar, tearoff=0)
menubar.add_cascade(label="File", menu=filemenu)
filemenu.add_separator()
filemenu.add_command(label="Exit", command=root.quit)
papermenu = Menu(menubar, tearoff=0)
menubar.add_cascade(label="Paper", menu=papermenu)
boardmenu = Menu(menubar, tearoff=0)
menubar.add_cascade(label="Board", menu=boardmenu)
boardmenu.add_command(label="Strength Properties", command = board_strength)
boardmenu.add_command(label="Structural Properties", command= 'board_structure')
root.config(menu=menubar)
root.mainloop()
When I run this code, I get the following error:
TypeError: add_entry() missing 4 required positional arguments: 'window', 'test_name', 'units', and 'row'
Why does it still want these arguments if I've already passed them creating the instance ect_widget? And how do I increase the row number accordingly when new rows are added? The row numbers are going to be a headache.
Any help or input would greatly be appreciated. Thanks!
This snippet of the code you have offered isn't enough to actually run it so I'm sorry for minor errors, but I think I know what you are missing here.
You probably want the add_entry method to not have all those arguments required. While it needs those things to do what it does; you can save that data in the instance of the class when you create it.
def __init__(self, window, test_name, units, row):
self.window = window
self.test_name = test_name
self.units = units
self.row = row
self.add_entry()
def add_entry(self):
self.entries = []
lbl_of_test = Label(self.window,
text = f'{self.test_name} ({self.units}):',
font = ('Arial', 10))
lbl_of_test.grid(row = self.row, column = 0)
for i in range(3):
self.entry = Entry(self.window, width=8)
self.entry.grid(row=self.row, column=i+1)
self.entries.append(self.entry)
Something like this is probably more appropriate. We store attributes of an instance using that "self." syntax. The "self.window = window" etc saves these things so you can use them whenever without passing them. Then, in the add_entry method, it can use the saved values of those things by calling up the 'self.' version of them.
(Technically speaking, there is nothing magical about using the word 'self' before those variables; it works that way because 'self' is the first argument passed into non-static methods--including __init__--in a class. Whatever is first refers to the instance of that class, ergo the typical name of 'self.' If you ever needed to refer to the instance of a class within its own methods you would just use 'self.')
I hope that helps, let us know if you have more questions.
P.S. I split up that really long line into multiple...generally, you don't want lines longer than 80 characters, and Python lets you cut lines freely between items inside of parentheses.
I am using python 3.7 and tkinter.
I want to display text information which is periodically updated and I want to display it in a table format where each cell is of fixed width. Without going into too many specifics, I am using grid geometry manager. Unfortunately, the text is not displaying in the cells, and I don't know why.
I understand the construct of my "table"-like GUI is not as simple as it could be. I want to be able to easily hide/show rows, so I have given each row its own frame making it easy to show/hide any row. (I have also used a grid layout within each row as well, thinking that this will be the easiest way to eventually force the geometry manager to give me uniform/non-changing cell widths.)
My first problem is that no text is displaying in my labels in the "table".
I have verified via print('label text = ', label.cget("textvariable")), that the correct textvariable was in fact assigned to the label.
I have read many posts regarding "tkinter label text not showing" and nothing has helped.
Thank you for any insights. Here is the code:
driverList = ['bob','fred','ethel']
tracks = ['daytona', 'charlotte', 'atlanta', 'darlington','lasvegas','watkins','talladega']
dynamicAllDriverFlagReportDict = {'bob': ['bob','i','','RevDn','','','',''],
'fred': ['fred','In-Up','','','','RevUp','Ham',''],
'ethel': ['ethel','','RevDn','','','In-Dn','Sht','']
}
global driverFrameDict
driverFrameDict= dict()
# Set up the GUI
window = tk.Tk()
myDataFrame = tk.Frame(master=window)
myDataFrame.pack()
btnFrame= tk.Frame(master=window, bg="gray")
btnFrame.pack(side="bottom")
for driverIndex in range(len(driverList)):
myOneStockFrame = tk.Frame(master=myDataFrame, height=25, width=800)
for tfIndex in range(len(tracks)+1):
oneTFFrame = tk.Frame(master=myOneStockFrame,relief=tk.RAISED, borderwidth=1)#, height=25)#, width=8)
if tfIndex == 0:
label = tk.Label(master=oneTFFrame,text=driverList[driverIndex])#, height=15, width=8)
else:
print('driverIndex=', driverIndex, '; tfIndex=', tfIndex, 'dynamicAllDriverFlagReportDict=', dynamicAllDriverFlagReportDict[driverList[driverIndex]][tfIndex])
label = tk.Label(master=oneTFFrame,textvariable=dynamicAllDriverFlagReportDict[driverList[driverIndex]][tfIndex])#,height=20)#, width=8)
print('label text = ', label.cget("textvariable"))
label.pack()#padx=5,pady=5)
#label.pack_propagate(0)
oneTFFrame.grid(row=0,column=tfIndex)#, sticky= "nswe", padx=0, pady=0)
#oneTFFrame.grid_propagate(0)
myOneStockFrame.grid(row=driverIndex,column=0)#, sticky= "nswe", padx=0, pady=0)
#myOneStockFrame.grid_propagate(0)
driverFrameDict[driverList[driverIndex]] = myOneStockFrame
#print('driverFrameDict['+driverList[driverIndex]+'] = ', myOneStockFrame)
window.mainloop()
Here is the output:
Thank you.
textvariable has to be set to an instance of one of the special tkinter variables (StringVar, etc). You're passing a string, which causes a new internal variable to be created with a name that matches the string.
I am having the problem where I am able to populate two optionmenus using Tkinter, (the second one dynamically based off the first), but I notice when I try to select one of the values in the second optionmenu, it does not allow me to select. I have noticed on the same GUI, sometimes when running another function, it will have this affect on a different option menu that worked fine prior to running the function. The options will show correctly, and the mouse can scan over them, but when you click on one it doesn't show that it has been selected, or execute the command set to it. Has anyone had this problem before?
Ok, so my hopes of someone else having the same issue are going down, and I'll include more code by request of some of the responders in case this might shed light on the issue. I'll try and pull everything applicable:
class GUI(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.build_gui()
#reads the tabs on an excel and inputs the tab names as the values in the first optionmenu (this works)
def read_board_tabs(self):
#many of these variables in the function may not be defined. Ignore them as they are taken out of context. This should just help visually see the structure.
filepath = 'Board Control.xlsx'
wkbk = load_workbook((filepath))
sheets = wkbk.get_sheet_names()
print sheets
return sheets
#reads content of that excel tab chosen to populate the second optionmenu (populating the option menu works, but they can't be selected once populated)
def read_serials(self, board):
#many of these variables in the function may not be defined. Ignore them as they are taken out of context. This should just help visually see the structure.
sheet = board
if(sheet == ''):
return ['']
else:
filepath = 'Board Control.xlsx'
workbook = excel_transfer.workbook(filepath)
wb = workbook.open_existing_workbook()
ws = workbook.activate_specific_worksheet(wb, sheet)
row = 1
current_row = 0
previous_row = 0
serials = []
#read the serials that exist for the board selected
while current_row != None:
previous_row = current_row
row = row + 1
cell = 'A' + str(row)
current_row = workbook.read_new_cell(cell, ws)
if(current_row != None):
serials.append(current_row)
if(len(serials) == 0):
serials = ['']
self.BOARD_SERIAL_OPTION['menu'].delete(0, 'end')
for item in serials:
self.BOARD_SERIAL_OPTION['menu'].add_command(label=item)
#this is the command that won't execute when I try to select the second optionmenu value
def find_board_info(self, serial):
#many of these variables in the function may not be defined. Ignore them as they are taken out of context. This should just help visually see the structure.
self.board_checkout_status_var.set('')
sheet = (self.boards_var).get()
new_search = StringFound.StringSearch(serial)
results = new_search.read_board(sheet)
if(results == ['']):
self.boardstatusvar.set('No board was found')
else:
self.boardstatusvar.set('Board: %s Serial: %s' %(results[1], serial))
self.boardstatus_locationvar.set('Found in file: %s' %results[0])
self.boards_var.set('%s serial %s' %(results[1], serial))
self.dispositionvar.set(results[3])
self.TXvar.set(results[5])
self.RXvar.set(results[6])
self.lastvar.set(results[10])
self.datevar.set(results[9])
if(results[14] != None):
self.currentvar.set(results[10])
self.locationvar.set(results[4])
self.imagevar.set(results[8])
self.notesvar.set(results[12])
self.current_board_chosen = [sheet, serial, results[15]]
#creates the gui
def build_gui(self):
n = Notebook(self)
board_process = Tkinter.LabelFrame(self, text="Board Updates")
n.add(board_process, text='Board Signout')
n.pack()
self.boards_var = StringVar()
self.boards_var.set("")
self.serials_var = StringVar()
self.serials_var.set("")
self.SEARCHBOARDFRAME = Tkinter.LabelFrame(board_process, text='Find Board')
self.SEARCHBOARDFRAME.grid(row=0, column=0, sticky='WE')
self.BOARD_SEARCH_LABEL = Label(self.SEARCHBOARDFRAME, text='Type:')
self.BOARD_SEARCH_LABEL.grid(row=0, column=0, sticky='W', padx=5, pady=2)
self.BOARD_SEARCH_OPTION = OptionMenu(self.SEARCHBOARDFRAME, self.boards_var, *self.list_of_boards, command=self.read_serials)
self.BOARD_SEARCH_OPTION.grid(row=0, column=1, sticky='W', padx=5, pady=2)
self.BOARD_SERIAL_LABEL = Label(self.SEARCHBOARDFRAME, text='Serial:')
self.BOARD_SERIAL_LABEL.grid(row=1, column=0, sticky='W', padx=5, pady=2)
self.BOARD_SERIAL_OPTION = OptionMenu(self.SEARCHBOARDFRAME, self.serials_var, *self.list_of_serials, command=self.find_board_info)
self.BOARD_SERIAL_OPTION.grid(row=1, column=1, sticky='W', padx=5, pady=2)
if __name__ == '__main__':
root = Tk()
app = GUI(root)
root.mainloop()
The problem is in these lines of code:
for item in serials:
self.BOARD_SERIAL_OPTION['menu'].add_command(label=item)
You are putting items in the option menu with a label, but you aren't giving them a command. The whole reason the Optionmenu widget exists is to add a special command to each item that gives the option menu its behavior. Unless you add that command when you dynamically create new items, those items won't do anything when you select them -- it's just a dumb menu with labels on it.
Unfortunately, the command that is associated with each item is returned from a private factory class (Tkinter._setit), so you don't have any officially supported way to add new items to an option menu. If you aren't afraid to use private commands, you can change your code to be this:
for item in serials:
command=Tkinter._setit(self.serials_var, item, self.find_board_info)
self.BOARD_SERIAL_OPTION['menu'].add_command(label=item, command=command)
For another way to solve this problem see Change OptionMenu based on what is selected in another OptionMenu
I have written the following code:
from Tkinter import *
root = Tk()
root.title("Power Method")
labeltext1 = StringVar()
labeltext2 = StringVar()
labeltext1.set('Parameters') # Set start value
labeltext2.set('Epsilon')
label1 = Label (root, textvariable = labeltext1, height = 4)
label1.pack()
Entry(root, textvariable = labeltext1). pack()
label2 = Label (root, textvariable = labeltext2)
label2.pack()
Entry(root, textvariable = labeltext2). pack()
checkBox1 = Checkbutton(root, text = "NumPy")
checkBox1.pack()
checkBox2 = Checkbutton(root, text = "Not NumPy")
checkBox2.pack()
Button(root, text = "Exit").pack(side = RIGHT)
Button(root, text = "Compute").pack(side = RIGHT)
root.mainloop()
This code, when run, creates a window that contains Parameters, Epsilon as two places that you could enter value and NumPy and Not NumPy as checkboxes and finally a compute and exit button.
I am trying to format the code, in a way that Parameters and Epsilon appear on the left of the window, Numpy and Not Numpy infront of them on the right and Compute and Exit stay in their current positions.
Any help regarding the formatting would be appreciated.
Alright you need to google .grid() and .pack() and .place() for tkinter to learn more about what each is good for. For your case you could use any of them, but .grid() is probably the best and easiest for you to use.
To do this you must change .pack() to .grid() everywhere you have it, then you should use the row and column options to put your widgets where you want them. E.G.
label1.grid(row = 1, column = 1)
label2.grid(row = 1, column = 2)
this will put label2 on the right side of label1. The way .grid() works is that the columns are as wide as the largest (widest) widget in that column, same applies for rows.
if you need anything else just ask, but please do look at the documentations as Bryan said.