Looking for a bit of help please, on understand why I am getting a keyerror in my JSON loading.
Error is:
Exception in Tkinter callback
Traceback (most recent call last):
File "...\AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 1921, in __call__
return self.func(*args)
File "...\test22.py", line 108, in <lambda>
command=lambda: controller.show_frame(TestClass.saved_frame))
File "...\test22.py", line 77, in show_frame
frame = self.frames[cont]
KeyError: "<class '__main__.PageTwo'>"
Goal and steps: To have file menu (save) preserve class variables, which can then be loaded if program is exited and the relaunched, to resume the program where the save was initiated.
This will be done by class PageOne taking TestClass.saved_frame, which is stored/loaded in JSON, and open specified frame alongside relevant stored variables using the "load save" button.
I have the class variables working to properly save and load (can be done with file menu save the "load save" main menu button in GUI in a single instance). The problem is retrieving the class variables for loading in a new instance of the program.
I am not dead set on implementing the save/load in this fashion if there is a better available. I do plan on scaling this method with more class variables if I can get it working with these two class variables (num and frame).
Relevant code causing issue is in the function **def load_game()**inside of class TestClass and possibly JSON interaction with def show_frame()
I have tried variations of stripping quotes, iterating key/value in the JSON and selecting the relevant values, literal_eval, but each time I get KeyError.
To replicate issue, open the GUI > select num > next page > file menu and save > exit program then open GUI again > file menu load > load save button
import tkinter as tk
import json
from pathlib import Path
def generated_savegame_folder():
path = Path.cwd() / 'Save Games'
path.mkdir(parents=True, exist_ok=True)
generated_savegame_folder()
class TestClass(tk.Tk):
num = None
active_frame = None
saved_frame = None
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.wm_title(self, "Game")
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (PageOne, PageTwo, LoadPage):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(PageOne)
def save_game(): # JSON METHOD
TestClass.saved_frame = TestClass.active_frame
print("Saved as", TestClass.saved_frame)
data = {
"Saved Frame": str(TestClass.saved_frame),
"Num": TestClass.num.get()
}
save_folder = Path.cwd() / 'Save Games'
save_file = "saveJSON.txt"
file_path = save_folder / save_file
with file_path.open("w", encoding="utf-8") as f:
json.dump(data, f)
def load_game(): # JSON METHOD
path = Path.cwd() / 'Save Games' / 'saveJSON.txt'
with path.open('r') as f:
data = json.load(f)
print("loading", data)
for k, v in data.items():
print(k, v)
TestClass.saved_frame = data["Saved Frame"]
def menu():
menu_bar = tk.Menu(container)
file_menu = tk.Menu(menu_bar, tearoff=0)
file_menu.add_command(label="Save Game", command=save_game)
file_menu.add_command(label="Load Game", command=load_game)
file_menu.add_separator()
file_menu.add_command(label="Exit", command=quit)
menu_bar.add_cascade(label="File", menu=file_menu)
tk.Tk.config(self, menu=menu_bar)
menu()
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
TestClass.active_frame = cont
print("prints container(active_frame) on page change:", TestClass.active_frame) # testing
print("prints frame(frame.tkraise) on page change", frame) # testing
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.label = tk.Label(self,
text="Make a selection",
wraplength=450, justify='center')
self.label.pack(padx=10, pady=10)
TestClass.num = tk.StringVar(value=' ')
command = lambda *args: PageTwo.label.config(text=PageTwo.text.format(TestClass.num.get()))
tk.Radiobutton(self, text="1", variable=TestClass.num, command=command, value="1", ).pack()
tk.Radiobutton(self, text="2", variable=TestClass.num, command=command, value="2", ).pack()
tk.Radiobutton(self, text="3", variable=TestClass.num, command=command, value="3", ).pack()
view_selection = tk.Button(self, text="test selection", command=lambda: print(TestClass.num.get()))
view_selection.pack()
next_page = tk.Button(self, text="Next Page",
command=lambda: controller.show_frame(PageTwo))
next_page.pack(pady=10, padx=10)
# meant to load the last open page. PageTwo, PageThree... PageTwenty, etc. Whichever was last open before (save)
# while also loading any saved variables
load_last = tk.Button(self, text="Load Instance Save",
command=lambda: controller.show_frame(TestClass.saved_frame))
load_last.pack(pady=10, padx=10)
# load_last2 = tk.Button(self, text="Load JSON Save",
# command=lambda: controller.show_frame(restore_game))
# load_last2.pack(pady=10, padx=10)
class PageTwo(tk.Frame):
label = ''
text = ''
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
PageTwo.text = "The number should show up here -> {} <- "
PageTwo.label = tk.Label(self, text=self.text.format(''))
PageTwo.label.pack()
text1 = tk.Text(self)
text1.pack()
see_num = tk.Button(self, text="View Number",
command=lambda: text1.insert('1.0', TestClass.num.get()))
see_num.pack(pady=10, padx=10)
home_button = tk.Button(self, text="Go Home",
command=lambda: controller.show_frame(PageOne))
home_button.pack(pady=10, padx=10)
class LoadPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
if __name__ == "__main__":
app = TestClass()
app.mainloop()
CodePudding user response:
The data loaded from the JSON file is string and the key cont in self.frames[cont] is expected a reference to class.
You need to save the class name, for example "PageTwo" instead of "<class '__main__.PageTwo'>" into the JSON file. Then you need to convert the string "PageTwo" to reference of class after loading it from the JSON file:
def save_game(): # JSON METHOD
TestClass.saved_frame = TestClass.active_frame
# use .__name__ to get the class name and save it to JSON file
print("Saved as", TestClass.saved_frame.__name__)
data = {
"Saved Frame": TestClass.saved_frame.__name__,
"Num": TestClass.num.get()
}
save_folder = Path.cwd() / 'Save Games'
save_file = "saveJSON.txt"
file_path = save_folder / save_file
with file_path.open("w", encoding="utf-8") as f:
json.dump(data, f)
def load_game(): # JSON METHOD
path = Path.cwd() / 'Save Games' / 'saveJSON.txt'
with path.open('r') as f:
data = json.load(f)
print("loading", data)
for k, v in data.items():
print(k, v)
# convert the class name to reference of class
TestClass.saved_frame = globals()[data["Saved Frame"]] # or eval(data["Saved Frame"])
print('loaded', TestClass.saved_frame)
CodePudding user response:
Consider this little example:
myList = [1, 2, 3, 4]
print(myList [10]) # This will cause a KeyError as there isn't a 10th index in the list
Try printing the value of cont and the number of entries in frames - I think you'll find that you're trying to access an index that doesn't exist. Printing something like this / using a Debugger to view the values
Consider this little example:
```python
myList = [1, 2, 3, 4]
print(myList [10]) # This will cause a KeyError as there isn't a 10th index in the list
print(f"Number of Frames: {self.frames.len}, Cont: {cont}") frame = self.frames[cont]
**I'm not sure what method / variabe / member variable is used to get the length, so I've just used {obj}.len here as a placeholder.**
