Dynamic Widget creation based of configparser file - python

I am trying a piece of python based tkinter code with following objective:
(Please go through the objective, then I will take an example to explain what exactly i require and then in the end will post the script I have written)
Reads from a config file, which is implemented using configparser module.
Based on options read from this file it automatically generates widget.
These widgets are restricted to only labels and entry box as of now.
Every entry box is associated with a variable. It is hence needed to generate a variable
automatically whenever a entry box is declared.
Now when the user enters any value in the entry box, and presses calculate button a list is
generated with combination of values entered by user( in a specific format).
Example:
Let the configparser file has following content:
[widget]
label = ani_label,sham_label
entry = ani,sham
The list generated for this case will be like this:
out_list = ['-ani','< ani >','-sham','< sham >']
< ani > means value stored in ani variable
And below is the code that i have tried.
from Tkinter import *
from Tkinter import Tk
import Tkinter as tk
import ttk
import ConfigParser
import sys
############ Initialize ###############################
parser_read = ConfigParser.ConfigParser()
parser_read.read('option_read.config')
config_list = {}
config_list['label'] = parser_read.get('widget','label').split(',')
config_list['entry'] = parser_read.get('widget','entry').split(',')
######
def calculate():
#Will include the list generation part
pass
#######
root = Tk()
root.title("NRUNTEST GUI VERSION 1")
#
menuframe = ttk.Frame(root)
menuframe.grid(column=0,row=0)
menuframe.columnconfigure(0,weight=1)
menuframe.rowconfigure(0,weight=1)
#
mainframe_label = ttk.Frame(root)
mainframe_label.grid(column=1,row=0)
mainframe_label.columnconfigure(0,weight=1)
mainframe_label.rowconfigure(0,weight=1)
#
mainframe_entry = ttk.Frame(root)
mainframe_entry.grid(column=2,row=0)
mainframe_entry.columnconfigure(0,weight=1)
mainframe_entry.rowconfigure(0,weight=1)
#
general_label= Label(menuframe,text="Please Enter the Values Below").grid(column=1,row=0,sticky=(E))
compiler_label= ttk.Label(menuframe,text="Compiler")
compiler_label.grid(column=1,row=1,sticky=W)
#
calculate_button = ttk.Button(menuframe, text="Calculate", command=calculate).grid(column=1,row=2,sticky=(W,E))
#Automatic Widget declaration ###
for x in config_list['label']:
x = ttk.Label(mainframe_label,text=x).grid()
for x in config_list['entry']:
#print x
var = lambda: sys.stdout.write(x)
x = ttk.Entry(mainframe_entry,textvariable = x).grid()
root.mainloop()
The content of option_read.config is
[widget]
label : animesh_label,sharma
entry : animesh_entry,sharma
STATUS:
I can create the required widgets automatically. But I am not able to create the variables dynamically to store the entry box values.
Once the variable has been calculated, I can write the calculate function on my own.
Please advice how i can proceed.
If you have any better way to meet my requirements, please do suggest.
Also do ping me if you require any more inputs or my query is not clear.

The easiest way to do this, IMO, is to use a dict to store the references to the dynamically created variables. You could use the label as the key. For example:
vars = {}
for x in config_list['entry']:
vars[x] = StringVar()
entry = ttk.Entry(mainframe_entry, textvariable=vars[x])
entry.grid()
By the way... are you aware that if you do something like x=ttk.Entry(...).grid(...), x does not contain a reference to the widget? It contains the result of the call to grid, which is None.

Related

Build a fixed-size data table with tkinter / tksheet in Python -- bug in sheet_display_dimensions

I am building a user interface with several (as many as the user wants) tabular (spreadsheet-like) forms of user-specified size (but the size won't change once initialized). The user populates these tables either by copy-pasting data (usually from excel) or directly typing data to the cells. I am using the Tksheet Tkinter add-on.
It seems that there are several options in Tksheet to achieve the goal of opening an empty table of i rows and j columns:
a) set_sheet_data_and_display_dimensions(total_rows = None, total_columns = None).
This routine throws a TypeError. The error is raised in:
GetLinesHeight(self, n, old_method = False)
The subroutine expects the parameter n to be an integer, but receives a tuple.
The calling routine is sheet_display_dimensions, and the relevant line is:
height = self.MT.GetLinesHeight(self.MT.default_rh).
MT.default_rh is apparently a complex object, it can be an integer, but also a string or a tuple. Other routines that use it in Tksheet perform elaborate manipulation to make sure it is handed to the subroutine in integer form, but not so sheet_display_dimensions.
b) sheet_data_dimensions(total_rows = None, total_columns = None)
This seems to work programmatically, but does not display the table to the user.
One may add the line sheet_display_dimensions(i,j) but--you guessed it--this raises an error...
Sample code:
from tksheet import Sheet
import tkinter as tk
from tkinter import ttk
# This class builds and displays a test table. It is not part of the question but merely used to illustrate it
class SeriesTable(tk.Frame):
def __init__(self, master):
super().__init__(master) # call super class init to build frame
self.grid_columnconfigure(0, weight=1) # This configures the window's escalators
self.grid_rowconfigure(0, weight=1)
self.grid(row=0, column=0, sticky="nswe")
self.sheet = Sheet(self, data=[[]]) # set up empty table inside the frame
self.sheet.grid(row=0, column=0, sticky="nswe")
self.sheet.enable_bindings(bindings= # enable table behavior
("single_select",
"select_all",
"column_select",
"row_select",
"drag_select",
"arrowkeys",
"column_width_resize",
"double_click_column_resize",
"row_height_resize",
"double_click_row_resize",
"right_click_popup_menu",
"rc_select", # rc = right click
"copy",
"cut",
"paste",
"delete",
"undo",
"edit_cell"
))
# Note that options that change the structure/size of the table (e.g. insert/delete col/row) are disabled
# make sure that pasting data won't change table size
self.sheet.set_options(expand_sheet_if_paste_too_big=False)
# bind specific events to my own functions
self.sheet.extra_bindings("end_edit_cell", func=self.cell_edited)
self.sheet.extra_bindings("end_paste", func=self.cells_pasted)
label = "Change column name" # Add option to the right-click menu for column headers
self.sheet.popup_menu_add_command(label, self.column_header_change, table_menu=False, index_menu=False, header_menu=True)
# Event functions
def cell_edited(self, info_tuple):
r, c, key_pressed, updated_value = info_tuple # break the info about the event to individual variables
'''
updated_value checked here
'''
# passed tests
pass # go do stuff
def cells_pasted(self, info_tuple):
key_pressed, rc_tuple, updated_array = info_tuple # break the info about the event to individual variables
r, c = rc_tuple # row & column where paste begins
err_flag = False # will be switched if errors are encountered
'''
updated_array is checked here
'''
# passed tests
if err_flag: # error during checks is indicated
self.sheet.undo() # undo change
else:
pass # go do stuff
def column_header_change(self):
r, c = self.sheet.get_currently_selected()
col_name = sd.askstring("User Input", "Enter column name:")
if col_name is not None and col_name != "": # if user cancelled (or didn't enter anything), do nothing
self.sheet.headers([col_name], index=c) # This does not work - it always changes the 1st col
self.sheet.redraw()
# from here down is test code
tk_win = tk.Tk() # establish the root tkinter window
tk_win.title("Master Sequence")
tk_win.geometry("600x400")
tk_win.config(bg='red')
nb = ttk.Notebook(tk_win) # a notebook in ttk is a [horizontal] list of tabs, each associated with a page
nb.pack(expand=True, fill='both') # widget packing strategy
settings_page = tk.Frame(nb) # initiate 1st tab object in the notebook
nb.add(settings_page, text = "Settings") # add it as top page in the notebook
test = SeriesTable(nb) # creates a 1 row X 0 column table
nb.add(test, text = "Table Test") # add it as second page in the notebook
i = 4
j = 3
#test.sheet.set_sheet_data_and_display_dimensions(total_rows=i, total_columns=j) # raises TypeError
#test.sheet.sheet_data_dimensions(total_rows=i, total_columns=j) # extends the table to 4 X 3, but the display is still 1 X 0
#test.sheet.sheet_display_dimensions(total_rows=i, total_columns=j) # raises TypeError
test.sheet.insert_columns(j) # this works
test.sheet.insert_rows(i - 1) # note that we insert i-1 because table already has one row
test.mainloop()
I figured out a work-around with:
c)
insert_columns(j)
insert_rows(i - 1)
Note that you have to insert i-1 rows. This is because the sheet object is initiated with 1 row and 0 columns. (But does it say so in the documentation? No it does not...)

How to change a list depending on tkinter checkbutton

I have been trying to use tkinter CheckButton widget to edit items in a list - each item of the list is a new checkbutton. I want a save method to save the data to a text file, and a load method to load the info from the text file and mark the checkbutton boxes depending on the items in the list.
Here is my code so far, but the list doesn't seem to change when I check the buttons and update the list/file
Here is my code, I need to know why the list isn't updating when I check the boxes:
import tkinter.messagebox as box
modulesMain = Tk()
moduleChecks = []
def SaveChanges():
# Clear the text file
modules = open("modules.txt", "w") #Write mode to overwrite the whole file
modules.write("") # Put in blank text
modules.close()
modules = open("modules.txt", "a") # Append mode to append the file
for item in moduleChecks:
modules.write(item + "\n")
print(moduleChecks)
appButton = Checkbutton(modulesMain, text = "Test", variable = moduleChecks[0]).grid()
searchButton = Checkbutton(modulesMain, text = "Test", variable = moduleChecks[1]).grid()
Save = Button(modulesMain, text = "Save Changes", command = SaveChanges).grid()
The variable for each of your checkboxes needs to be an IntVar. Your moduleCheck list is currently un-initialised so when you try to access the elements inside it, you'll get an error.
In the below code (modified from yours), I've initialised the moduleCheck to contain two IntVars.
When you press the save button, it will print to the console the current state of the check boxes.
import tkinter as tk
modulesMain = tk.Tk()
moduleChecks = [tk.IntVar() for i in range(2)]
def SaveChanges():
for idx,item in enumerate(moduleChecks):
print(f"{idx} = {item.get()}")
appCheck = tk.Checkbutton(modulesMain, text = "App", variable = moduleChecks[0])
appCheck.grid()
searchCheck = tk.Checkbutton(modulesMain, text = "Check", variable = moduleChecks[1])
searchCheck.grid()
saveButton = tk.Button(modulesMain, text = "Save Changes", command = SaveChanges)
saveButton.grid()
modulesMain.mainloop()
Here is a basic solution.
import tkinter as tk
root = tk.Tk()
#list of options. doubles as checkbox label text
#appending to this list is all that is necessary to create more options
opts = ['App', 'Search']
#create an StringVar for every option
#this way there doesn't have to be type conversions for reading/writing files
vars = [tk.StringVar(value='0') for _ in opts]
#create checkboxes for every option
for opt, var in zip(opts, vars):
tk.Checkbutton(root, text=opt, variable=var, onvalue='1', offvalue='0').grid(sticky='w')
#write the checkbox values to file
def save_options_state():
with open("modules.txt", "w") as file:
file.write(''.join([v.get() for v in vars]))
#set checkbox values from file
def load_options_state():
with open("modules.txt", "r") as file:
for n, var in zip(list(file.read().strip()), vars):
var.set(n)
#save/load buttons
tk.Button(root, text="save", command=save_options_state).grid()
tk.Button(root, text="load", command=load_options_state).grid(row=2, column=1)
root.mainloop()
Here's a runnable example which I think shows how to do everything you've asked about. It assumes the modules.txt file contains data in the following format, where each line consists of a module's name and whether it's checked or not.
Mod1,0
Mod2,0
Mod3,0
The data in the file is used to create a list of Checkbuttons — one for each module — and initially sets to its current status as indicated in the file.
Here's the sample code:
from tkinter import *
import tkinter.messagebox as box
dataFilename = "modules.txt"
moduleChecks = []
def ReadModules():
with open(dataFilename, "r") as file:
for line in (line.strip() for line in file):
moduleName, moduleState = line.split(',')
moduleChecks.append((moduleName, int(moduleState)))
print('read:', moduleChecks)
def SaveChanges():
print('writing:', moduleChecks)
with open(dataFilename, "w") as file:
for checkButton in moduleCheckbuttons:
moduleName = checkButton.cget('text')
moduleState = checkButton.var.get()
file.write(','.join((moduleName, str(moduleState))) + '\n')
ReadModules()
modulesMain = Tk()
moduleCheckbuttons = []
# Create a Checkbutton for each module based on moduleChecks values.
for moduleName, moduleState in moduleChecks:
intVar = IntVar(value=moduleState)
checkButton = Checkbutton(modulesMain, text=moduleName, variable=intVar)
checkButton.var = intVar # Attach reference to variable.
checkButton.grid()
moduleCheckbuttons.append(checkButton)
saveButton = Button(modulesMain, text="Save Changes", command=SaveChanges)
saveButton.grid()
modulesMain.mainloop()

How to Convert Int to IntVar in Python Tkinter Module using SQLite/Pandas

So i'm building a project macro tracker using Python Gui Tkinter module.
I'm now trying to extra data from an SQLite database. The data extracted is type Int and I need to convert this to an IntVar so that I can use it as a textvariable in Tkinter's Label.
The Label(textvariable=) method required it be an IntVar in this case.
So just to summarize I need to convert an Int variable to an IntVar variable
Any help would be great,
def display_daily_macros():
daily = sqlite3.connect('daily.db')
data = pandas.read_sql_query("Select * from day", daily)
data_time = data[data['sqltime'] == "2020-09-11"]
all_info = data_time.sum()
protein = all_info['Protein']
print(type(protein))
You could do something like this,
from tkinter import *
root = Tk()
protien = '3' #this could be your value from the database
h = IntVar(value=protien) #declaring an IntVar and setting it to protien
e = Entry(root,textvariable=h) #assigning it to the entry widget
e.pack()
root.mainloop()
Here your defining an IntVar() and then later using the set() method to make it protien.
Hope this is what your looking for.
Cheers

Focusing on next entry box after typing a number in current entry box

I'm trying to see if there's a way to type a number between 1 and 4 into an entry box, then go to the next entry box (with the number entered into the box; the code below skips to the next entry without entering anything)
I'm creating a program that will take item-level data entry to be computed into different subscales. I have that part working in different code, but would prefer not to have to hit tab in between each text entry box since there will be a lot of them.
Basic code:
from tkinter import *
master = Tk()
root_menu = Menu(master)
master.config(menu = root_menu)
def nextentrybox(event):
event.widget.tk_focusNext().focus()
return('break')
Label(master, text='Q1',font=("Arial",8)).grid(row=0,column=0,sticky=E)
Q1=Entry(master, textvariable=StringVar)
Q1.grid(row=0,column=1)
Q1.bind('1',nextentrybox)
Q1.bind('2',nextentrybox)
Q1.bind('3',nextentrybox)
Q1.bind('4',nextentrybox)
Label(master, text='Q2',font=("Arial",8)).grid(row=1,column=0,sticky=E)
Q2=Entry(master, textvariable=StringVar)
Q2.grid(row=1,column=1)
Q2.bind('1',nextentrybox)
Q2.bind('2',nextentrybox)
Q2.bind('3',nextentrybox)
Q2.bind('4',nextentrybox)
### etc for rest of questions
### Scale sums, t-score lookups, and report generator to go here
file_menu = Menu(root_menu)
root_menu.add_cascade(label = "File", menu = file_menu)
file_menu.add_separator()
file_menu.add_command(label = "Quit", command = master.destroy)
mainloop()
Thanks for any help or pointers!
The simplest solution is to enter the event keysym before proceeding to the next field.
In the following example, notice how I added a call to event.widget.insert before moving the focus:
def nextentrybox(event):
event.widget.insert("end", event.keysym)
event.widget.tk_focusNext().focus()
return('break')

Changing entry text in tkinter, if the entry is created from list

I created 12 Entry boxes using a for loop with a default value of N/A. Any change in the text of entry is detected through .trace method.
I want to use reset button to make all the text on the Entry Boxes back to N/A
from tkinter import *
root = Tk()
t_diesel_price_EnF_variable = ["JanVar", "FebVar", "MarVar", "AprVar","MayVar","JuneVar","JulyVar","AugVar","SeptVar", "OctVar", "NovVar", "DecVar"]
t_diesel_price_EnF_values = ["N/A", "N/A","N/A", "N/A","N/A", "N/A","N/A", "N/A","N/A", "N/A","N/A", "N/A"]
def EnFChanging(*events):
for EnF in range(0,len(t_diesel_price_EnF_variable)):
t_diesel_price_EnF_values[EnF]=t_diesel_price_EnF_variable[EnF].get()
try:
t_diesel_price_EnF_values[EnF] = float(t_diesel_price_EnF_values[EnF])
except ValueError:
pass
print(t_diesel_price_EnF_values)
for EnF in range(0,len(t_diesel_price_EnF_values)):
t_diesel_price_EnF_variable[EnF] = StringVar(root , value = "N/A")
t_diesel_price = Entry(root , textvariable = t_diesel_price_EnF_variable[EnF], width = 10).pack()
t_diesel_price_EnF_variable[EnF].trace("w",EnFChanging)
def ChangeText():
for EnF in range(0, len(t_diesel_price_EnF_values)):
t_diesel_price[EnF].delete(0,END)
t_diesel_price[EnF].insert(0,"N/A")
return
b1 = Button(root, text = "Reset" , command = ChangeText).pack()
root.mainloop()
When I press the button it gives an error t_diesel_price[EnF].delete(0,END)
TypeError: 'NoneType' object is not subscriptable
What should I do now, Please ignore the basic errors of programming as I am a Mechanical Engineer with not a programming back ground. And I have to make a lot of other boxes too for my energy calculator.
You trying treat t_diesel_price as an Entry (and as a list) when your variable is None.
First of all I suggest you to install some IDE (e.g. PyCharm) and place break points to see whats wrong with variable!
Your problem occures because you create and pack your widget in one line! So t_diesel_price is None because pack() always returns None (link).
Just split your declaration and packing to:
t_diesel_price = Entry(root , textvariable = t_diesel_price_EnF_variable[EnF], width = 10)
t_diesel_price.pack()
After that it's works for me, except this fact that t_diesel_price is last created entry and the value changes only in it. So I assume that you need another list to iterate over entries:
...
# another fresh list
t_diesel_price_EnF_entries = list()
...
# declare entry
t_diesel_price = Entry(root, textvariable=t_diesel_price_EnF_variable[EnF], width=10)
# pack entry
t_diesel_price.pack()
# append entry to list
t_diesel_price_EnF_entries.append(t_diesel_price)
...
def ChangeText():
# iterate over entries
for diesel_price in t_diesel_price_EnF_entries:
diesel_price.delete(0,END)
diesel_price.insert(0,"N/A")
...
Alternatively you can iterate over StringVar's if you don't wanna store your entries at all:
def ChangeText():
# iterate over stringvars
for EnF in range(len(t_diesel_price_EnF_variable)):
t_diesel_price_EnF_variable[EnF].set('N/A')
And you can make it more readable as in example with entry iterating:
def ChangeText():
# iterate over stringvars
for string_var in t_diesel_price_EnF_variable:
string_var.set('N/A')
Cheers!

Categories

Resources