Sunday, August 30, 2026
Finally getting Catalog, Mixed Collections, and Patterns to work independently
I found that tree sub-folders makes it difficult to switch between workspaces. It's better to create a new folder in the parent folder.
Also, main.py (or whatever handles this) has to create a new .self in order to discern sub-layers as needed in the Patterns section.
Two examples of using what works out of the box vs. what is needed to modify.
https://github.com/marcususa/Totten
text### Page 1 ###
self.analysis_workspace = MixedAnalysis(self)
self.analysis_workspace.grid(row=0, column=1, sticky="nsew", padx=5, pady=5)
self.analysis_workspace.grid_remove() # Hide initially
# Register references in state
state.workspace = self.catalog_workspace
state.catalog_workspace = self.catalog_workspace
state.mixed_workspace = self.mixed_workspace
state.analysis_workspace = self.analysis_workspace
state.app_root = self
state.show_workspace = self.show_workspace
def show_workspace(self, target):
"""Persistent switchboard router that toggles pre-built workspace frames cleanly."""
# Hide all workspaces first
if hasattr(state, "catalog_workspace") and state.catalog_workspace:
state.catalog_workspace.grid_remove()
if hasattr(state, "mixed_workspace") and state.mixed_workspace:
state.mixed_workspace.grid_remove()
if hasattr(state, "analysis_workspace") and state.analysis_workspace:
state.analysis_workspace.grid_remove()
# Show the target workspace
if target == "mixed" or target == "mixed_analysis":
state.mixed_workspace.grid(row=0, column=1, sticky="nsew", padx=5, pady=5)
state.mixed_workspace.tkraise()
state.workspace = state.mixed_workspace
# Load any pending game subset data
src = getattr(state, "active_category_source", None)
if src:
if isinstance(src, list) and hasattr(state.mixed_workspace, "load_games_list"):
state.mixed_workspace.load_games_list(src)
elif isinstance(src, str):
if hasattr(state.mixed_workspace, "load_catalog_data"):
state.mixed_workspace.filename = src
state.mixed_workspace.load_catalog_data()
else:
if hasattr(state.mixed_workspace, "load_catalog_data"):
state.mixed_workspace.load_catalog_data()
elif target == "analysis":
state.analysis_workspace.grid(row=0, column=1, sticky="nsew", padx=5, pady=5)
state.analysis_workspace.tkraise()
state.workspace = state.analysis_workspace
else:
state.catalog_workspace.grid(row=0, column=1, sticky="nsew", padx=5, pady=5)
state.catalog_workspace.tkraise()
state.workspace = state.catalog_workspace
if __name__ == "__main__":
ctk.set_appearance_mode("Dark")
ctk.set_default_color_theme("blue")
app = Totten()
app.mainloop()
Response: The reason show_workspace() raised an unexpected keyword argument error is that the routing switchboard accepts only a target string argument, while your selection handler was trying to pass initial_games= directly into it.
Furthermore, looking at Totten._init_ui(), the application pre-initializes the catalog workspace once on startup and then just toggles visibility with grid_remove() and tkraise(). Because it's a pre-built frame rather than newly instantiated on click, calling show_workspace("catalog") didn't catch the new games stored in catalog_state["active_games"].
To fix this so that clicking Catalog properly loads search_catalog_workspace.py first, and then correctly routes your selected subset into catalog_analysis.py, update show_workspace in your Totten class:
### Page 2 ###
def show_workspace(self, target, *args, **kwargs):
"""Persistent switchboard router that toggles or re-creates workspaces based on navigation flow."""
# Hide all main workspaces first
if hasattr(state, "catalog_workspace") and state.catalog_workspace:
state.catalog_workspace.grid_remove()
if hasattr(state, "mixed_workspace") and state.mixed_workspace:
state.mixed_workspace.grid_remove()
if hasattr(state, "analysis_workspace") and state.analysis_workspace:
state.analysis_workspace.grid_remove()
# Also clean up any transient search/selector workspace frame if active
if hasattr(state, "transient_workspace") and state.transient_workspace:
state.transient_workspace.destroy()
state.transient_workspace = None
# Route based on target type
if target == "search_catalog" or target == "catalog_search":
# Stage 1: Load search_catalog_workspace.py selector view
from gui.search_catalog_workspace import SearchCatalogWorkspace
try:
self.transient_workspace = SearchCatalogWorkspace(self)
except TypeError:
from gui.search_catalog_workspace import create_workspace
self.transient_workspace = create_workspace(self)
self.transient_workspace.grid(row=0, column=1, sticky="nsew", padx=5, pady=5)
self.transient_workspace.tkraise()
state.workspace = self.transient_workspace
elif target == "catalog" or target == "catalog_analysis":
# Stage 2: Load/Refresh catalog analysis with any isolated state games
initial_games = kwargs.get("initial_games") or state.catalog_state.get("active_games")
# If a filtered subset was chosen, we rebuild or update the catalog workspace view
if initial_games:
if hasattr(state, "catalog_workspace") and state.catalog_workspace:
state.catalog_workspace.destroy()
from gui.catalog_analysis import create_workspace
state.catalog_workspace = create_workspace(self, initial_games=initial_games)
state.catalog_workspace.grid(row=0, column=1, sticky="nsew", padx=5, pady=5)
# Clear out bucket after consumption
state.catalog_state["active_games"] = None
state.catalog_workspace.grid(row=0, column=1, sticky="nsew", padx=5, pady=5)
state.catalog_workspace.tkraise()
state.workspace = state.catalog_workspace
elif target == "mixed" or target == "mixed_analysis":
state.mixed_workspace.grid(row=0, column=1, sticky="nsew", padx=5, pady=5)
state.mixed_workspace.tkraise()
state.workspace = state.mixed_workspace
src = kwargs.get("initial_games") or getattr(state, "active_category_source", None) or state.mixed_state.get("active_games")
if src:
if isinstance(src, list) and hasattr(state.mixed_workspace, "load_games_list"):
state.mixed_workspace.load_games_list(src)
elif isinstance(src, str):
if hasattr(state.mixed_workspace, "load_catalog_data"):
state.mixed_workspace.filename = src
state.mixed_workspace.load_catalog_data()
else:
if hasattr(state.mixed_workspace, "load_catalog_data"):
state.mixed_workspace.load_catalog_data()
### Page 3 ###
elif target == "analysis":
state.analysis_workspace.grid(row=0, column=1, sticky="nsew", padx=5, pady=5)
state.analysis_workspace.tkraise()
state.workspace = state.analysis_workspace
else:
state.catalog_workspace.grid(row=0, column=1, sticky="nsew", padx=5, pady=5)
state.catalog_workspace.tkraise()
state.workspace = state.catalog_workspace
Wiring Your Sidebar / Buttons
1. When your sidebar button for Catalog is clicked, call:
state.show_workspace("search_catalog")
2. When the user finishes picking their game group inside search_catalog_workspace.py, finalize it with:
import gui.app_state as state
state.catalog_state["active_games"] = chosen_games_list
state.show_workspace("catalog")
User prompt: I am unsure about 1 and 2 at the end. Like this?
self.btn_catalog = ctk.CTkButton(self, text="Catalog", anchor="w", fg_color="transparent", hover_color="#2e4a8c", text_color="white", command=lambda: self.on_navigate("search_catalog"))
self.btn_catalog.pack(fill="x", padx=4, pady=(15, 5))
state.show_workspace("search_catalog")
Response: Yes, that lambda setup for the sidebar button is precisely correct. When clicked, self.on_navigate("search_catalog") will trigger state.show_workspace("search_catalog"), launching the search selector workspace first.
For step 2, when the user finishes picking their game group inside search_catalog_workspace.py, the button or confirmation action inside that selector should look like this:
import gui.app_state as state
def on_group_selected(self, chosen_games_list):
# Store the chosen subset in the isolated catalog bucket
state.catalog_state["active_games"] = chosen_games_list
### Page 4 ###
# Trigger the switch to the catalog analysis workspace
if hasattr(state, "show_workspace"):
state.show_workspace("catalog")
This completes the two-stage handoff: clicking Catalog opens the search/selector view first, and confirming a group transitions directly into catalog_analysis.py populated exclusively with that filtered subset.
User prompt: And that just goes up at the top?
Response: Yes, place import gui.app_state as state at the very top of search_catalog_workspace.py alongside your other imports (like customtkinter).
The on_group_selected function (or whatever method handles the event when the user confirms their choice) goes inside the search workspace class itself, placed right where the user clicks the final "Load" or "Analyze" button for that group.
User prompt: OMG!!!
Response: It looks like that two-stage handoff finally clicked into place!
With the search selection now routing cleanly into catalog_analysis.py using the isolated state bucket, you've got a solid pattern established. Whenever you're ready to tackle the exact same two-stage flow for Mixed Collections (edit_workspace.py transitioning to mixed_analysis.py), just let me know.
Subscribe to:
Posts (Atom)
-
**See 6/22 post for adjustments.** Code can be tested at https://www.programiz.com/python-programming/online-compiler/ (Remove the 3 lines ...
-
The code below starts the bidding process. It's possible now to add graphics, that might be next as well as continuing with picking up. ...