2

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?)

ham-sandwich
  • 3,975
  • 10
  • 34
  • 46
18166
  • 111
  • 1
  • 13
  • Are you looking for a way to monitor a file for changes, or how to populate an `OptionMenu`? – Jasper Dec 28 '14 at 17:14
  • @Jasper Sort of - I want to update the options in the OptionMenu widget to display the first index in the lists in the datafile I've made. – 18166 Dec 28 '14 at 17:19

2 Answers2

1

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()
Community
  • 1
  • 1
Michael0x2a
  • 58,192
  • 30
  • 175
  • 224
  • I think I'd prefer to use the first one - only because I kinda understand it. A few questions: you comment saying check your file, I'm not to sure how to do that, would I do datafile.readline() or what? – 18166 Dec 28 '14 at 17:30
  • @18166 -- more or less yes. I'd actually re-open the file and read from it again rather then reusing datafile. You should eventually close each file you open, and it's better to do that sooner rather then later so you don't forget about it. I edited my question to add more detail. – Michael0x2a Dec 28 '14 at 17:36
  • What should I put in the OptionMenu parameters (OptionMenu(root, etc.))? I can't find what to put in there to utilise this. – 18166 Dec 28 '14 at 17:49
  • @18166 -- unfortunately, I'm not going to be able to help much here. It's been a while since I've used Tkinter. In general, I would say that what you want to do is first create the option menu (without concerning yourself about checking the text file), then run the event where you can update the option menu you had created. You might be able to find more info on setting up the option menu [here](http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/optionmenu.html) – Michael0x2a Dec 28 '14 at 17:55
  • Ok, thanks for your answer, it works almost as expected, however I'm trying to make the lines = my_file.readlines() return the first item on the list, and putting 0 in the parentheses does brings up an error, and putting [0] says that integer argument expected, not list. Any suggestions? – 18166 Dec 28 '14 at 18:11
0

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.

Jasper
  • 3,939
  • 1
  • 18
  • 35