I've created a Combobox
GUI that allows the user to search through the Combobox
by entering a letter on the keyboard. If user enter letter 'L' it will search through the Combobox
drop-down list for the first occurrence of a word that starts with letter 'L'.
Please let me know if I could have made my findInBox
method better. Do you know of a way to search through the drop-down list while it is open?
import tkinter
tk = tkinter
from tkinter import StringVar, Label, Button
from tkinter import font
from tkinter.ttk import Combobox
class Parking_Gui(tk.Frame):
def __init__(self):
"""Sets up the window and widgets"""
tk.Frame.__init__(self)
self.myfont = font.Font(family="Calibri", size=11, weight="normal")
self.master.geometry("315x125")
self.master.title("MSU PARKING APP")
self.master.rowconfigure(0, weight = 1)
self.master.columnconfigure(0, weight = 1)
self.grid(sticky = 'NW')
# Label for the parking lots
self.LotLabel = tk.Label(self, text = "MSU Parking", font=self.myfont)
self.LotLabel.grid(row = 0, column = 0)
# Combo Box for parking lots
self._ComboValue = tk.StringVar()
self._LotCombo = Combobox(self, textvariable=self._ComboValue,
state='readonly', height = '6',
justify = 'center', font=self.myfont)
# List of parking lots
self._LotCombo['values']=('ARTX', 'BURG', 'CLAY_HALL', 'GLAB',
'HAMH_HUTC', 'HHPA', 'JVIC', 'LIBR','PLSU',
'POST', 'PROF', 'STEC', 'STRO_NORTH',
'STRO_WEST', 'TROP')
self._LotCombo.grid(row = 0, column = 1)
# Button to open parking diagram
self._button = tk.Button(self, text = "Open", font=self.myfont)
self._button.bind('<Button-1>', self._runParkingLot)
self._button.grid(row = 0, column = 2)
# Press enter to open selected parking diagram
self._LotCombo.bind("<Return>", self._runParkingLot)
# Search Combobox with keyboard
self._LotCombo.bind("<Key>", self.findInBox)
self._LotCombo.focus()
def _runParkingLot(self, event):
"""Event handler for the button. Will run the
functinon associated with selected item"""
parkingLot = self._LotCombo.get()
"""The 'globals' keyword, will return a dictionary of every function,
variable, etc. currently defined within the global scope."""
if parkingLot in globals():
func = globals()[parkingLot]
func()
def findInBox(self, event):
"""findInBox method allows user to search through Combobox values
by keyboard press"""
alpha = ('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z')
lot = ('ARTX', 'BURG', 'CLAY_HALL', 'GLAB', 'HAMH_HUTC', 'HHPA',
'JVIC' ,'LIBR', 'PLSU', 'POST', 'PROF', 'STEC',
'STRO_NORTH', 'STRO_WEST', 'TROP')
keypress = event.char
keypress = keypress.upper()
# If key pressed is a letter in alphabet
if keypress in alpha:
# if user press 'A' on keyboard return first item in list
if(keypress == lot[0][0]):
self._LotCombo.current(0)
# Searches list for first occurrence of key entered by user
else:
count = 1
while(keypress != lot[count][0] and count < len(lot)-1 ):
count +=1
self._LotCombo.current(count)
count = 1
def main():
Parking_Gui().mainloop
main()
2 Answers 2
Imports
There is a more idiomatic way of doing
import tkinter
tk = tkinter
it is import tkinter as tk
.
You’re also importing StringVar
, Label
and Button
from tkinter
but still calling them using the tk
namespace. Either remove the import line or remove the tk.
part when creating such objects.
Same for font
, you import it from tkinter
and use it only for font.Font
. Either do from tkinter.font import Font
and call it using only Font
or call it with tk.font.Font
. All in all, it’s more a matter of consistency than saving on typing.
ComboBox management
First of, you should define your values as a constant somewhere. Either at the top-level of the file or as a class attribute. That way you could re-use it in both __init__
and findInBox
without having to define it twice.
Secondly, your alpha
is pretty much string.ascii_uppercase
. No need to redefine it again.
Lastly, you’d be better of using a combination of for
and enumerate
to iterate over your different values. Both because a for loop makes the intent of iteration clearer, and the index management is simpler using enumerate
:
import tkinter as tk
from tk.font import Font
from tk.ttk import Combobox
from string import ascii_uppercase
class Parking_Gui(tk.Frame):
PARKING_LOTS = ('ARTX', 'BURG', 'CLAY_HALL', 'GLAB', 'HAMH_HUTC',
'HHPA', 'JVIC', 'LIBR', 'PLSU', 'POST', 'PROF',
'STEC', 'STRO_NORTH', 'STRO_WEST', 'TROP')
def __init__(self):
...
self._LotCombo['values'] = Parking_Gui.PARKING_LOTS
...
...
def findInBox(self, event):
keypress = event.char.upper()
if keypress in ascii_uppercase:
for index, lot_name in enumerate(Parking_Gui.PARKING_LOTS):
if lot_name[0] >= keypress:
self._LotCombo.current(index)
break
PEP8 & PEP257
Small improvements in readability but:
- class names should be TitleCase not Title_Snake_Case
- functions and variable names should be snake_case and not TitleCase or camelCase. (And constants like
PARKING_LOTS
in UPPER_SNAKE_CASE.) - new lines in docstring should be indented at the same level than the start of the
"""
, not their end. Multi-line docstrings should also have the closing"""
on their own line.
Matthias gave you a good thorough answer, but I have a nitpick about naming and comments. You have this pair of lines:
# If key pressed is a letter in alphabet
if keypress in alpha:
You could turn this into a single readable line with different naming. Python is designed to be a more human readable language so you should take advantage of that. And you are very close, just take the name from your comment:
if keypress in alphabet:
Now you've eliminated the need for the comment at all because the name itself makes it a lot clearer what's going on. Comments should be reserved for code that is too complex or abstract to be easily parseable. Any time you're commenting on something relatively simple, reconsider. Maybe you can rewrite the code, rename some variables or rearrange the syntax into a more readable form.
Also you can still do this easily with Matthias's suggestion about importing from string
as imports can be aliased:
from string import ascii_uppercase as alphabet