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.
Related
How would I create multiple frames that have the same widgets in Tkinter? Basically what I want to do is create 15 copies of a set of multiple frames that all contain the same widgets as shown in the image, the purpose of this program is to assist the user in sorting photographs into groups based on a specific ID supplied by the user. The radio buttons are there for the user to classify each photo, i.e front, back ,top etc..
Its not very efficient to copy the code 15 times and I want to know if it's possible to use a class to define the frame once and reuse the code for each new frame. I need to keep track of what the user does on each frame and save their selections on the radio buttons and check boxes for each frame. After all the photos have been classified by the user, a button is clicked that should then save all the photos with a new ID and also saves the info from the radio buttons into a csv file. Then the next batch of photos is loaded and the process repeats.
I have included an example of the code I used to create one of the frames, this is the code that I want to make reusable. I do not want to have to repeat it 15 times.
############################################################################
#FRAME 3
Photo_2 = Frame(master, bg = "white",relief = RIDGE, bd = 2)
Photo_2.grid(column = 2, row = 1, padx=5, pady=5)
Lbl2 = Label(Photo_2,text = 'Frame 3')
Lbl2.grid(row = 0, column = 0, columnspan = 4, pady = 5)
# Check box
varc2 = StringVar()
varc2.set(0)
Check_2 = Checkbutton(Photo_2, variable = varc2, text="Relevant?", command = lambda:Chk_Val(varc2))
Check_2.grid(row = 1,column = 0,columnspan = 4)
# Photo 1
P2 = "Photo_2.jpg"
P2 = Image.open(P2).resize((200, 200), Image.ANTIALIAS)
phot2 = ImageTk.PhotoImage(P2)
panel = Label(Photo_2, image = phot2)
panel.grid(columnspan = 3, column=1)
# Create Multiple Radio Buttons
Rad_Cont = Frame(Photo_2)
Rad_Cont.grid(column = 0, row = 2)
v2 = StringVar()
v2.set("Address")
for text,mode in RADIO:
b = Radiobutton(Rad_Cont, text=text, variable=v2,
value=mode, command = lambda:Rad_Val(v2))
b.pack()
################################################################################
Of course it is possible to create a class to represent similar objects.
Here is how I might implement what you're trying to accomplish:
import tkinter as tk
class PhotoFrame(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master, bg='white', relief='ridge', bd=2)
self.label_widget()
self.checkbox_widget()
self.photo_widget()
self.radio_widgets()
def label_widget(self):
self.title_label = tk.Frame(self, text='Frame 3') # Or Frame 1, 2 etc.
self.title_label.grid(row=0, column=0, columnspan=4, pady=5)
def checkbox_widget(self):
self.varc = tk.StringVar()
self.varc.set(0)
self.checkbox = tk.Checkbutton(self, variable=self.varc,
text='Relevant?', command=self.Chk_Val)
self.checkbox.grid(row=1, column=0, columnspan=4)
def photo_widget(self):
# ... Your code here
def radio_widgets(self):
# ... Your code here
def Chk_Val(self):
# ... Your code here
Now I tried not to give you the entire solution so you can learn and figure the rest out by yourself, but I'm sure you can see what I'm getting at in terms of using a class. Now this class PhotoFrame could be used as many times as you'd like, although please understand you'll have to configure each frame appropriately, e.g. I would omit the the text attribute assignment in the label_widget section (You don't want all of your frames titled 'Frame 3'), so in your main program logic, you'd configure each instance of a PhotoFrame object like so:
frame1 = PhotoFrame(master)
frame1.title_label.configure(text='Frame 1') # Along with any other configuration
I hope this all helps - if you are not familiar with classes check out the provided documentation, and here is a great tkinter reference: NMT tkinter
I am quite new to Tkinter, but, nevertheless, I was asked to "create" a simple form where user could provide info about the status of their work (this is sort of a side project to my usual work).
Since I need to have quite a big number of text widget (where users are required to provide comments about status of documentation, or open issues and so far), I would like to have something "scrollable" (along the y-axis).
I browsed around looking for solutions and after some trial and error I found something that works quite fine.
Basically I create a canvas, and inside a canvas a have a scrollbar and a frame. Within the frame I have all the widgets that I need.
This is a snipet of the code (with just some of the actual widgets, in particular the text ones):
from Tkinter import *
## My frame for form
class simpleform_ap(Tk):
# constructor
def __init__(self,parent):
Tk.__init__(self,parent)
self.parent = parent
self.initialize()
#
def initialize(self):
#
self.grid_columnconfigure(0,weight=1)
self.grid_rowconfigure(0,weight=1)
#
self.canvas=Canvas(self.parent)
self.canvas.grid(row=0,column=0,sticky='nsew')
#
self.yscrollbar = Scrollbar(self,orient=VERTICAL)
self.yscrollbar.grid(column =4, sticky="ns")
#
self.yscrollbar.config(command=self.canvas.yview)
self.yscrollbar.pack(size=RIGTH,expand=FALSE)
#
self.canvas.config(yscrollcommand=self.yscrollbar.set)
self.canvas.pack(side=LEFT,expand=TRUE,fill=BOTH)
#
self.frame1 = Frame(self.canvas)
self.canvas.create_window(0,0,window=self.frame1,anchor='nw')
# Various Widget
# Block Part
# Label
self.labelVariableIP = StringVar() # Label variable
labelIP=Label(self.frame1,textvariable=self.labelVariableIP,
anchor="w",
fg="Black")
labelIP.grid(column=0,row=0,columnspan=1,sticky='EW')
self.labelVariableIP.set(u"IP: ")
# Entry: Single line of text!!!!
self.entryVariableIP =StringVar() # variable for entry field
self.entryIP =Entry(self.frame1,
textvariable=self.entryVariableIP,bg="White")
self.entryIP.grid(column = 1, row= 0, sticky='EW')
self.entryVariableIP.set(u"IP")
# Update Button or Enter
button1=Button(self.frame1, text=u"Update",
command=self.OnButtonClickIP)
button1.grid(column=2, row=0)
self.entryIP.bind("<Return>", self.OnPressEnterIP)
#...
# Other widget here
#
# Some Text
# Label
self.labelVariableText = StringVar() # Label variable
labelText=Label(self.frame1,textvariable=
self.labelVariableText,
anchor="nw",
fg="Black")
labelText.grid(column=0,row=curr_row,columnspan=1,sticky='EW')
self.labelVariableTexta.set(u"Insert some texts: ")
# Text
textState = TRUE
self.TextVar=StringVar()
self.mytext=Text(self.frame1,state=textState,
height = text_height, width = 10,
fg="black",bg="white")
#
self.mytext.grid(column=1, row=curr_row+4, columnspan=2, sticky='EW')
self.mytext.insert('1.0',"Insert your text")
#
# other text widget here
#
self.update()
self.geometry(self.geometry() )
self.frame1.update_idletasks()
self.canvas.config(scrollregion=(0,0,
self.frame1.winfo_width(),
self.frame1.winfo_height()))
#
def release_block(argv):
# Create Form
form = simpleform_ap(None)
form.title('Release Information')
#
form.mainloop()
#
if __name__ == "__main__":
release_block(sys.argv)
As I mentioned before, this scripts quite does the work, even if, it has a couple of small issue that are not "fundamental" but a little annoying.
When I launch it I got this (sorry for the bad screen-capture):
enter image description here
As it can be seen, it only shows up the first "column" of the grid, while I would like to have all them (in my case they should be 4)
To see all of the fields, I have to resize manually (with the mouse) the window.
What I would like to have is something like this (all 4 columns are there):
enter image description here
Moreover, the scrollbar does not extend all over the form, but it is just on the low, right corner of the windows.
While the latter issue (scrollbar) I can leave with it, the first one is a little more important, since I would like to have the final user to have a "picture" of what they should do without needing to resize the windows.
Does any have any idea on how I should proceed with this?
What am I missing?
Thanks in advance for your help
In the __init__ method of your class, you do not appear to have set the size of your main window. You should do that, or it will just set the window to a default size, which will only show whatever it can, and in your case, only 1 column. Therefore, in the __init__ method, try putting self.geometry(str(your_width) + "x" + str(your_height)) where your_width and your_height are whatever integers you choose that allow you to see what you need to in the window.
As for your scrollbar issue, all I had to do was change the way your scrollbar was added to the canvas to a .pack() and added the attributes fill = 'y' and side = RIGHT to it, like so:
self.yscrollbar.pack(side = 'right', fill = 'y')
Also, you don't need:
self.yscrollbar.config(command=self.canvas.yview)
self.yscrollbar.pack(size=RIGHT,expand=FALSE)
Just add the command option to the creation of the scrollbar, like so:
self.scrollbar = Scrollbar(self,orient=VERTICAL,command=self.canvas.yview)
In all, the following changes should make your code work as expected:
Add:
def __init__(self,parent):
Tk.__init__(self,parent)
self.parent = parent
self.initialize()
# Resize the window from the default size to make your widgets fit. Experiment to see what is best for you.
your_width = # An integer of your choosing
your_height = # An integer of your choosing
self.geometry(str(your_width) + "x" + str(your_height))
Add and Edit:
# Add `command=self.canvas.yview`
self.yscrollbar = Scrollbar(self,orient=VERTICAL,command=self.canvas.yview)
# Use `.pack` instead of `.grid`
self.yscrollbar.pack(side = 'right', fill = 'y')
Remove:
self.yscrollbar.config(command=self.canvas.yview)
self.yscrollbar.pack(size=RIGHT,expand=FALSE)
I am an amateur python programer with 2 months of experience. I am trying to write a GUI to-do list through tkinter. The actual placement of the buttons are not important. I can play around with those after. I need some help with displaying the appended item to the list. In the program, it updates well on the digit, but it won't print onto the list. I double checked it on the console and it says "tkinter.StringVar object at 0x102fa4048" but didn't update the actual list. What I need help is how can I update the list Main_Q on my the label column? Much appreciate some direction and coding help. Thanks.
Main_Q =["read","clean dishes", "wash car"]
from tkinter import*
root=Tk(className="total tasks in the Q")
#formula
def update():
global Main_Q
a=len(Main_Q)
num.set(a)
def add2list():
Main_Q.append(name)
a=len(Main_Q)
num.set(a)
print (Main_Q)
#output
num=StringVar()
y=Label(root, textvariable=num).grid(row=0, column=1)
#input
name=StringVar()
b=Entry(root, textvariable=name).grid(row=7,column=0)
#buttons
z=Button(root, text="update", command=update).grid(row=7, column=2)
add2list=Button(root,text="add", command=add2list).grid(row=7,
column=1)
r = 0
for c in Main_Q:
Label(text=c, relief=RIDGE,width=15).grid(row=r,column=0)
r = r + 1
root.mainloop()
Your problem is that your for loop which build up your labels doesnt get called after each time you have entered a new "task". To easily fix this you can move this loop into your update function.
If you want to prevent of looping through widget everytime you can create a new list with all widgets which already have been created:
createdWidgets = []
widgetsQueue = []
In your update function you than have to iterate through the widgetsQueue (widgetsQueue.pop() for instance), create the widgets and append the widget to the createdWidgetes list.
def update():
global Main_Q
r = 0
for c in Main_Q:
Label(text=c, relief=RIDGE,width=15).grid(row=r,column=0)
r += 1 # shorthand for r = r + 1
Some addition notes:
for the entry it is easier to seperate the definition and placement:
b = Entry(root)
b.grid(row=7,column=0)
because than Entry() returns its instance and you can use it to get the text:
b.get()
if you go shopping do you throw everything into one bag ?
from tkinter import *
does axactly that(in this case the globals() variable would be the bag).If you want to read more about that Importing Python Modules. To prevent that and shorten the amount of letters to type:
import tkinter as t # or tk
root = t.Tk()
*But for sure, if you just want a small program its okay.
Design:
To resolve your problem, you need to design this simple solution:
retrieve the text of the Tkinter.Entry widget using get() method.
add the text you got in 1 to Main_Q using append() method.
bind the button that updates on click both Main_Q and your GUI using command method.
create a new Tkinter.Label widget, set its text to the value you got in 1 and increment its corresponding row in the GUI.
I prefer to organize your code within a class that contains a constructor where Main_Q is initialized so that we call initialize_user_interface() to initialize the GUI with its three elements:
def __init__(self, parent):
Tkinter.Frame.__init__(self, parent)
self.parent = parent
self.Main_Q = ["read", "clean dishes", "wash car"]
self.r = 0 # position of the row of each label
self.initialize_user_interface()
The method initialize_user_interface() does what its name says. We mainly bind the function update_gui() that inserts a new label with the text set to what the user types in Tkinter.Entry widget using command = self.update_gui
ef initialize_user_interface(self):
self.parent.title("Update GUI")
self.parent.grid_rowconfigure(0, weight = 1)
self.parent.grid_columnconfigure(0, weight = 1)
for e in self.Main_Q:
Tkinter.Label(self.parent, anchor = Tkinter.W, text = e).grid(row = self.r, sticky = Tkinter.W)
self.r+=1
self.entry_text = Tkinter.Entry(self.parent)
self.entry_text.grid(row = 0, column = 1)
self.button_update = Tkinter.Button(self.parent, text = "Update", command = self.update_gui).grid(row = 1, column = 1, sticky = Tkinter.E)
Finally, nothing is simpler than update_gui() function:
def update_gui(self):
self.r+=1 # increment the row reserved to the new label
self.Main_Q.append(self.entry_text.get())
Tkinter.Label(self.parent, anchor = Tkinter.W, text = self.entry_text.get()).grid(row = self.r, sticky = Tkinter.W)
Programming the application:
Here is the full program:
'''
Created on Mar 11, 2016
#author: Bill BEGUERADJ
'''
import Tkinter
class Begueradj(Tkinter.Frame):
def __init__(self, parent):
Tkinter.Frame.__init__(self, parent)
self.parent = parent
self.main_queue = ["read", "clean dishes", "wash car"]
self.r = 0
self.initialize_user_interface()
def initialize_user_interface(self):
self.parent.title("Update GUI")
self.parent.grid_rowconfigure(0, weight = 1)
self.parent.grid_columnconfigure(0, weight = 1)
for e in self.main_queue:
Tkinter.Label(self.parent, anchor = Tkinter.W, text = e).grid(row = self.r, sticky = Tkinter.W)
self.r+=1
self.entry_text = Tkinter.Entry(self.parent)
self.entry_text.grid(row = 0, column = 1)
self.button_update = Tkinter.Button(self.parent, text = "Update", command = self.update_gui).grid(row = 1, column = 1, sticky = Tkinter.E)
def update_gui(self):
self.r+=1
self.main_queue.append(self.entry_text.get())
Tkinter.Label(self.parent, anchor = Tkinter.W, text = self.entry_text.get()).grid(row = self.r, sticky = Tkinter.W)
def main():
root = Tkinter.Tk()
b = Begueradj(root)
root.mainloop()
if __name__ == "__main__":
main()
Demo:
Here is a screenshot of the running program:
Note:
I coded the previous program using Python 2.7, so if you want to test it, please change Tkinter to tkinter. Everything else remains the same.
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
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()