7
\$\begingroup\$

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()
\$\endgroup\$
5
\$\begingroup\$

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.
\$\endgroup\$
0
1
\$\begingroup\$

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
\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.