I'm writing a program for my class that allows you to make a recipe, save it and edit it even after closing the program. You obviously need a text file to do this.
I am using an OptionMenu (Tkinter, Python 3.3.3), but I cannot figure out how to keep updating it to have the first option in the list I have made in my text file. So how do I do that?
My code is thus:
###########################################
###########################################
### RECIPE BOOK TASK ##### By 18166 #######
###########################################
###########################################
from tkinter import *
def script ():
#### MAIN ####
fake_window = Tk()
new_recipe_window = fake_window
start_window = fake_window
start_window.title("Recipe Book Task")
#### MAIN ####
## DATA FILE ##
global datafile
datafile = open("StoredRecipes.txt", "a+")
## DATA FILE ##
### Functions ###
def close (x): ## Close Original Window ##
global start_window
global new_recipe_window
(x).withdraw()
def new_recipe ():
new_recipe_window = Tk() ## Making new window ##
new_recipe_window.title("New Recipe")
close(start_window)
recipe_name_label = Label(new_recipe_window, text="Recipe Name: ") ## Making new recipe label ##
recipe_name_label.grid(row=0, column=0)
recipe_name_box = Entry(new_recipe_window) ## Making new recipe entry ##
recipe_name_box.grid(row=0, column=1)
num_people_label = Label(new_recipe_window, text="Number of people: ") ## Making number of people label ##
num_people_label.grid(row=1, column=0)
num_people_box = Entry(new_recipe_window) ## Making number of people entry ##
num_people_box.grid(row=1, column=1)
item_label = Label(new_recipe_window, text="Items: ") ## Making item label ##
item_label.grid(row=2, column=0)
item_box = Entry(new_recipe_window) ## Making item entry ##
item_box.grid(row=2, column=1)
quantity_label = Label(new_recipe_window, text="Quantity: ") ## Making quantity label ##
quantity_label.grid(row=3, column=0)
quantity_box = Entry(new_recipe_window) ## Making quantity entry ##
quantity_box.grid(row=3, column=1)
unit_label = Label(new_recipe_window, text="Unit: ") ## Making unit label ##
unit_label.grid(row=4, column=0)
unit_box = Entry(new_recipe_window) ## Making unit entry ##
unit_box.grid(row=4, column=1)
def write ():
a = recipe_name_box.get()
b = num_people_box.get()
c = item_box.get()
d = quantity_box.get()
e = unit_box.get()
line = (a, b, c, d, e)
datafile.write(str(line) + "\n")
datafile.close()
saved_recipes.config(a)
close(new_recipe_window)
script()
finish_button = Button(new_recipe_window, text="Save and Finish", command=write) ## Making finish button ##
finish_button.grid(row=5, column=0, sticky=S)
# Dropdown Box #
default = StringVar(start_window, 'Recipe 1')
default.set("Select Your Recipe")
saved_recipes = OptionMenu(start_window, default, "Hi")
saved_recipes.grid(row=0, column=1)
# Dropdown Box #
# New Recipe Button #
new_recipe = Button(start_window, text="New Recipe", command=new_recipe)
new_recipe.grid(row=0, column=0)
# New Recipe Button #
script()
(Sorry for the block, I think all is useful to answering possibly?)
I believe you have two different options.
One option you could do is set up a timer to check the text file every couple of seconds, see if it's changed at all, and update your OptionMenu accordingly. You can find more info on how to do this here, but in a nutshell, you'd want your code to look something like:
def recheck(root, option_menu, file_name):
with open(file_name) as my_file:
lines = my_file.readlines():
# `lines` is a list where each item is a single line
# do any checks and updates you need here.
root.after(1000, recheck, root, option_menu, file_name)
# schedule the function to run again after 1000 milliseconds.
def script():
# set up your gui
start_window.after(1000, recheck, start_window, option_menu, "StoredRecipies.txt")
Note: you can find more info on the with statement here: http://effbot.org/zone/python-with-statement.htm
The downside of this is that the update will be a little laggy -- you'll end up rechecking the file only once a second, so the update won't be instantaneous.
Alternatively, you could use something like Watchdog. It's a 3rd party library that you can set up to "watch" a particular file and run a function whenever the file changes. It's much more responsive in that you'll call the function only if the file actually changes, but it might end up being more complicated since you need to figure out how to make it work with Tkinter. I'm going to guess that your code will look roughly like this:
import os.path
from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer
def setup_observer(option_menu, filename):
normalized_filename = os.path.normpath(input_filename)
class MyEvent(FileSystemEventHandler):
def on_modified(self, event):
if os.path.normpath(event.src_path) == normalized_filename:
# update your option menu
observer = Observer()
observer.schedule(MyEvent(), '.', recursive=False)
return observer
def script():
# setup gui
observer = setup_observer(option_menu, "myfile.txt")
start_window.mainloop()
To add elements to an OptionList, you can use the following method (from http://www.prasannatech.net/2009/06/tkinter-optionmenu-changing-choices.html)
datafile = open("StoredRecipes.txt", "r")
for line in datafile.readlines():
saved_recipes['menu'].add_command(label=line,
command=lambda temp = line: saved_recipes.setvar(saved_recipes.cget("textvariable"), value = temp))
Which uses (has to use) a closure and an anonymous function -- definitely nothing you should deal with on your level of experience (guessing from the structure of your code).
This snippet adds a command for each line in your file. Because an OptionMenu is something that executes things when elements are selected, you have to provide a command for each line. Right now this is just setting the displayed text to the selected line.
To accomplish this, it uses an anonymous function (lambda) that sets the textvariable of the OptionMenu to the current line.
Related
I want to make a kind of chat box, and I would like the letters to be word by word, I did that function but it stays loading until the loop ends, and it gives me the final result, i see in other pages and questions, and i saw that the "after" funtion works, maybe i did something wrong when implementing it, sorry for my english
import tkinter as tk
from tkinter import ttk
import os
from PIL import ImageTk
import PIL.Image
# parent window where is an image of the chatbox
def Ventana_Text_Box(event):
#Ventana De Text box
global ventana_BT
ventana_BT = tk.Tk()
ventana_BT.geometry("300x300+"+str(200)+"+"+str(100))
ventana_BT.configure(background="gray")
I_Text_Box_Image = ImageTk.PhotoImage(I_Text_Box)
Box_Texto = tk.Label(ventana_BT, image = I_Text_Box_Image, bg="gray")
Box_Texto.pack()
Box_Texto.bind("<Button-1>", Ventana_Texto)
Box_Texto.bind("<Button-3>", escribir_texto)
#ventana_BT.wm_attributes("-topmost", 1)
ventana_BT.wm_attributes("-transparentcolor", "gray")
ventana_BT.overrideredirect(1)
ventana_BT.mainloop()
# window where the text will be
def Ventana_Texto(event):
# Ventana hija para el texto
global ventana_T
global W_texto
ventana_T = tk.Toplevel()
ventana_T.geometry("300x300+"+str(ventana_BT.winfo_rootx()-70)+"+"+str(ventana_BT.winfo_rooty()+140))
ventana_T.configure(background="gray")
W_texto = tk.Label(ventana_T, text="", bg="pink")
W_texto.config(fg="black", font=("Consola", 15))
W_texto.pack()
#escribir_texto("Hola")
#ventana_T.wm_attributes("-topmost", 1)
ventana_T.wm_attributes("-transparentcolor", "gray")
ventana_T.overrideredirect(1)
ventana_T.mainloop()
# Function that changes the text from letter to letter
def mecanografiar(texto):
for i in range(len(texto)+1):
return W_texto.config(text=texto[0:i])
# test function to see if it works write "HOLA"
def escribir_texto(event):
texto = "hola"
W_texto.after(400, mecanografiar(texto))
scriptpath = os.path.abspath(__file__)
scriptdir = os.path.dirname(scriptpath)
Text_Box = os.path.join(scriptdir, "Dialogo", "text_box.png")
#800x712
I_Text_Box = PIL.Image.open(Text_Box)
W_I = 350
H_I = W_I*712/800
I_Text_Box = I_Text_Box.resize((W_I,int(H_I)), PIL.Image.ANTIALIAS)
if __name__ == "__main__":
Ventana_Text_Box(None)
import tkinter as tk
root = tk.Tk()
root.geometry('200x200')
# this is whatever string you want to type out slowly
chat_str = 'Hello, friend!'
# storing text in a StringVar will update the label automatically
# whenever the value of the variable is changed (see 'textvariable' below)
text_var = tk.StringVar()
label = tk.Label(textvariable=text_var)
label.pack()
# index represents the character index in 'chat_str'
index = 0
# we need an empty string to store the typed out string as it updates
placeholder = ''
def type_text():
# use 'global' to allow the function to access these variables
global index
global placeholder
try:
# concat the placeholder with the next character in 'chat_str'
placeholder += chat_str[index]
# set 'text_var' to update the label automatically
text_var.set(placeholder)
# go to the next index (character) in 'chat_str'
index += 1
# call this function again after 150mS
# (change this number to modify the typing speed)
root.after(150, type_text)
except IndexError: # when you run out of characters...
return # bail
# NOTE:
# using a 'try:except' block above avoids issues stopping 'root.after()'
type_text()
root.mainloop()
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()
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 have written a program in Python that allow me to change the names of many files all at once. I have one issue that is quite odd.
When I use raw_input to get my desired extension, the GUI will not launch. I don't get any errors, but the window will never appear.
I tried using raw_input as a way of getting a file extension from the user to build the file list. This program will works correctly when raw_input is not used.The section of code that I am referring to is in my globList function. For some reason when raw_imput is used the window will not launch.
import os
import Tkinter
import glob
from Tkinter import *
def changeNames(dynamic_entry_list, filelist):
for index in range(len(dynamic_entry_list)):
if(dynamic_entry_list[index].get() != filelist[index]):
os.rename(filelist[index], dynamic_entry_list[index].get())
print "The files have been updated!"
def drawWindow(filelist):
dynamic_entry_list = []
my_row = 0
my_column = 0
for name in filelist:
my_column = 0
label = Tkinter.Label(window, text = name, justify = RIGHT)
label.grid(row = my_row, column = my_column)
my_column = 1
entry = Entry(window, width = 50)
dynamic_entry_list.append(entry)
entry.insert(0, name)
entry.grid(row = my_row, column = my_column)
my_row += 1
return dynamic_entry_list
def globList(filelist):
#ext = raw_input("Enter the file extension:")
ext = ""
desired = '*' + ext
for name in glob.glob(desired):
filelist.append(name)
filelist = []
globList(filelist)
window = Tkinter.Tk()
user_input = drawWindow(filelist)
button = Button(window, text = "Change File Names", command = (lambda e=user_input: changeNames(e, filelist)))
button.grid(row = len(filelist) + 1 , column = 1)
window.mainloop()
Is this a problem with raw_input?
What would be a good solution to the problem?
This is how tkinter is defined to work. It is single threaded, so while it's waiting for user input it's truly waiting. mainloop must be running so that the GUI can respond to events, including internal events such as requests to draw the window on the screen.
Generally speaking, you shouldn't be mixing a GUI with reading input from stdin. If you're creating a GUI, get the input from the user via an entry widget. Or, get the user input before creating the GUI.
A decent tutorial on popup dialogs can be found on the effbot site: http://effbot.org/tkinterbook/tkinter-dialog-windows.htm
G'day All,
I have a little app that os.walks gathering some data. While it is working I thought it might be nice to put an Entry widget with the text "Processing" in it and change that to "Completed" when the walking was done.
The issue is the "Processing" never appears. The processing takes many seconds so it's not like it's over too soon to be seen.
def do_search():
txtProgress.delete(0,END)
txtProgress.insert(0, "Processing Data")
print 'do_search called'
arrayOfDirectories = [] # Store the categories here
global path
print 'The value for path = ' + path # Delete this in final
searchpath = path
print 'The value for searchpath = ' + searchpath # Delete this in final
for (searchpath, directories, files) in os.walk(searchpath):
for directory in directories:
arrayOfDirectories.append(directory) # Create an array or dirs to use for the categories
id = 1
finalJSON = '['
for eachDirectory in arrayOfDirectories:
readpath = os.path.join(path, eachDirectory, 'URLS') # Grab each list of URLs
print('readpath = ' + readpath)
if os.path.exists(readpath):
file = open(readpath) # Open the list of URLs
for lines in file: # Step through each URL in turn
ruleString = '{"id":' + str(id) + ',"enabled":true, "category":"' + eachDirectory + '","description":"' + lines + '","flagged":true,"string":"' + lines + '","name":"","javaClass":"com.untangle.uvm.node.GenericRule","blocked":true}'
#print(ruleString)
finalJSON = finalJSON + ruleString # Create a rule and add it to the final string
id = id + 1 # Increment the id after each rule
file.close() # Close the file when all have been read
It's not a train smash if it doesn't work but I am at a loss to understand why text is not appearing.
As always, all advice gratefully accepted.
In short, you never give your program a chance to display it. Redrawing the screen only happens as the result of a repaint event, so unless the event loop has a chance to service that event nothing will be displayed. This isn't unique to Tkinter -- all GUI toolkits work this way.
The simple solution is to call txtProgress.update_idletasks() whenever you want the screen to be updated. This will allow events that refresh the screen to run. This is not the best solution, but it might solve your immediate problem.
The best solution is to refactor your program to either do the long-running work in a separate thread or process, or break the work down to chunks that can be done one at a time with each iteration of the event loop.
Just to put the final touches on this project. I applied both of Bryan's suggestions and used the .update_idletasks() method as well as employing threading. The final code works as desired and looks like this:
from Tkinter import *
import os
import threading
from tkFileDialog import askdirectory
#####################################
# Put the grunt stuff up here #
#####################################
def loadpath():
print 'loadpath called'
global path
path = askdirectory()
txtPath.delete(0, END)
txtPath.insert(0, path)
def update_the_status():
txtProgress.delete(0,END)
txtProgress.insert(0, "Processing Data")
txtProgress.update_idletasks()
def do_the_search():
print 'do_search called'
arrayOfDirectories = [] # Store the categories here
global path
print 'The value for path = ' + path # Delete this in final
searchpath = path
print 'The value for searchpath = ' + searchpath # Delete this in final
for (searchpath, directories, files) in os.walk(searchpath):
for directory in directories:
arrayOfDirectories.append(directory) # Create an array or dirs to use for the categories
id = 1
finalJSON = '['
for eachDirectory in arrayOfDirectories:
readpath = os.path.join(path, eachDirectory, 'URLS') # Grab each list of URLs
print('readpath = ' + readpath)
if os.path.exists(readpath):
file = open(readpath) # Open the list of URLs
for lines in file: # Step through each URL in turn
ruleString = '{"id":' + str(id) + ',"enabled":true, "category":"' + eachDirectory + '","description":"' + lines + '","flagged":true,"string":"' + lines + '","name":"","javaClass":"com.untangle.uvm.node.GenericRule","blocked":true}'
#print(ruleString)
finalJSON = finalJSON + ruleString # Create a rule and add it to the final string
id = id + 1 # Increment the id after each rule
file.close() # Close the file when all have been read
finalJSON = finalJSON + ']' # Close the JSON array
outputPath = os.path.join(os.path.dirname(path), 'Blacklist.json')
print('Output path = ' + outputPath)
outputFile = open(outputPath, 'w')
outputFile.write(finalJSON)
txtProgress.delete(0,END)
txtProgress.insert(0,"Process Complete")
outputFile.close()
def do_search():
WriteThread = threading.Thread(target=update_the_status())
CalcThread = threading.Thread(target=do_the_search())
WriteThread.start()
CalcThread.start()
def do_quit():
print 'do_quit called'
sys.exit()
#####################################
# Build the interface #
#####################################
# Some global variables
path = ''
# Create the application window and give it a title.
main = Tk()
main.geometry('600x400')
main.title('Blacklist JSON array builder')
# Populate the window with widgets.
lbSpace1 = Label(main, text='')
lbSpace1.grid(row=0, column=0, columnspan=3)
lbDesc = Message(main, width=800, text='After you have unzipped the .tar file select the blacklist folder \nthat has been created using the browse button. Then click start. The Blacklist.json \nfile will be created in the same directory as the Blacklist folder.')
lbDesc.grid(row=1, column=0, columnspan=4, pady=10)
lbPath = Label(main, text='Directory')
lbPath.grid(row=2, column=0, pady=10)
txtPath = Entry(main, width=50)
txtPath.grid(row=2, column=1)
pbPath = Button(main, text='Browse', command=loadpath)
pbPath.grid(row=2, column=2)
lbSpace2 = Label(main, text='')
lbSpace2.grid(row=3, column=0, columnspan=3)
pbStart = Button(main, text='Begin', command=do_search)
pbStart.grid(row=4, column=1, sticky=W, pady=20)
pbQuit = Button(main, text='Quit', command=do_quit)
pbQuit.grid(row=4, column=2, sticky=W)
lbSpace3 = Label(main, text='')
lbSpace3.grid(row=5, column=0, columnspan=3)
txtProgress = Entry(main, width=50)
txtProgress.grid(row=6, column=1)
txtProgress.insert(0,'Waiting')
mainloop()
Thanks for the help guys.