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")
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!
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()
I've been creating an app where there are Clients that I can add to a table, the problem is, I need a scrollbar to scroll through all the clients since the app Height is limited and the clients aren't.
Using tkinter I found a way to create a "table" using Entry and grid, but what if I want to create 100 rows? they would be outside of the view, so that's why the need of a scrollbar.
For those of you who know Java, I wanted to create something similar to Jtable, it has a method to create row, delete row, and it generates automatically that scrollbar as soon as the JTable runs out of space.
I've tried to use TkTable from ttk and mess around with some properties, but I preferred how Entries look.
root = Tk()
root.geometry("1200x900")
for i in range(10):
e = Entry(relief=RIDGE)
e.grid(row=i, column=2, sticky=N)
root.mainloop()
I created a root = Tk() and used root to grid them.
You'll see 10 Entries on top of the other.
When a window contains many widgets, they might not all be visible. However, neither a window (Tk or Toplevel instance) nor a Entry are scrollable.
One solution to make the window content scrollable is to put all the widgets in a Frame, and then, embed this Frame in a Canvas using the create_window method.
from tkinter import *
root = Tk()
canvas = Canvas(root)
scroll_y = Scrollbar(root, orient="vertical", command=canvas.yview)
frame = Frame(canvas)
# group of widgets
for i in range(100):
e = Entry(frame, relief=RIDGE, width = 100)
e.grid(row=i, column=2, sticky=N)
# put the frame in the canvas
canvas.create_window(0, 0, anchor='nw', window=frame)
# make sure everything is displayed before configuring the scrollregion
canvas.update_idletasks()
canvas.configure(scrollregion=canvas.bbox('all'),
yscrollcommand=scroll_y.set)
canvas.pack(fill='both', expand=True, side='left')
scroll_y.pack(fill='y', side='right')
root.mainloop()
output:
I'm just wondering how I can deselect from a list box in thinter. Whenever I click on something in a list box, it gets highlighted and it gets underlined, but when I click off of the screen towards the side, the list box selection stays highlighted. Even when I click a button, the selection still stays underlined. For ex: in the example code below, I can't click off of the list box selection after clicking on one of them.
from tkinter import *
def Add():
listbox.insert(END, textVar.get())
root = Tk()
textVar = StringVar()
entry = Entry(root, textvariable = textVar)
add = Button(root, text="add", command = Add)
frame = Frame(root, height=100, width=100, bg="blue")
listbox = Listbox(root, height=5)
add.grid(row=0, column=0, sticky=W)
entry.grid(row=0, column=1, sticky=E)
listbox.grid(row=1, column=0)
frame.grid(row=1, column=1)
root.mainloop()
Yes, that's the normal behavior of the listbox. If you want to change that you could call the clear function every time the listbox left focus:
listbox.bind('<FocusOut>', lambda e: listbox.selection_clear(0, END))
Use the selectmode parameter on the Listbox widget.
You can click the selected item again and it will clear the selection.
See the effbot link:
http://effbot.org/tkinterbook/listbox.htm
listbox = Listbox(root, height=5, selectmode=MULTIPLE)
I have managed to create the functionality needed within the Listbox widget so that when a user clicks either back on the same item in the Listbox or elsewhere on screen the currently selected item is deselected. The solution came out to be quite simple.
Firsly I created a binding so that when the left mouse button is pressed anywhere on the window a function to deselect the list box is executed.
root.bind('<ButtonPress-1>', deselect_item)
I then created a variable to store the value of the last listbox item to be selected and initialised its value to None
previous_selected = None
Then I defined the function to deselect the listbox as follows. Firsly the new item (what item the user has just clicked on) is selected and compared to the previously selected item. If this is true then the user has clicked on an already highlighted item in the listbox and so the listbox's selection is cleared, removing the selected item. Finally, the function updates the previously selected box to the current selected box.
def deselect_item(event):
if listbox.curselection() == previous_selected:
listbox.selection_clear(0, tkinter.END)
previous_selected = listbox.curselection()
A full working example of this (in python 3.8.0) is shown below:
import tkinter
class App(tkinter.Tk):
def __init__(self):
tkinter.Tk.__init__(self)
self.previous_selected = None
self.listbox = tkinter.Listbox(self)
self.bind('<ButtonPress-1>', self.deselect_item)
self.listbox.insert(tkinter.END, 'Apple')
self.listbox.insert(tkinter.END, 'Orange')
self.listbox.insert(tkinter.END, 'Pear')
self.listbox.pack()
def deselect_item(self, event):
if self.listbox.curselection() == self.previous_selected:
self.listbox.selection_clear(0, tkinter.END)
self.previous_selected = self.listbox.curselection()
app = App()
app.mainloop()
I am trying to add an entry from a toplevel window into a listbox in the main window.
So far I have managed to create a button that opens a new window containing 4 entry widgets(name, address, phone number and DOB). Is there any way, after I press the OK button on the pop up window, that all four entries are added to the listbox on the main window?
Thanks.
Unless I'm missing something in your problem description, the OK button command just needs to copy the values from the Entry fields to the Listbox. Was there more to it than that?
from tkinter import Tk, Frame, Label, Entry, Button, Listbox
def ok_button():
li.delete(0, "end")
for i in range(len(fields)):
li.insert("end", e[i].get())
root = Tk()
root.title("Listbox")
cf = Frame(root)
cf.pack()
fields = ("Name", "Address", "Phone", "DOB")
e = []
for f in fields:
i = len(e)
Label(cf, text=f).grid(column=2, row=i, sticky="e")
e.append(Entry(cf, width=16))
e[i].grid(column=4, row=i)
Button(cf, text="OK", command=ok_button).grid(column=2, row=10, columnspan=3)
li = Listbox(cf)
li.grid(column=2, row=8, columnspan=3)
root.mainloop()