I've got an Excel spreadsheet I'm pulling info from. This will probably be changed to a database at some point but for now its fine for testing. It has two columns with data and looks like:
Key | ProgramName | Path
0 | Calculator | C:\Windows\System32\calc.exe
1 | Notepad | C:\Windows\System32\notepad.exe
I've built a pretty basic GUI using Python Tkinter and use the xlrd library to extract data from the spreadsheet into a list that contains dictionary entries.
https://pypi.python.org/pypi/xlrd
This gives me a list with dictionary entries:
[{'Path':'C:\Windows\System32\notepad.exe', 'Program':'Notepad', 'Key':0.0}, {'Path':'C:\Windows\System32\calc.exe', 'Program':'Calculator', 'Key':1.0}]
I can get the radio buttons working decently but I'm having an issue pulling the path data and re-using it, since I will (hopefully) use subprocess.Popen to actually open the program selected.
Heres the code so far:
from Tkinter import *
from xlrd import open_workbook
import os
import subprocess
class App:
def __init__(self, master):
frame = Frame(master)
frame.pack()
bottomframe= Frame(master)
bottomframe.pack(side = BOTTOM)
book = open_workbook('programlist.xls')
sheet = book.sheet_by_index(0)
# read header values into the list
keys = [sheet.cell(0, col_index).value for col_index in xrange(sheet.ncols)]
global dict_list
dict_list = []
for row_index in xrange(1, sheet.nrows):
d = {keys[col_index]: sheet.cell(row_index, col_index).value
for col_index in xrange(sheet.ncols)}
dict_list.append(d)
self.w = StringVar()
self.w.set(dict_list[0]['Key']) # initialize
for eachprogram in dict_list:
self.c = Radiobutton(master, text=eachprogram['Program'], variable=self.w, value=eachprogram['Key'])
self.c.pack(anchor=W)
self.quitButton = Button(
bottomframe, text="QUIT" , fg="red", command=frame.quit
)
self.quitButton.pack(side=LEFT, anchor=S)
self.programRun = Button(bottomframe, text="Run", command=self.programRun)
self.programRun.pack(side=LEFT, anchor=S)
def programRun(self):
???
root = Tk()
app = App(root)
root.mainloop()
root.destroy()
Not sure what I need to do so that when the programRun button is pressed, correct path is pulled so I can put it into a "subprocess.Popen" command.
Do I need to create another variable? Can I pull this info using a dictionary key?
Any suggestions are greatly appreciated.
It's not a hard task to pull path from your example and here're even two options to choose from. And you don't need to create an another variable.
According to Docs:
The variable option must be set to a control variable, either an IntVar or a StringVar. All the radiobuttons in a functional group must share the same control variable.
Set the value option of each radiobutton in the group to a different value. Whenever the user sets a radiobutton, the variable will be set to the value option of that radiobutton, and all the other radiobuttons that share the group will be cleared.
As you see - you don't need to use a StringVar, but DoubleVar since your value parameters (and keys) are floats. Downside here - you need to iterate over list to check each dictionary for Key.
try:
import tkinter as tk
except ImportError:
import Tkinter as tk
class App(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.frame = tk.Frame(self)
self.frame.pack()
self.bottomframe = tk.Frame(self)
self.bottomframe.pack(side=tk.BOTTOM)
# book = open_workbook('programlist.xls')
# sheet = book.sheet_by_index(0)
# read header values into the list
# keys = [sheet.cell(0, col_index).value for col_index in xrange(sheet.ncols)]
self.keys = ['Key', 'ProgramName', 'Path']
self.dict_list = [{'Path': r'C:\Windows\System32\notepad.exe', 'Program': 'Notepad', 'Key': 0.0},
{'Path': r'C:\Windows\System32\calc.exe', 'Program': 'Calculator', 'Key': 1.0}]
# global dict_list
# dict_list = []
# for row_index in xrange(1, sheet.nrows):
# d = {keys[col_index]: sheet.cell(row_index, col_index).value
# for col_index in xrange(sheet.ncols)}
# dict_list.append(d)
self.w = tk.DoubleVar()
self.w.set(self.dict_list[0]['Key']) # initialize
for each_program in self.dict_list:
self.c = tk.Radiobutton(self.master, text=each_program['Program'], variable=self.w, value=each_program['Key'])
self.c.pack(anchor=tk.W)
self.quitButton = tk.Button(
self.bottomframe, text="QUIT", fg="red", command=self.frame.quit
)
self.quitButton.pack(side=tk.LEFT, anchor=tk.S)
self.programRun = tk.Button(self.bottomframe, text="Run", command=self.programRun)
self.programRun.pack(side=tk.LEFT, anchor=tk.S)
def programRun(self):
print('Pulled path: %s' % self.search_list_dict())
def search_list_dict(self):
try:
return [item for item in self.dict_list if item['Key'] == self.w.get()][0]['Path']
except IndexError:
return ''
app = App()
app.mainloop()
As an alternative - you can use a StringVar and Path item as a value parameter! Just change some lines:
try:
import tkinter as tk
except ImportError:
import Tkinter as tk
class App(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
...
self.w = tk.StringVar()
self.w.set(self.dict_list[0]['Path']) # initialize
for each_program in self.dict_list:
self.c = tk.Radiobutton(self.master, text=each_program['Program'], variable=self.w, value=each_program['Path'])
self.c.pack(anchor=tk.W)
...
def programRun(self):
print('Pulled path: %s' % self.w.get())
app = App()
app.mainloop()
Related
I'm trying to avoid to multiply functions in code by using
def Return_Label(self,number)
with a parameter.
Any Idea how to use string in order to define variable name usable to .set value to StringVar()?
Example code below:
import tkinter as tk
from tkinter import *
class WINDOW():
def __init__(self):
self.Settings_Window()
def Settings_Window(self):
self.settings_window = tk.Tk()
self.settings_window.minsize(200,200)
self.entry = Entry(self.settings_window)
self.entry.pack()
self.entry2 = Entry(self.settings_window)
self.entry2.pack()
self.label1input = StringVar()
self.label = Label(self.settings_window,textvariable=self.label1input, bg='yellow')
self.label.pack(expand='yes',fill='x')
self.label2input = StringVar()
self.label2 = Label(self.settings_window, textvariable=self.label2input, bg='yellow')
self.label2.pack(expand='yes', fill='x')
self.button = Button(self.settings_window,text='SETUP1',command=self.Next)
self.button.pack()
self.button2 = Button(self.settings_window,text='SETUP2',command=self.Next2)
self.button2.pack()
self.settings_window.mainloop()
def Next(self):
self.number=1
self.Return_Label(self.number)
def Next2(self):
self.number=2
self.Return_Label(self.number)
def Return_Label(self,number):
self.entry_field_value = self.entry.get()
print(self.entry_field_value)
#self.label1input.set(self.entry_field_value)
setattr(self,'label'+str(number)+'input.set',self.entry_field_value)
window=WINDOW()
I prefer a list approach to managing multiple entry fields and updating values.
By using list you can use the index value to manage the labels as well :D.
See the below example of how you can use list to deal with all the values and updates.
import tkinter as tk
from tkinter import *
class Window(tk.Tk):
def __init__(self):
super().__init__()
self.minsize(200, 200)
self.entry_list = []
self.label_list = []
entry_count = 2
for i in range(entry_count):
self.entry_list.append(Entry(self))
self.entry_list[i].pack()
for i in range(entry_count):
self.label_list.append(Label(self,bg='yellow'))
self.label_list[i].pack(expand='yes', fill='x')
Button(self, text='SETUP', command=self.Return_Label).pack()
def Return_Label(self):
for ndex, lbl in enumerate(self.label_list):
lbl.config(text=self.entry_list[ndex].get())
if __name__ == '__main__':
Window().mainloop()
Create lists of objects rather than individual attributes for each object. For example,
import tkinter as tk
from tkinter import *
class Window:
def __init__(self):
self.settings_window()
def Settings_Window(self):
self.settings_window = tk.Tk()
self.settings_window.minsize(200,200)
self.entries = [
Entry(self.settings_window),
Entry(self.settings_window)
]
for e in self.entries:
e.pack()
self.labelinputs = [
StringVar(),
StringVar()
]
self.labels = [
Label(self.settings_window, textvariable=label, bg='yellow')
for label in self.labelinputs
]
for l in self.labels:
l.pack(expand='yes', fill='x')
self.buttons = [
Button(self.settings_window,text='SETUP1',command=lambda: self.return_label(0))
Button(self.settings_window,text='SETUP2',command=lambda: self.return_label(1))
]
for b in self.buttons:
b.pack()
self.settings_window.mainloop()
def return_label(self,number):
entry_field_value = self.entry.get()
self.labelsinput[number].set(entry_field_value)
window=WINDOW()
Dynamicly computing variable names should be avoided at all costs. They are difficult to do correctly, and it makes your code hard to understand, hard to maintain, and hard to debug.
Instead, store the widgets in a dictionary or list. For example:
def __init___(self):
...
self.vars = {}
...
self.vars[1] = StringVar()
self.vars[2] = StringVar()
...
def Return_Label(self,number):
self.entry_field_value = self.entry.get()
var = self.vars[number]
var.set(self.entry_field_value)
Though, you really don't need to use StringVar at all -- they usually just add extra overhead without providing any extra value. You can save the labels instead of the variables, and call configure on the labels
self.labels[1] = Label(...)
...
self.labels[number].configure(text=self.entry_field_value)
I've cloned a class called ListBoxChoice found on the web found (adding some needed features) below:
from Tkinter import *
class ListBoxChoice(object):
def __init__(self, master=None, title=None, message=None,\
list=[]):
self.master = master
self.value = None
self.list = list[:]
self.modalPane = Toplevel(self.master)
self.modalPane.transient(self.master)
self.modalPane.grab_set()
self.modalPane.bind("<Return>", self._choose)
self.modalPane.bind("<Escape>", self._cancel)
if title:
self.modalPane.title(title)
if message:
Label(self.modalPane, text=message).pack(padx=5, pady=5)
listFrame = Frame(self.modalPane)
listFrame.pack(side=TOP, padx=5, pady=5)
scrollBar = Scrollbar(listFrame)
scrollBar.pack(side=RIGHT, fill=Y)
# get the largest value of the 'list' to set the width
widthOfList = 0
for k in list:
if len(str(k)) > widthOfList:
widthOfList = len(str(k))
# now pad some space to back of the widthOfList
widthOfList = widthOfList + 2
self.listBox = Listbox(listFrame, selectmode=SINGLE,\
width=widthOfList)
self.listBox.pack(side=LEFT, fill=Y)
scrollBar.config(command=self.listBox.yview)
self.listBox.config(yscrollcommand=scrollBar.set)
self.list.sort()
for item in self.list:
self.listBox.insert(END, item)
buttonFrame = Frame(self.modalPane)
buttonFrame.pack(side=BOTTOM)
chooseButton = Button(buttonFrame, text="Choose",\
command=self._choose)
chooseButton.pack()
cancelButton = Button(buttonFrame, text="Cancel",\
command=self._cancel)
cancelButton.pack(side=RIGHT)
def _choose(self, event=None):
try:
firstIndex = self.listBox.curselection()[0]
self.value = self.list[int(firstIndex)]
except IndexError:
self.value = None
self.modalPane.destroy()
def _cancel(self, event=None):
self.modalPane.destroy()
def returnValue(self):
self.master.wait_window(self.modalPane)
return self.value
if __name__ == '__main__':
import random
root = Tk()
returnValue = True
list = [random.randint(1,100) for x in range(50)]
while returnValue:
returnValue = ListBoxChoice(root, "Number Picking",\
"Pick one of these crazy random numbers",\
list).returnValue()
print returnValue
Now this example says to do something like this:
results = ListBoxChoice(root, list=listOfItems).returnValue().
What I'm trying to do is provide a list of values from which the user selects a single value. The window should close before I use the results from the selected value. Here is that code:
from tkinter import Tk, Label
form ListBoxChoice import ListBoxChoice
...
eventList = ["20190120","20190127","20190203"]
root = Tk()
root.withdraw() # This causes the ListBoxChoice object not to appear
selectValue = ListBoxChoice(root, title="Event",\
message="Pick Event", list=eventList).returnValue()
root.wait_window() # Modal Pane/window closes but not the root
print("selectValue:", selectValue)
A root window is placed behind the modalPane (Toplevel). I have to close that window before the calling process continues. So there is a block in place.
I've tried to put a sleep(1.01) command above but had no impact.
How do I get the ListBoxChoice to close once the selection has been made
before my print statement of the selectValue? For it is at that point I want to use the results to plot data.
If I don't use root.wait_winow(), it is only when the plot is closed (end of the process) that the ListBoxChoice box close as well.
Suggestions?
Slightly updated
Here's a version of the ListBoxChoice class which I think works the way you desire. I've updated my previous answer slightly so the class is now defined in a separate module named listboxchoice.py. This didn't change anything I could see when I tested—it other words it still seems to work—but I wanted to more closely simulate the way you said you're using it the comments.
It still uses wait_window() because doing so is required to give tkinter's mandatory event-processing-loop the opportunity to run (since mainloop() isn't called anywhere). There's some good background material in the article Dialog Windows about programming tkiner dialogs you might find useful. The added root.withdraw() call eliminates the issue of not being able to close it because it's not there. This is fine since there's no need to have the empty window being displayed anyway.
test_lbc.py
import random
try:
import Tkinter as tk # Python 2
except ModuleNotFoundError:
import tkinter as tk # Python 3
from listboxchoice import ListBoxChoice
root = tk.Tk()
root.withdraw() # Hide root window.
values = [random.randint(1, 100) for _ in range(50)]
choice = None
while choice is None:
choice = ListBoxChoice(root, "Number Picking",
"Pick one of these crazy random numbers",
values).returnValue()
print('choice: {}'.format(choice))
listboxchoice.py
""" ListBoxChoice widget to display a list of values and allow user to
choose one of them.
"""
try:
import Tkinter as tk # Python 2
except ModuleNotFoundError:
import tkinter as tk # Python 3
class ListBoxChoice(object):
def __init__(self, master=None, title=None, message=None, values=None):
self.master = master
self.value = None
if values is None: # Avoid use of mutable default argument value.
raise RuntimeError('No values argument provided.')
self.values = values[:] # Create copy.
self.modalPane = tk.Toplevel(self.master, takefocus=True)
self.modalPane.bind("<Return>", self._choose)
self.modalPane.bind("<Escape>", self._cancel)
if title:
self.modalPane.title(title)
if message:
tk.Label(self.modalPane, text=message).pack(padx=5, pady=5)
listFrame = tk.Frame(self.modalPane)
listFrame.pack(side=tk.TOP, padx=5, pady=5)
scrollBar = tk.Scrollbar(listFrame)
scrollBar.pack(side=tk.RIGHT, fill=tk.Y)
# Get length the largest value in 'values'.
widthOfList = max(len(str(value)) for value in values)
widthOfList += 2 # Add some padding.
self.listBox = tk.Listbox(listFrame, selectmode=tk.SINGLE, width=widthOfList)
self.listBox.pack(side=tk.LEFT, fill=tk.Y)
scrollBar.config(command=self.listBox.yview)
self.listBox.config(yscrollcommand=scrollBar.set)
self.values.sort()
for item in self.values:
self.listBox.insert(tk.END, item)
buttonFrame = tk.Frame(self.modalPane)
buttonFrame.pack(side=tk.BOTTOM)
chooseButton = tk.Button(buttonFrame, text="Choose", command=self._choose)
chooseButton.pack()
cancelButton = tk.Button(buttonFrame, text="Cancel", command=self._cancel)
cancelButton.pack(side=tk.RIGHT)
def _choose(self, event=None):
try:
firstIndex = self.listBox.curselection()[0]
self.value = self.values[int(firstIndex)]
except IndexError:
self.value = None
self.modalPane.destroy()
def _cancel(self, event=None):
self.modalPane.destroy()
def returnValue(self):
self.master.wait_window(self.modalPane)
return self.value
I just forayed into tkinter for the first time and am running into some issues. I want to display several lists to users, and store their selections for each list in a dictionary (used to filter several columns of a dataframe later). Suppose, for instance, there are two lists: 1) One labeled "Brand", containing 'Brand X' and 'Brand Y' as options, 2) another "Customer Type", containing "New," "Existing," "All."
In sum, when all is said and done, if a user picks "Brand X", "New", and "All", then I'd get a dictionary back of {'Brand':['Brand X'],'Customer Type':['New','All']}. Getting one list is easy... but looping through the lists is presenting problems.
I have the below code so far:
from tkinter import *
from tkinter import ttk
class checkList(Frame):
def __init__(self, options, parent=None):
Frame.__init__(self, parent)
self.makeHeader()
self.options = options
self.pack(expand=YES, fill=BOTH, side=LEFT)
self.makeWidgets(self.options)
self.selections = []
def makeHeader(self):
header = ttk.Label(self,text='Please select options to limit on.')
header.pack(side=TOP)
self.header = header
def makeWidgets(self, options):
for key in self.options.keys():
lbl = ttk.Label(self, text=key)
lbl.pack(after=self.header)
listbox = Listbox(self, selectmode=MULTIPLE, exportselection=0)
listbox.pack(side=LEFT)
for item in self.options[key]:
listbox.insert(END, item)
listbox.bind('<<ListboxSelect>>', self.onselect)
self.listbox = listbox
def onselect(self, event):
selections = self.listbox.curselection()
selections = [int(x) for x in selections]
self.selections = [self.options[x] for x in selections]
if __name__ == '__main__':
options = {'Brand':['Brand','Brand Y'], 'Customer Type': ['All Buyers','New Buyers','Existing Buyers']}
checkList(options).mainloop()
Needless to say, the [self.options[x] for x in selections] works great with just one list, but since I have a dictionary, I really need [self.options[key][x] for x in selections]. However, I can't figure out how to pass the key at any given point in the loop. Is there a way to achieve what I'm trying to do?
The "magic" you're looking for to pass the key is simple because the tkinter objects are extensible. Here's your code working, I believe, the way you want:
from tkinter import *
from tkinter import ttk
class checkList(Frame):
def __init__(self, options, parent=None):
Frame.__init__(self, parent)
self.makeHeader()
self.options = options
self.pack(expand=YES, fill=BOTH, side=LEFT)
self.listboxes = [] # New
self.makeWidgets(self.options)
self.selections = {} # Changed
def makeHeader(self):
header = ttk.Label(self,text='Please select options to limit on.')
header.pack(side=TOP)
self.header = header
def makeWidgets(self, options):
for key in self.options.keys():
lbl = ttk.Label(self, text=key)
lbl.pack(after=self.header)
listbox = Listbox(self, selectmode=MULTIPLE, exportselection=0)
listbox.key = key # here's the magic you were asking about...
listbox.pack(side=LEFT)
self.listboxes.append(listbox) # New
for item in self.options[key]:
listbox.insert(END, item)
listbox.bind('<<ListboxSelect>>', self.onselect)
self.listbox = listbox
def onselect(self, event):
for lb in self.listboxes:
selections = lb.curselection()
selections = [int(x) for x in selections]
self.selections[lb.key] = [self.options[lb.key][x] for x in selections]
print(self.selections)
if __name__ == '__main__': # \/
options = {'Brand':['Brand X','Brand Y'], 'Customer Type': ['All Buyers','New Buyers','Existing Buyers']}
checkList(options).mainloop()
With the code you posted, you only have access to the last ListBox created by makeWidgets in onselect.
With minimal changes:
from tkinter import *
from tkinter import ttk
class checkList(Frame):
def __init__(self, options, parent=None):
Frame.__init__(self, parent)
self.listboxes = []
self.selections = {}
self.makeHeader()
self.options = options
self.pack(expand=YES, fill=BOTH, side=LEFT)
self.makeWidgets(self.options)
def makeHeader(self):
header = ttk.Label(self,text='Please select options to limit on.')
header.pack(side=TOP)
self.header = header
def makeWidgets(self, options):
for key in self.options.keys():
lbl = ttk.Label(self, text=key)
lbl.pack(after=self.header)
listbox = Listbox(self, selectmode=MULTIPLE, exportselection=0)
listbox.pack(side=LEFT)
for item in self.options[key]:
listbox.insert(END, item)
listbox.bind('<<ListboxSelect>>', self.onselect)
self.listboxes.append(listbox)
def onselect(self, event):
for (option, options), listbox in zip(self.options.items(), self.listboxes):
self.selections[option] = [options[x] for x in map(int, listbox.curselection())]
print(self.selections)
if __name__ == '__main__':
options = {'Brand':['Brand','Brand Y'], 'Customer Type': ['All Buyers','New Buyers','Existing Buyers']}
checkList(options).mainloop()
This recreates selections every time either ListBox selection is modified. Alternatively, you could use event to determine which ListBox selection was modified and update the corresponding part of selections. This would require initializing selections, though.
I want to update the name of the child when you press one of the buttons below. With this lambda function, I always get the last name no matter which button is pressed. Is it possible to fix it using lambda functions? As far as I have seen lambda functions work well with define values but I have not seen any examples with variables. How can I fix it? Thanks,
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#------------------------------------------------------------
__title__= 'Control'
__date__ = '10/07/2017'
__version__ = '0.0.1'
from tkinter import *
from tkinter import ttk
class Panel():
def __init__(self, database, name):
self.root = Tk()
self.root. title('Control V' + __version__)
self.root.configure(bg='beige')
self.window = ttk.Frame(self.root, borderwidth=2, relief="raised",
padding=(10,10))
self.window.grid(column=0, row=0)
self.window.option_add("*Font", "Helvetica 12")
# Row 0
self.header = ttk.Label(self.window, text=name, padding=(1,1),
anchor='center')
self.header.grid(row=0, column=0, columnspan=3)
# Row 1
for col, record in enumerate(database, 0):
name = database[col]['Letter']
label = ttk.Button(self.window, text=name,
command=lambda *args: self.get_name(col, database))
label.grid(row=1, column=(col), sticky='ew')
# All set
self.root.mainloop()
def get_name(self, row, database):
name = database[row]['Name']
self.header.configure(text=name)
def main():
database = [
{'Letter': 'A', 'Name': 'Zara'},
{'Letter': 'B', 'Name': 'Ezra'},
{'Letter': 'C', 'Name': 'Amy'},
]
name = database[0]['Name']
my_panel = Panel(database, name)
return 0
if __name__ == '__main__':
main()
If you change get_name to a curried function:
def get_name(row, database):
name = database[row]['Name']
def _get_name():
self.header.configure(text=name)
return _get_name
and replace the command declaration with this returned function:
label = ttk.Button(self.window, text=name,
command=self.get_name(col, database))
the result is as expected.
(What this does is define the return value of the button click when you create the button, rather than trying to pass in a parameter later. Your original code had all the buttons depend on 'col' (the variable). This remains the last thing it was set to (2->Amy), which explains why you were always getting that result.)
I am currently working on an assignment that states that I must build a GUI using Tkinter, that will load strings from a text file and display them in a text box. The instructions also state that classes must be utilized.
Being new to programming, I'm not sure how this all works out. My current text file looks like this:
(Item identity #, Quantity,Item, Location, Color)
(23871243, 20, Remote, California, White)
(94938443, 10, Socks, Canada, Black)
As per the requirements, each line must be an individual object, with
attributes, such as Quantity, Location, etc.
I'm fine with the GUI component, however the main problem I am having is telling Python that each line in the text file is a separate object, with certain attributes.
The 'OpenFile' function is likely where the issue is. As of right now it returns a list of strings, but I would like it to return an object with 5 attributes(as listed above, in the text file).
Any help would be greatly appreciated.
from tkinter import *
from tkinter import ttk
from tkinter import font
from tkinter.filedialog import askopenfile
class Manager:
def __init__(self, root):
#The frame for the GUI itself
mainframe = ttk.Frame(root, relief=SUNKEN, padding="3 10 12 12")
mainframe.grid(column=0, row=0, columnspan=10, rowspan=10, sticky="NW")
button_load= ttk.Button(mainframe, text="Load",command=self.OpenFile)
button_load.grid(row=35, column=17, sticky = "NE", padx=5, pady=10)
global text_identity
text_identity = Text(mainframe, width = 15, height = 2)
text_identity.grid(column=8, row=5, sticky=(N,W))
def OpenFile(self):
listing=[]
name = askopenfile(mode='r',initialdir="D:/Documents",
filetypes =(("Text File", "*.txt"),("All Files","*.*")),
title = "Choose a file.")
with name as rd:
global items
items=rd.readlines()
one=[x.strip('\n') for x in items]
return one
class Items:
identity=''
num=''
name = ''
location = ''
other = ''
def __init__(self,identity,num,name,location,other):
self.identity = identity
self.num = num
self.name = name
self.location = location
self.other = other
def main():
root = Tk()
Manager(root)
root.title("Data Management")
root.mainloop()
if __name__ == main():
main()
First of all, you should create a file named item_descriptions.csv and fill it with the following text:
item_id,quantity,item,location,color
23871243,20,Remote,California,White
94938443,10,Socks,Canada,Black
The first row of any CSV file will need to have a row of identifiers that can be used within Python. Why? Because the following program relies on field names to automatically generate a named tuple:
#! /usr/bin/env python3
import collections
import csv
import pathlib
import tkinter.filedialog
import tkinter.messagebox
import tkinter.scrolledtext
import tkinter.ttk
# Make the constants easy to refer to in the rest of the program.
from tkinter.constants import *
class Manager(tkinter.ttk.Frame):
"""Manager(master=None, **kw) -> Manager instance"""
#classmethod
def main(cls):
"""Create a root window for the Manager and display the widget."""
tkinter.NoDefaultRoot()
root = tkinter.Tk()
root.title('Manager')
root.minsize(680, 420)
frame = cls(root)
frame.grid(sticky=NSEW)
root.grid_columnconfigure(0, weight=1)
root.grid_rowconfigure(0, weight=1)
root.mainloop()
def __init__(self, master=None, **kw):
"""Initialize the Manager instance and its attributes."""
super().__init__(master, **kw)
self.initial_dir = pathlib.Path.home()
self.scrolled_text = tkinter.scrolledtext.ScrolledText(self)
self.load_button = tkinter.ttk.Button(self)
self.size_grip = tkinter.ttk.Sizegrip(self)
self.setup_widgets()
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
def setup_widgets(self):
""" Change options on the widgets so they work properly."""
self.scrolled_text.configure(state=DISABLED, wrap=WORD)
self.load_button.configure(text='Load', command=self.find_csv_file)
# Place widgets where they belong in the frame.
self.scrolled_text.grid(row=0, column=0, columnspan=2, sticky=NSEW)
self.load_button.grid(row=1, column=0, sticky=EW)
self.size_grip.grid(row=1, column=1, sticky=SE)
def find_csv_file(self):
"""Begin the process of loading a CSV file for display."""
source = tkinter.filedialog.askopenfilename(
parent=self,
title='Where is the file you want to open?',
multiple=False,
defaultextension='.csv',
filetypes=(('Spreadsheet', '.csv'), ('All Files', '*')),
initialdir=self.initial_dir
)
if source:
self.initial_dir = pathlib.Path(source).parent
self.show_records(self.load_records(source))
def load_records(self, source):
"""Open the requested file and try to yield out its records."""
with open(source, newline='') as file:
reader = csv.DictReader(file)
try:
Record = collections.namedtuple('Record', reader.fieldnames)
except Exception as error:
tkinter.messagebox.showerror(
'Exception',
f'{type(error).__name__}: {error}',
master=self
)
else:
self.scrolled_text.configure(state=NORMAL)
self.scrolled_text.delete(0.0, END)
yield from (Record(**row) for row in reader)
def show_records(self, iterable):
"""Display each record when able without locking up the GUI."""
try:
record = next(iterable)
except StopIteration:
self.scrolled_text.configure(state=DISABLED)
else:
self.scrolled_text.insert(END, f'{record}\n')
self.after_idle(self.show_records, iterable)
if __name__ == '__main__':
Manager.main()
If you need further help with your program, you may need to ask another question for more answers.