I am new to python and very new to GUI tkinter. I am working on a project for work that is a simple task, but I wanted to try to make it work using a GUI. The task is to scan three barcodes, parse the last nine digits of the barcodes, and compare. If they all match, then display "pass"; if they don't display, "fail". I have written the code using the basic command line and I was able to get everything to work. Now I am trying to implement that code with a graphical user interface, which is where I am struggling.
The issue I am running into is knowing how or what to update/refresh. My logic works as long as I have a string entered in the ENTRY text box before the code is executed. If the strings are different, it fails; if they are the same or empty, it passes.
I wanted to be able to scan three serial numbers and have the bottom frame update pass or fail and then scan a different set of serial numbers and get another result. See my code below. Any help will be greatly appreciated. Thanks everyone!
from tkinter import *
# FUNCTION THAT ALLOWS THE CURSOR TO MOVE TO THE NEXT ENTRY TEXT BOX AUTOMATICALLY.
def go_to_next_entry(event, entry_list, this_index):
next_index = (this_index + 1) % len(entry_list)
entry_list[next_index].focus_set()
# FUNCTION THAT DETERMINES IF THE THREE SERIAL NUMBERS ARE THE SAME OR DIFFERENT.
# DISPLAYS PASS OR FAIL BASED ON RESULT OF CONDITION.
def get_results():
# DECLARING AND INITIALISING VARIABLES THAT ARE EQUAL TO INPUT FROM ENTRY TEXT BOXES.
scan_one = scan_one_entry.get()
scan_two = scan_two_entry.get()
scan_three = scan_three_entry.get()
# PARSING THE LAST NINE DIGITS OF THE ENTERED STRING.
parsed_scan_one = scan_one[-9:]
parsed_scan_two = scan_two[-9:]
parsed_scan_three = scan_three[-9:]
# IF-ELSE CONDITION THAT DISPLAYS PASS IF THREE SERIAL NUMBERS ARE THE SAME AND FAIL IF THEY
ARE NOT THE SAME.
if parsed_scan_one == parsed_scan_two and parsed_scan_two == parsed_scan_three and
parsed_scan_three == parsed_scan_one:
# DELETING DATA THAT IS STORED IN ENTRY TEXT BOXES.
scan_one_entry.delete(0, END)
scan_two_entry.delete(0, END)
scan_three_entry.delete(0, END)
# PLACING THE PASS BOTTOM FRAME IF CONDITION IS MET.
pass_bottom_frame.grid(row=1, sticky="ns, ew")
# CREATING PASS BOTTOM FRAME WIDGETS OF IF CONDITION IS MET.
pass_label = Label(pass_bottom_frame,
text='PASSED SCAN CHECK!',
font=('Helvetica', 100),
justify="center")
# PICKING BACKGROUND COLOR AND FONT COLOR OF LABEL WIDGET
pass_label.config(bg="#63d464", fg="#000000")
# PLACING THE PASS BOTTOM FRAME WIDGET IF CONDITION IS MET
pass_label.place(relx=.5, rely=.5, anchor="center")
else:
# DELETING DATA THAT IS STORED IN ENTRY TEXT BOXES.
scan_one_entry.delete(0, END)
scan_two_entry.delete(0, END)
scan_three_entry.delete(0, END)
# PLACING THE FAILED BOTTOM FRAME.
fail_bottom_frame.grid(row=1, sticky="ns, ew")
# CREATING PASS BOTTOM FRAME WIDGETS.
fail_label = Label(fail_bottom_frame,
text='FAILED SCAN CHECK!',
font=('Helvetica', 100),
justify="center")
# PICKING BACKGROUND COLOR AND FONT COLOR OF LABEL WIDGET.
fail_label.config(bg="#f51023", fg="#000000")
# PLACING THE FAILED BOTTOM FRAME WIDGET.
fail_label.place(relx=.5, rely=.5, anchor="center")
# CREATING MAIN WINDOW
main_window = Tk()
main_window.title('Serial Number Barcode Scan Check')
main_window.state("zoomed")
# CREATING THE MAIN FRAMES THAT WILL BE PLACED IN MAIN WINDOW
top_frame = Frame(main_window, width=1800, height=500)
pass_bottom_frame = Frame(main_window, bg="#63d464", width=1800, height=500)
fail_bottom_frame = Frame(main_window, bg="#f51023", width=1800, height=500)
# LAYOUT MAIN TOP FRAME
main_window.grid_rowconfigure(1, weight=1)
main_window .grid_columnconfigure(0, weight=1)
# PLACING TOP FRAME
top_frame.grid(row=0, sticky="ns, ew")
# CREATING TOP FRAME WIDGETS
# TOP THREE ARE LABELS AND THE LAST THREE ARE ENTRY BOXES
scan_one_label = Label(top_frame,
text='ENTER SCAN ONE: ',
font=('Helvetica', 40))
scan_two_label = Label(top_frame,
text='ENTER SCAN TWO: ',
font=('Helvetica', 40))
scan_three_label = Label(top_frame,
text='ENTER SCAN THREE: ',
font=('Helvetica', 40))
scan_one_entry = Entry(top_frame, font="Helvetica 20", justify="center", border=5)
scan_two_entry = Entry(top_frame, font="Helvetica 20", justify="center", border=5)
scan_three_entry = Entry(top_frame, font="Helvetica 20", justify="center", border=5)
# PLACING THE TOP FRAME WIDGETS INTO THE TOP FRAME
scan_one_label.grid(row=0, column=0, sticky='w')
scan_two_label.grid(row=1, column=0, sticky='w')
scan_three_label.grid(row=2, column=0, sticky='w')
scan_one_entry.grid(row=0, column=1, ipadx=100, ipady=8)
scan_two_entry.grid(row=1, column=1, ipadx=100, ipady=8)
scan_three_entry.grid(row=2, column=1, ipadx=100, ipady=8)
# CODE USED TO MOVE TO EACH ENTRY TEXT BOX AUTOMATICALLY
entries = [child for child in top_frame.winfo_children() if isinstance(child, Entry)]
for index, entry in enumerate(entries):
entry.bind('<Return>', lambda e, index=index: go_to_next_entry(e, entries, index))
# CALLING THE FUNCTION GET RESULTS
get_results()
# MAIN LOOP
main_window.mainloop()
Related
I'm trying to create a GUI using Tkinter for a Pip-boy from Fallout 3. I'm running into a problem where after I click a button in one of the frames, the spacing between the buttons gets messed up. This spacing change happens for all but one of the buttons in the frame (the Lockpick one).
This is what I want the button spacing to look like (what it looks like before I click a button):
This is what happens after I click a button (in this case the Barter one)
Here is the code I am using:
from tkinter import *
# to read descriptions of each skill from a text file
with open("skills.txt") as f:
lines = f.readlines()
# function that updates the label with a different description when the corresponding button is clicked
def show_skill_desc(index):
desc['text'] = lines[index-1]
# makes the window and frame
root = Tk()
root.geometry("1024x600")
root.title("Skills")
frame = Frame(root)
frame.grid()
# creates the label
Label(root, text="Stats").grid(row=0, column=0)
# list of skills which will each have their separate labels
skills_list = ["Barter", "Big Guns", "Energy Weapons", "Explosives", "Lockpick", "Medicine",
"Melee Weapons", "Repair", "Science", "Small Guns", "Sneak", "Speech", "Unarmed"]
# placing the label in the frame
desc = Label(root, text="", padx=30, wraplength=600, justify=LEFT)
desc.grid(row=2, column=1)
# creates a button for each skill
button_list = []
for i in range(12):
button_list.append(Button(root, text=skills_list[i], width=40,
height=2, command=lambda c=i: show_skill_desc(button_list[c].grid_info()['row']), padx=0, pady=0))
button_list[i].grid(row=i+1, column=0)
root.mainloop()
The purpose of the GUI is to display the description of each skill, when the button for a skill is clicked.
Does anyone know why the spacing change happens? Thank you!
My buttons are shown in the order of "previous image" and "next image" on the left side and "exit" and "next set" on the right side. I would like to have the next set button still be on the right side overall but be to the left of the exit button. So I would like the "next set" and "exit" buttons to switch spots.
I was also wondering if it was possible to show the exit button on the bottom right of the screen. The second image I provided shows what happens whenever I use .pack(side=tk.BOTTOM), as the exit button doesn't really go underneath the whole image.
fram = tk.Frame(self)
tk.Button(fram, text="Previous Image", bg="green", command=self.prev).pack(side=tk.LEFT)
tk.Button(fram, text=" Next Image ", command=self.next).pack(side=tk.LEFT)
tk.Button(fram, text=" Next set ", command=self._load_dataset).pack(side=tk.RIGHT)
tk.Button(fram, text=" Exit ", command=self.destroy, fg = "grey").pack(side=tk.RIGHT)
tk.Label(fram, textvariable=self.clickStatus, font='Helvetica 18 bold').pack(side=tk.RIGHT)
# inside or outside
fram.pack(side=tk.TOP, fill=tk.BOTH)
enter image description here
So I would like the "next set" and "exit" buttons to switch spots.
The packer works by placing widgets along one of the sides of the unallocated space in the master widget. Order matters, since each widget causes the unallocated space to change. For example, if you want a widget on the far right side of a frame, you should pack it on the right before you pack any other widgets.
In your case, the solution is as simple as changing the order in which you call pack.
tk.Button(fram, text=" Exit ", command=self.destroy, fg = "grey").pack(side=tk.RIGHT)
tk.Button(fram, text=" Next set ", command=self._load_dataset).pack(side=tk.RIGHT)
I was also wondering if it was possible to show the exit button on the bottom right of the screen.
Yes, it's possible. The simplest solution is to divide your window into three areas: a top set of buttons, a buttom set of buttons, and the image area.
For example, the following code creates three frames for these three areas, and uses pack to arrange them. Once you do these, it becomes trivial to add buttons to the top and bottom frames in whatever order you want.
top_frame = tk.Frame(self)
bottom_frame = tk.Frame(self)
image_frame = tk.Frame(self)
top_frame.pack(side="top", fill="x")
bottom_frame.pack(side="bottom", fill="x")
image_frame.pack(side="top", fill="both", expand=True)
exit_button = tk.Button(bottom_frame, text="Exit", ...)
exit_button.pack(side="right")
previous_button = tk.Button(top_frame, text="Previous Image", ...)
next_button = tk.Button(top_frame, text="Next Image", ...)
next_set_button = tk.Button(top_frame, text="Next Set", ...)
previous_button.pack(side="left")
next_button.pack(side="left")
next_set_button.pack(side="right")
Another way to do this is to make all of the buttons a child of self which makes it easy to define them all in the same block of code, and use the in_ parameter to specify where they go.
exit_button = tk.Button(self, text="Exit", ...)
previous_button = tk.Button(self, text="Previous Image", ...)
next_button = tk.Button(self, text="Next Image", ...)
next_set_button = tk.Button(self, text="Next Set", ...)
exit_button.pack(in_=bottom_frame, side="right")
previous_button.pack(in_=top_frame, side="left")
next_button.pack(in_=top_frame, side="left")
next_set_button.pack(in_=top_frame, side="right")
For the definitive description of how the packer works, see Packer Algorithm in the official tk documentation.
An alternative to pack is to use the button's grid attributes to place them in the frame.
I have used separate frames for placing buttons in my app:
frame_buttons = ttk.Frame(tab_details)
frame_buttons.grid(column = 0, row = 3, sticky = tk.EW)
frame_buttons.columnconfigure(0, weight = 1)
frame_buttons.columnconfigure(1, weight = 1)
button_process = ttk.Button(frame_buttons, text = 'Process', command = text_to_xml)
button_process.grid(column = 0, row = 0) # in the centre of the left column
button_clear = ttk.Button(frame_buttons,text = 'Clear', command = clear_entries)
button_clear.grid(column = 1, row = 0) # in the centre of the right column
The grid is described here in the TkDocs site:
TkDocs
I've got a text widget, that is filled with some text. I'd like to add a simple bookmark (by Y) without using the text indices (e.g. "50.2"). How can I do it?
I've tried:
from tkinter import *
bookmarks = [] # create a list for bookmarks
def add_bookmark():
bookmark = textbox.yview() # get the vertical position of the view
bookmarks.append(bookmark) # and add it to the bookmarks' list
def goto_bookmark(bookmark):
textbox.yview_moveto(bookmark) # set the vertical positionn of the view
root = Tk() # create a root window
# set the column's and row's weight
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
textbox = Text(root) # create the Text widget
scrollbar = Scrollbar(root, command=textbox.yview) # create the Scrollbar widget, and attach it to the Text widget
textbox["yscrollcommand"] = scrollbar.set # attach the Text widget to the Scrollbar
# show the widgets using grid geometry manager
textbox.grid(row=0, column=0, sticky="nswe")
scrollbar.grid(row=0, column=1, sticky="nswe")
Button(root, text="Add bookmark", command=add_bookmark).grid() # create and show the "Add bookmark" button
Button(root, text="Goto lastbookmark", command=lambda: goto_bookmark(bookmarks[-1])).grid() # create and show the "Goto last bookmark" button
textbox.insert(END, "TEXT\n" *1000) # fill the textbox with something
root.mainloop() # start the mainloop
But I've got this exception when I am trying to go to a bookmark:
_tkinter.TclError: expected floating-point number but got "0.7501873126873126 0.7741633366633367"
Change the button command as following:
Button(root, text="Goto lastbookmark", command=lambda: goto_bookmark(bookmarks[-1][-1])).grid() # create and show the "Goto last bookmark" button
Tkinter's text.yview() method returns a tuple containing the normalized start and end points of the text box. In this instance, you only care about the first element, the top position of the window, so you can pull that element out of the tuple and save that as your bookmark. This fix is as simple as adding [0] to the end of line 8. Here is a working version:
from tkinter import *
bookmarks = [] # create a list for bookmarks
def add_bookmark():
bookmark = textbox.yview()[0] # yview returns the start and end point of the view. We only care about the start
# point so we can pull out the first element in the tuple.
bookmarks.append(bookmark) # and add it to the bookmarks' list
def goto_bookmark(bookmark):
textbox.yview_moveto(bookmark) # set the vertical positionn of the view
root = Tk() # create a root window
# set the column's and row's weight
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
textbox = Text(root) # create the Text widget
scrollbar = Scrollbar(root, command=textbox.yview) # create the Scrollbar widget, and attach it to the Text widget
textbox["yscrollcommand"] = scrollbar.set # attach the Text widget to the Scrollbar
# show the widgets using grid geometry manager
textbox.grid(row=0, column=0, sticky="nswe")
scrollbar.grid(row=0, column=1, sticky="nswe")
Button(root, text="Add bookmark", command=add_bookmark).grid() # create and show the "Add bookmark" button
Button(root, text="Goto lastbookmark", command=lambda: goto_bookmark(bookmarks[-1])).grid() # create and show the "Goto last bookmark" button
textbox.insert(END, "TEXT\n" *1000) # fill the textbox with something
root.mainloop() # start the mainloop
The text widget supports a feature called "marks". Think of them like named indexes.
You can set a mark with the mark_set method. For example, to set a mark at the start of line 5 you would do it like this:
textbox.mark_set("bookmark1", "5.0")
Later you can jump to that bookmark with the see method, which scrolls an index into view.
textbox.see("bookmark1")
When I run my code, my grid of entry fields does not fit within the window, meaning I have to expand the window in order to access the lower entry fields and the buttons. I want to be able to get to them by scrolling instead.
I have tried various combinations of frames and canvasses, including putting the entry fields directly on the canvas, but at no point have I been able to create a canvas the size of the window(and therefore smaller than the grid of entries contained within it).
def __init__(self, window):
# parameter entry fields below
column_headers = ["Duration (ns)", "SPDT state", "SP4T state", "RF relative wave"]
row_number=50
self.entries = []
canvas = tk.Canvas(window, width=700, height=600)
frame=tk.Frame(canvas)
frame.grid(row=0, column=0)
canvas.configure(scrollregion=frame.bbox("all"))
for col_num, col_name in enumerate(column_headers):
tk.Label(frame, text = col_name).grid(row = 0, column = col_num)
for row_num in range(row_number): # Creates grid of entry fields and stores locations in a list of lists.
self.entries.append([]) # Entry field contents accessed by self.entries[row_num][col_num].get() (both starting at 0)
for col_num, col_name in enumerate(column_headers):
self.entries[row_num].append(tk.StringVar())
self.entries[row_num][col_num] = tk.Entry(frame)
self.entries[row_num][col_num].grid(row = row_num+1, column = col_num)
tk.Button(frame, text = "Update Parameters", command=self.get_params).grid(row = row_number+4)
tk.Button(frame, text = "Run Sweep", command= run_expt).grid(row = row_number+4, column = 1)
tk.Button(frame, text = "Abort", command = abort).grid(row = row_number+4, column = 2)
# data storage stuff below
tk.Label(frame, text="File Name").grid(sticky='W', row=row_number+3, column=0)
self.fileNameEntry = tk.StringVar()
self.fileNameEntry = tk.Entry(frame)
self.fileNameEntry.grid(row=row_number+3, column=1)
vbar = tk.Scrollbar(window, orient=tk.VERTICAL, command=canvas.yview)
canvas.configure(yscrollcommand=vbar.set)
vbar.pack(side=tk.RIGHT, fill=tk.Y)
canvas.pack(fill=tk.BOTH)
window=tk.Tk()
window.geometry("700x600")
EPR=EPRGUI(window)
window.mainloop()
Just so no one suggests this, I'd like to point out that I do have functions for all the buttons in my code, but have omitted them from this question to make it a bit quicker to read.
I have written some code for some buttons. However, I am not sure how to add a specific number of pixels of spacing for each button. So far is the code I have written. However, I have not yet figured out a reliable way to add spacing between the buttons in pixel sizes.
import tkinter as tk
#from tkinter import PhotoImage
def banana():
print ("Sundae")
def tomato():
print ("Ketchup")
def potato():
print ("Potato chips")
root = tk.Tk()
root.geometry("960x600")
f1 = tk.Frame(root, width=70, height=30)
f1.grid(row=3, column=0, sticky="we")
button_qwer = tk.Button(f1, text="Banana", command=banana)
button_asdf = tk.Button(f1, text="Tomato", command=tomato)
button_zxcv = tk.Button(f1, text="Potato", command=potato)
button_qwer.grid(row=0, column=0)
button_asdf.grid(row=0, column=1)
button_zxcv.grid(row=0, column=2)
root.mainloop()
Adding space between widgets depends on how you are putting the widgets in the window. Since you are using grid, one simple solution is to leave empty columns between the buttons, and then give these columns a minsize equal to the space you want.
Example:
f1.grid_columnconfigure((1, 3), minsize=10, weight=0)
button_qwer.grid(row=0, column=0)
button_asdf.grid(row=0, column=2)
button_zxcv.grid(row=0, column=4)
Using a specific number of pixels of spacing between each Buttondoesn't sound to me like such as good idea because it isn't very flexible nor easily portable to devices with different resolutions.
Nevertheless I've figured-out a way of doing it—namely by putting a do-nothing invisible button between of the each real ones. This got somewhat involved, mostly because it requires putting an image on each Button used this way so its width option argument will be interpreted as number of pixels instead of number of characters (here's some documentation describing the various Button widget configuration options).
import tkinter as tk
# Inline XBM format data for a 1x1 pixel image.
BITMAP = """
#define im_width 1
#define im_height 1
static char im_bits[] = {
0x00
};
"""
root = tk.Tk()
root.geometry("960x600")
bitmap = tk.BitmapImage(data=BITMAP, maskdata=BITMAP)
f1 = tk.Frame(root, width=70, height=30)
f1.grid(row=3, column=0, sticky=tk.EW)
def banana():
print ("Sundae")
def tomato():
print ("Ketchup")
def potato():
print ("Potato chips")
def layout_buttons(parent, buttons, spacing):
if buttons:
first, *rest = buttons
first.grid(row=0, column=0) # Position first Button.
for index, button in enumerate(rest, start=1):
col = 2*index
# Dummy widget to separate each button from the one before it.
separator = tk.Button(parent, relief=tk.FLAT, state=tk.ACTIVE,
image=bitmap, borderwidth=0, highlightthickness=0,
width=spacing)
separator.grid(row=0, column=col-1)
button.grid(row=0, column=col)
buttons = (
tk.Button(f1, text="Banana", command=banana),
tk.Button(f1, text="Tomato", command=tomato),
tk.Button(f1, text="Potato", command=potato),
)
layout_buttons(f1, buttons, 30)
root.mainloop()
Result:
Here's a blow-up showing that the spacing is exactly 30 pixels (as counted in my image editor and indicated by the thin horizontal black line between the adjacent edges of the two Buttons).