I am making a simple shopping list application.
Whenever I try to save the content from the listbox shopping_list it saves as a tuple ('.., .., ...,') in the .txt file the function creates.
When I use the open button, the listbox displays the text as a tuple.
Example:
If in the entry field I write something like pizza and add it to the listbox and save it. When I try to open the same file into the listbox, the listbox now displays: ('pizza'). How can I get the listbox to display only pizza?
The code:
from tkinter import *
from tkinter import messagebox
from tkinter import filedialog
from PIL import ImageTk, Image
def add_item():
'''
This function takes the input from the entry field
and places the item in a shopping list.
:return: Adds to list
'''
if len(user_input.get()) == 0: # Checks if user typed into the entry field.
messagebox.showwarning("Warning", "No keys were detected from the Entry field") # Send warning for no input.
# Else statement for adding the user input to shopping list.
else:
shopping_list.insert(END, user_input.get())
user_input.delete(0, END) # Deletes whatever the user wrote in the entry box after button is clicked.
def remove_item():
'''
This function deletes the selected items when REMOVE button is clicked
'''
try:
selected = shopping_list.curselection()
for item in selected[::-1]: # Fetches all selected items from shopping list
shopping_list.delete(item)
# Make a warning if the shopping list is empty or items are not selected when REMOVE button is clicked.
except TclError:
messagebox.showwarning("Warning", "The shopping list is either empty \nor you didn't select an item")
def open_list():
'''
This function opens an existing shopping list that was previously saved.
:return: Opens older saved files.
'''
root.file = filedialog.askopenfilename(initialdir="./", title="Select a file",
filetypes=(("Txt files", "*.txt"), ("All files", "*,*")))
with open(root.file, "r") as file:
data_content = file.read()
shopping_list.delete(0, END)
shopping_list.insert(END, data_content)
def save_file():
'''
This function saves the whole shopping list and writes the content to the "shopping_list.txt" file.
:return: None
'''
text_file = filedialog.asksaveasfilename(defaultextension=".txt")
try:
with open(text_file, "w") as file:
file.write(str(shopping_list.get(0, END)))
except FileNotFoundError:
messagebox.showinfo("Alert", "No file was saved")
# backup eldre løsning:
"""
def save_file():
'''
This function saves the whole shopping list and writes the content to the "shopping_list.txt" file.
:return: None
'''
root.filename = filedialog.asksaveasfilename()
with open("shopping_list.txt", "w") as f:
for i in shopping_list.get(0, END):
f.write(i+"\n")
"""
# Root files
root = Tk()
root.title("Skoleoppgave OBLIG 5")
root.iconbitmap('favicon.icns')
root.geometry("500x500")
root.resizable(0, 0)
# TODO: Entry widget ENTRY 1
# Creating an Entry widget so user can add items to shopping-list.
user_input = Entry(root, relief=SUNKEN)
user_input.pack()
user_input.place(x=250, y=20, anchor=N)
# Todo: Buttons widget ADD ITEM 2
# Make a Button-widget that adds the user input from entry field to the shopping list.
add_entry = Button(root, text="ADD ITEM", command=add_item, padx=10, pady=5)
add_entry.pack()
add_entry.place(x=158, y=57)
# Todo: Listbox widget 4
# Creating Listbox for shopping list items
shopping_list = Listbox(root, selectmode=EXTENDED)
shopping_list.pack()
shopping_list.place(x=160, y=100)
"""
# Opens last saved shopping list when program starts.
try:
read = open("shopping_list.txt","r")
list_file = read.readlines()
read.close()
for data in list_file:
shopping_list.insert(0, END, data)
except FileNotFoundError:
read = open("shopping_list.txt","w")
"""
# Todo: Buttons widget REMOVE 3
# Make a Button-widget that deletes the selected item from shopping list
remove_entry = Button(root, text="REMOVE", command=remove_item, padx=10, pady=5)
remove_entry.pack()
remove_entry.place(x=160, y=285)
# Todo: Buttons widget OPEN 5
# Make a Button-widget that deletes the selected item from shopping list
open_file = Button(root, text="OPEN", command=open_list, padx=10, pady=5)
open_file.pack()
open_file.place(x=160, y=285)
# Todo: Buttons widget SAVE 6
# Make a Button-widget that deletes the selected item from shopping list
save_file = Button(root, text="SAVE", command=save_file, padx=10, pady=5)
save_file.pack()
save_file.place(x=282, y=57)
root.mainloop()
As mentioned in the comments, you are trying to put a string into a method that is asking for a tuple, that will give an error.
so one way is that you add functionality both in reading and writing to write a data format to the file that you control yourself. tip is trying to replicate one to one what you see in the listbox to the textfile. It looks like your "eldre losning" will put one item per line. So the thing is to read the file so that you get a list of strings where every row becomes an item in the list. Hint... .split on strings works good for that.
Or, just deal with the string you already have in the file.
One way to do that is to add/modify the following.
from ast import literal_eval # added import for safe evals
# modified open
with open(root.file, "rt") as file:
data_content = literal_eval(file.read())
shopping_list.delete(0, END)
shopping_list.insert(END, *data_content)
Side note #1, you don't need .pack() if you use .place.
Side note #2, next time around, mention that you are doing homework upfront.
Side note #3, if you use what's in this answer - you might have to do some reading before you can explain why it could be done like this to be believed that you came up with it yourself :-)
Good luck with your studies and learning.
Related
I have been trying to use tkinter CheckButton widget to edit items in a list - each item of the list is a new checkbutton. I want a save method to save the data to a text file, and a load method to load the info from the text file and mark the checkbutton boxes depending on the items in the list.
Here is my code so far, but the list doesn't seem to change when I check the buttons and update the list/file
Here is my code, I need to know why the list isn't updating when I check the boxes:
import tkinter.messagebox as box
modulesMain = Tk()
moduleChecks = []
def SaveChanges():
# Clear the text file
modules = open("modules.txt", "w") #Write mode to overwrite the whole file
modules.write("") # Put in blank text
modules.close()
modules = open("modules.txt", "a") # Append mode to append the file
for item in moduleChecks:
modules.write(item + "\n")
print(moduleChecks)
appButton = Checkbutton(modulesMain, text = "Test", variable = moduleChecks[0]).grid()
searchButton = Checkbutton(modulesMain, text = "Test", variable = moduleChecks[1]).grid()
Save = Button(modulesMain, text = "Save Changes", command = SaveChanges).grid()
The variable for each of your checkboxes needs to be an IntVar. Your moduleCheck list is currently un-initialised so when you try to access the elements inside it, you'll get an error.
In the below code (modified from yours), I've initialised the moduleCheck to contain two IntVars.
When you press the save button, it will print to the console the current state of the check boxes.
import tkinter as tk
modulesMain = tk.Tk()
moduleChecks = [tk.IntVar() for i in range(2)]
def SaveChanges():
for idx,item in enumerate(moduleChecks):
print(f"{idx} = {item.get()}")
appCheck = tk.Checkbutton(modulesMain, text = "App", variable = moduleChecks[0])
appCheck.grid()
searchCheck = tk.Checkbutton(modulesMain, text = "Check", variable = moduleChecks[1])
searchCheck.grid()
saveButton = tk.Button(modulesMain, text = "Save Changes", command = SaveChanges)
saveButton.grid()
modulesMain.mainloop()
Here is a basic solution.
import tkinter as tk
root = tk.Tk()
#list of options. doubles as checkbox label text
#appending to this list is all that is necessary to create more options
opts = ['App', 'Search']
#create an StringVar for every option
#this way there doesn't have to be type conversions for reading/writing files
vars = [tk.StringVar(value='0') for _ in opts]
#create checkboxes for every option
for opt, var in zip(opts, vars):
tk.Checkbutton(root, text=opt, variable=var, onvalue='1', offvalue='0').grid(sticky='w')
#write the checkbox values to file
def save_options_state():
with open("modules.txt", "w") as file:
file.write(''.join([v.get() for v in vars]))
#set checkbox values from file
def load_options_state():
with open("modules.txt", "r") as file:
for n, var in zip(list(file.read().strip()), vars):
var.set(n)
#save/load buttons
tk.Button(root, text="save", command=save_options_state).grid()
tk.Button(root, text="load", command=load_options_state).grid(row=2, column=1)
root.mainloop()
Here's a runnable example which I think shows how to do everything you've asked about. It assumes the modules.txt file contains data in the following format, where each line consists of a module's name and whether it's checked or not.
Mod1,0
Mod2,0
Mod3,0
The data in the file is used to create a list of Checkbuttons — one for each module — and initially sets to its current status as indicated in the file.
Here's the sample code:
from tkinter import *
import tkinter.messagebox as box
dataFilename = "modules.txt"
moduleChecks = []
def ReadModules():
with open(dataFilename, "r") as file:
for line in (line.strip() for line in file):
moduleName, moduleState = line.split(',')
moduleChecks.append((moduleName, int(moduleState)))
print('read:', moduleChecks)
def SaveChanges():
print('writing:', moduleChecks)
with open(dataFilename, "w") as file:
for checkButton in moduleCheckbuttons:
moduleName = checkButton.cget('text')
moduleState = checkButton.var.get()
file.write(','.join((moduleName, str(moduleState))) + '\n')
ReadModules()
modulesMain = Tk()
moduleCheckbuttons = []
# Create a Checkbutton for each module based on moduleChecks values.
for moduleName, moduleState in moduleChecks:
intVar = IntVar(value=moduleState)
checkButton = Checkbutton(modulesMain, text=moduleName, variable=intVar)
checkButton.var = intVar # Attach reference to variable.
checkButton.grid()
moduleCheckbuttons.append(checkButton)
saveButton = Button(modulesMain, text="Save Changes", command=SaveChanges)
saveButton.grid()
modulesMain.mainloop()
I'm trying to see if there's a way to type a number between 1 and 4 into an entry box, then go to the next entry box (with the number entered into the box; the code below skips to the next entry without entering anything)
I'm creating a program that will take item-level data entry to be computed into different subscales. I have that part working in different code, but would prefer not to have to hit tab in between each text entry box since there will be a lot of them.
Basic code:
from tkinter import *
master = Tk()
root_menu = Menu(master)
master.config(menu = root_menu)
def nextentrybox(event):
event.widget.tk_focusNext().focus()
return('break')
Label(master, text='Q1',font=("Arial",8)).grid(row=0,column=0,sticky=E)
Q1=Entry(master, textvariable=StringVar)
Q1.grid(row=0,column=1)
Q1.bind('1',nextentrybox)
Q1.bind('2',nextentrybox)
Q1.bind('3',nextentrybox)
Q1.bind('4',nextentrybox)
Label(master, text='Q2',font=("Arial",8)).grid(row=1,column=0,sticky=E)
Q2=Entry(master, textvariable=StringVar)
Q2.grid(row=1,column=1)
Q2.bind('1',nextentrybox)
Q2.bind('2',nextentrybox)
Q2.bind('3',nextentrybox)
Q2.bind('4',nextentrybox)
### etc for rest of questions
### Scale sums, t-score lookups, and report generator to go here
file_menu = Menu(root_menu)
root_menu.add_cascade(label = "File", menu = file_menu)
file_menu.add_separator()
file_menu.add_command(label = "Quit", command = master.destroy)
mainloop()
Thanks for any help or pointers!
The simplest solution is to enter the event keysym before proceeding to the next field.
In the following example, notice how I added a call to event.widget.insert before moving the focus:
def nextentrybox(event):
event.widget.insert("end", event.keysym)
event.widget.tk_focusNext().focus()
return('break')
I created 12 Entry boxes using a for loop with a default value of N/A. Any change in the text of entry is detected through .trace method.
I want to use reset button to make all the text on the Entry Boxes back to N/A
from tkinter import *
root = Tk()
t_diesel_price_EnF_variable = ["JanVar", "FebVar", "MarVar", "AprVar","MayVar","JuneVar","JulyVar","AugVar","SeptVar", "OctVar", "NovVar", "DecVar"]
t_diesel_price_EnF_values = ["N/A", "N/A","N/A", "N/A","N/A", "N/A","N/A", "N/A","N/A", "N/A","N/A", "N/A"]
def EnFChanging(*events):
for EnF in range(0,len(t_diesel_price_EnF_variable)):
t_diesel_price_EnF_values[EnF]=t_diesel_price_EnF_variable[EnF].get()
try:
t_diesel_price_EnF_values[EnF] = float(t_diesel_price_EnF_values[EnF])
except ValueError:
pass
print(t_diesel_price_EnF_values)
for EnF in range(0,len(t_diesel_price_EnF_values)):
t_diesel_price_EnF_variable[EnF] = StringVar(root , value = "N/A")
t_diesel_price = Entry(root , textvariable = t_diesel_price_EnF_variable[EnF], width = 10).pack()
t_diesel_price_EnF_variable[EnF].trace("w",EnFChanging)
def ChangeText():
for EnF in range(0, len(t_diesel_price_EnF_values)):
t_diesel_price[EnF].delete(0,END)
t_diesel_price[EnF].insert(0,"N/A")
return
b1 = Button(root, text = "Reset" , command = ChangeText).pack()
root.mainloop()
When I press the button it gives an error t_diesel_price[EnF].delete(0,END)
TypeError: 'NoneType' object is not subscriptable
What should I do now, Please ignore the basic errors of programming as I am a Mechanical Engineer with not a programming back ground. And I have to make a lot of other boxes too for my energy calculator.
You trying treat t_diesel_price as an Entry (and as a list) when your variable is None.
First of all I suggest you to install some IDE (e.g. PyCharm) and place break points to see whats wrong with variable!
Your problem occures because you create and pack your widget in one line! So t_diesel_price is None because pack() always returns None (link).
Just split your declaration and packing to:
t_diesel_price = Entry(root , textvariable = t_diesel_price_EnF_variable[EnF], width = 10)
t_diesel_price.pack()
After that it's works for me, except this fact that t_diesel_price is last created entry and the value changes only in it. So I assume that you need another list to iterate over entries:
...
# another fresh list
t_diesel_price_EnF_entries = list()
...
# declare entry
t_diesel_price = Entry(root, textvariable=t_diesel_price_EnF_variable[EnF], width=10)
# pack entry
t_diesel_price.pack()
# append entry to list
t_diesel_price_EnF_entries.append(t_diesel_price)
...
def ChangeText():
# iterate over entries
for diesel_price in t_diesel_price_EnF_entries:
diesel_price.delete(0,END)
diesel_price.insert(0,"N/A")
...
Alternatively you can iterate over StringVar's if you don't wanna store your entries at all:
def ChangeText():
# iterate over stringvars
for EnF in range(len(t_diesel_price_EnF_variable)):
t_diesel_price_EnF_variable[EnF].set('N/A')
And you can make it more readable as in example with entry iterating:
def ChangeText():
# iterate over stringvars
for string_var in t_diesel_price_EnF_variable:
string_var.set('N/A')
Cheers!
This might be a strange question because I am new to Python.
I am trying to create form in Python which data can be entered into boxes and saved, then opened again. I'm currently using Tkinter to create a Gui which has entry boxes and buttons:
import sys
from tkinter import *
def mstore():
pass
return
def msearch():
file_path = filedialog.askopenfilename()
return
mGui=Tk()
mGui.geometry('450x450+200+200')
mGui.title('Form Test')
#Top
mTitle = Label (mGui,text='Heading Text',bg='white').grid(row=1,column=1)
mDetail = Label (mGui,text='Flavour you can see',bg='white').grid(row=2,column=1)
#Entry Boxes
mFName = Label (mGui,text='Barcode',bg='white').grid(row=3,column=1)
mEntryname = Entry().grid(row=3,column=2)
#Buttons
mSave = Button (mGui,text='Save',bg='white', command = mstore).grid(row=4,column=1)
mSearch = Button (mGui,text='Search',bg='white', command = msearch).grid(row=5,column=1)
mGui.mainloop()
The search was going to be used to open up a file which has been saved before and fill in the boxes with that data, however before that I need help saving the data in a way it will be retrievable - All the information I find is about web-forms. I have also tried saving information with SQLite3 but I found that to not be quite what I was looking for.
Any help/guidance will be appreciated.
Thanks,
Hello Gregulimy!
I have simplified your code and made it do what you want it to do. I have left comments explaining what the code does. If you have any questions about what I have done feel free to ask!
from tkinter import *
def mstore(text):
file = open("file.txt", "w") # Create file.txt
file.write(text) # Write contents of mEntryname to file
file.close() # Closes text file
def msearch():
file = filedialog.askopenfilename() # Stores file directory that user chose
open_file = open(file, 'r') # Opens file user chose
print(open_file.read()) # Displays contents in console
open_file.close() # Closes text file
# Window Creation and Settings
window = Tk()
window.geometry('450x500')
window.title('Form Test')
# Create Widgets
mTitle = Label (window,text='Heading Text',bg='white')
mDetail = Label (window,text='Flavour you can see',bg='white')
mFName = Label (window,text='Barcode',bg='white')
mEntryname = Entry(window)
# Runs mstore function when pressed (passing the contents of the entry box)
mSave = Button (window,text='Save',bg='white', command = lambda: mstore(mEntryname.get()))
# Runs msearch function when pressed
mSearch = Button (window,text='Search',bg='white', command = lambda: msearch())
# Render Widgets
mTitle.pack()
mDetail.pack()
mFName.pack()
mEntryname.pack()
mSave.pack()
mSearch.pack()
window.mainloop()
I am creating a news feed program that uses the Feedparser module to read the Yahoo! RSS API, write key data to a text file, and then display the data organised in a Tkinter GUI.
I was enquiring if it is possible to have clickable hyperlinks in a text file/Tkinter message widget.
My current thinking is that you could write code that runs in the following fashion:
If item in the text file includes 'http', make it a hyperlink.
If anyone knows of a Pythonic way to achieve this, or knows if it is not in fact possible, please contribute.
Thank you for your time, here is my code:
def news_feed(event):
''' This function creates a new window within the main window, passes an event(left mouse click), and creates a text heading'''
root = Toplevel(window)
# Create a text heading and define its placement within the grid
menu_heading = Label(root, text = 'News feed', font = 'bold')
menu_heading.grid(row = 0, column = 0, columnspan = 3, pady = 4)
# Create a variable of the selected radio button
button_choice = IntVar()
def selection():
''' This function gets the activated radio button and calls its corresponding function.'''
# Get the value of the activated radio button, and call its corresponding function
news_choice = button_choice.get()
# If the user's choice is industry news, ask them which feed they would like (E.g. Stock market),
if news_choice == 0:
# grab the corresponding url segment to the user's feed choice from the dictionary,
news_choice_url = news_areas[news_feed]
# set the url variable using by inserting this segment into the API url,
rss_url = feedparser.parse('https://au.finance.yahoo.com/news/' + news_choice_url + '/?format=rss')
# and call the feed parsing function.
parse_feed()
# If the user's choice is the second button, call the company news function
elif news_choice == 1:
company_news()
def read_news_file(news_feed_message):
'''This function opens the companyNews text file and reads its contents, line by line'''
with open('C:\\Users\\nicks_000\\PycharmProjects\\untitled\\SAT\\GUI\\Text Files\\companyNews.txt', mode='r') as inFile:
news_data_read = inFile.read()
print('\n')
news_feed_message.configure(text = news_data_read)
def parse_feed(news_feed_message, rss_url):
''' This function parses the Yahoo! RSS API for data of the latest five articles, and writes it to the company news text file'''
# Define the RSS feed to parse from, as the url passed in of the company the user chose
feed = feedparser.parse(rss_url)
try:
# Define the file to write the news data to the company news text file
with open('C:\\Users\\nicks_000\\PycharmProjects\\untitled\\SAT\\GUI\\Text Files\\companyNews.txt', mode='w') as outFile:
# Create a list to store the news data parsed from the Yahoo! RSS
news_data_write = []
# Initialise a count
count = 0
# For the number of articles to append to the file, append the article's title, link, and published date to the news_elements list
for count in range(10):
news_data_write.append(feed['entries'][count].title)
news_data_write.append(feed['entries'][count].published)
article_link = (feed['entries'][count].link)
article_link = article_link.split('*')[1]
news_data_write.append(article_link)
# Add one to the count, so that the next article is parsed
count+=1
# For each item in the news_elements list, convert it to a string and write it to the company news text file
for item in news_data_write:
item = str(item)
outFile.write(item+'\n')
# For each article, write a new line to the company news text file, so that each article's data is on its own line
outFile.write('\n')
# Clear the news_elements list so that data is not written to the file more than once
del(news_data_write[:])
finally:
outFile.close()
read_news_file(news_feed_message)
def industry_news():
''' This function creates a new window within the main window, and displays industry news'''
industry_window = Toplevel(root)
Label(industry_window, text = 'Industry news').grid()
def company_news():
''' This function creates a new window within the main window, and displays company news'''
company_window = Toplevel(root)
company_label = Label(company_window, text = 'Company news')
company_label.grid(row = 0, column = 0, columnspan = 6)
def company_news_handling(company_ticker):
''' This function gets the input from the entry widget (stock ticker) to be graphed.'''
# set the url variable by inserting the stock ticker into the API url,
rss_url = ('http://finance.yahoo.com/rss/headline?s={0}'.format(company_ticker))
# and call the feed parsing function.
parse_feed(news_feed_message, rss_url)
# Create the entry widget where the user enters a stock ticker, and define its location within the grid
company_ticker_entry = Entry(company_window)
company_ticker_entry.grid(row = 1, column = 0, columnspan = 6, padx = 10)
def entry_handling():
'''This function validates the input of the entry box, and if there is nothing entered, an error is outputted until a value is'''
# Create a variable that equals the input from the entry widget
company_ticker = company_ticker_entry.get()
# Convert the input into a string
company_ticker = str(company_ticker)
if company_ticker == '':
news_feed_message.configure(text = 'Please input a stock ticker in the entry box.')
else:
company_news_handling(company_ticker)
# Create the button that the user presses when they wish to graph the data of the stock ticker they inputted in the entry widget
graph_button = Button(company_window, text = 'SHOW', command = entry_handling, width = 10).grid(row = 2, column = 0, columnspan = 6)
news_feed_message = Message(company_window, text='', width=500, borderwidth=5, justify=LEFT, relief=RAISED)
news_feed_message.grid(row=3, column=0, columnspan=6)
Most uses of hyperlinks in a tkinter application i have seen involved using the webbrowser and attaching events to your tkinter object to trigger callbacks, but there may be simpler ways, but heres what i mean :
from tkinter import *
import webbrowser
def callback(event):
webbrowser.open_new(r"http://www.google.com")
root = Tk()
link = Label(root, text="Google Hyperlink", fg="blue", cursor="hand2")
link.pack()
link.bind("<Button-1>", callback)
root.mainloop()
From this source
You could do as you said and read from a text file, and if the line contains "http" create a new label, and event, attaching the hyper link from the file to the event.
import re
with open(fname) as f:
content = f.readlines()
urls = re.findall('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_#.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', content)
Access the url's after this and generate your label's or whatever widget you attach the url's too and you can then have all of them open the web page when clicked.
Hope this helps in some way, let me know if you need more help :)
I think it is easy to create hyperlink in tkinter using following link and its easy for modifying as per your requirement
Updated Hyperlink in tkinter
hope this works for you.
regards Midhun
(Answer taken from effbot)
Support module for Text hyperlinks (File: tkHyperlinkManager.py)
from Tkinter import *
class HyperlinkManager:
def __init__(self, text):
self.text = text
self.text.tag_config("hyper", foreground="blue", underline=1)
self.text.tag_bind("hyper", "<Enter>", self._enter)
self.text.tag_bind("hyper", "<Leave>", self._leave)
self.text.tag_bind("hyper", "<Button-1>", self._click)
self.reset()
def reset(self):
self.links = {}
def add(self, action):
# add an action to the manager. returns tags to use in
# associated text widget
tag = "hyper-%d" % len(self.links)
self.links[tag] = action
return "hyper", tag
def _enter(self, event):
self.text.config(cursor="hand2")
def _leave(self, event):
self.text.config(cursor="")
def _click(self, event):
for tag in self.text.tag_names(CURRENT):
if tag[:6] == "hyper-":
self.links[tag]()
return
And here’s an example:
# File: hyperlink-1.py
import tkHyperlinkManager
from Tkinter import *
root = Tk()
root.title("hyperlink-1")
text = Text(root)
text.pack()
hyperlink = tkHyperlinkManager.HyperlinkManager(text)
def click1():
print "click 1"
text.insert(INSERT, "this is a ")
text.insert(INSERT, "link", hyperlink.add(click1))
text.insert(INSERT, "\n\n")
def click2():
print "click 2"
text.insert(INSERT, "this is another ")
text.insert(INSERT, "link", hyperlink.add(click2))
text.insert(INSERT, "\n\n")
mainloop()