Concepts#

This page covers the architecture and conventions behind a VIStk project. For a hands-on walkthrough, start with Quickstart.

Project Structure#

A VIStk project has the following folder layout:

MyProject/
├── .VIS/
│   ├── project.json        <- project registry (screens, versions, metadata)
│   ├── Host.py             <- Host entry point (generated by VIS new; not user-editable)
│   ├── Templates/          <- screen and element templates used by the CLI
│   └── project.spec        <- PyInstaller spec file (generated on release)
├── Screens/
│   └── <screen>/           <- UI element files, prefixed f_ (e.g. f_header.py)
├── modules/
│   └── <screen>/           <- logic files, prefixed m_ (e.g. m_header.py)
├── Icons/                  <- .ico (Windows) or .xbm (Linux) icon files
├── Images/                 <- image assets used by VIMG
├── <screen>.py             <- main script for each screen
└── dist/                   <- compiled binaries (created on release)

project.json is the source of truth for the project. It stores screen names, script paths, icons, descriptions, version numbers, and release configuration. It is managed automatically by the CLI and by VINFO/Project — do not edit it by hand.

App Lifecycle#

VIStk supports two runtime models: standalone (original) and Host-based (tabbed).

Standalone mode#

Each screen is its own Python process. Switching screens replaces the current process via os.execl. This is the original VIStk behaviour and still works for any screen where tabbed is false.

from VIStk.Objects import Root

root = Root()
root.screenTitle("MyScreen")
root.WindowGeometry.setGeometry(width=800, height=600, align="center")

# ... build your UI ...

if __name__ == "__main__":
    while root.Active:
        root.update()

Host mode#

The Host is a persistent process that owns a hidden Tk() root. All visible windows are DetachedWindow instances (Toplevels) that the Host manages. Screens marked tabbed: true open as tabs inside the active window; standalone screens open as new DetachedWindow instances.

On the first call to host.update(), the Host automatically opens the project’s default screen (set in project.json). This deferred open ensures that Host.py has time to configure shared menus before any window is created.

from VIStk.Objects import Host
from modules.menu import shared_menu_structure

host = Host()
host.default_menu_setup = lambda m: m.build_shared_menu(shared_menu_structure())

while host.Active:
    host.tick_fps()
    host.update()

Screen navigation from anywhere in the app:

# Routes through Host if running, otherwise os.execl
from VIStk.Structures._Project import Project
Project().open("WorkOrders")

Key rules:

  • Do not call root.mainloop() — this bypasses the while loop and prevents process switching.

  • Do not call root.destroy() to quit — call host.quit_host() instead.

  • Screen scripts must include if __name__ == "__main__": around the startup code so they can be imported as modules by the Host without executing the top-level loop.

  • Use Project.open() instead of Project.load() when a Host may be running — it routes correctly in both modes.

Screen Module Pattern#

Every tabbed screen must follow this pattern. Violating it will crash the Host on import:

# Module-level: only imports and pure data (no widget creation, no Tk calls)

def loop():                  pass  # called every frame (standalone only)
def configure_menu(menubar): pass  # contribute to HostMenu when tab is active
def on_focused():            pass  # tab gained focus
def on_unfocused():          pass  # tab lost focus

def setup(parent):
    # ALL widget creation goes here
    from tkinter import ttk
    frame = ttk.Frame(parent)
    frame.place(relx=0, rely=0, relwidth=1, relheight=1)

if __name__ == "__main__":
    from Screens.root import root, frame
    setup(frame)
    while root.Active:
        root.update()

The if __name__ == "__main__": guard is required for tabbed screens. When the Host imports the module to call setup(), the guard prevents the standalone loop from running.

Hook lookup priority: If modules/<screen>/m_<screen>.py exists, the Host checks it first for on_focused, on_unfocused, and configure_menu. The screen script is used as a fallback.

The f_element Pattern#

Elements are modular UI sections within a screen. Each element has a build(parent) function that receives the parent frame and places widgets into it.

# Screens/MyScreen/f_header.py

widget_ref = None  # module global -- set by build(), read by loop()

def build(parent):
    global f_elem, widget_ref
    f_elem = ttk.Frame(parent)
    f_elem.place(parent.Layout.cell(row, col))  # 0-indexed
    widget_ref = ttk.Label(f_elem, text="Header")
    widget_ref.pack()

The #% Marker System#

VIStk templates use #% comment markers as structural anchors. The stitch command uses these to locate and rewrite import blocks in screen scripts.

Warning

Do not delete or rename #% comment lines. They are not standard comments — they are structural anchors that VIStk searches for by text pattern.

The two critical blocks:

#%Screen Elements
from Screens.MyScreen.f_header import f_header
f_header.build(parent)
from Screens.MyScreen.f_body import f_body
f_body.build(parent)
#%Screen Grid

#%Screen Modules
from modules import MyScreen
#%Handle Arguments

stitch replaces everything between #%Screen Elements and #%Screen Grid with fresh imports and build() calls for each Screens/<screen>/f_*.py file. Everything between #%Screen Modules and #%Handle Arguments is replaced with a single package import: from modules import <ScreenName>.

stitch also generates modules/<screen>/__init__.py with three sections:

#%Modules (Auto-generated)
from . import m_header
from . import m_body

#%Screen Variables
# Hand-edited shared state (e.g. current_wo = None)

#%Exports
# Hand-edited public API (e.g. from .m_header import get_wo_num)

The #%Modules section is regenerated on every stitch. #%Screen Variables and #%Exports are preserved across stitches.

configure_menu Pattern#

Screens contribute menu items to the Host’s menu bar via the configure_menu hook. Items are added when the tab activates and automatically cleared when it deactivates.

def configure_menu(menubar):
    menubar.set_screen_items([
        {"label": "Export PDF", "command": export_pdf},
        {"label": "Print",      "command": print_fn},
    ], label="Work Orders")

    menubar.set_screen_items([
        {"label": "About", "command": show_about},
    ], label="Help")

Call set_screen_items multiple times to contribute multiple cascades.

Project-wide menus are configured in Host.py via default_menu_setup. This callback is called on every new DetachedWindow’s HostMenu, ensuring consistent menus across all windows:

host = Host()
host.default_menu_setup = lambda m: m.build_shared_menu({
    "File": [
        {"label": "New", "items": [...]},
        {"separator": True},
        {"label": "Exit", "command": host.quit_host},
    ],
    "Edit": [...],
    "View": [...],
    "Tools": [...],
})

project.json#

project.json is managed by the CLI and VINFO. Its structure:

{
    "MyApp": {
        "Screens": {
            "Home": {
                "script": "Home.py",
                "release": true,
                "icon": "home",
                "desc": "Main dashboard",
                "tabbed": true,
                "single_instance": false,
                "version": "1.0.0",
                "current": null
            }
        },
        "defaults": {
            "icon": "app",
            "default_screen": "Home"
        },
        "metadata": {
            "company": "My Company",
            "copyright": "My Company",
            "version": "0.1.0"
        },
        "release_info": {
            "location": "./dist/",
            "hidden_imports": []
        },
        "host": {
            "script": ".VIS/Host.py"
        }
    }
}

Per-screen fields:

Field

Type

Description

script

string

Python script filename in the project root

release

bool

Whether this screen is compiled to its own binary

icon

string/null

Icon name (without extension) from Icons/

desc

string

Screen description

tabbed

bool

true = Host tab, false = standalone subprocess

single_instance

bool

Prevent duplicate tabs when true

version

string

Screen-specific version (major.minor.patch)

current

string/null

Free-form current-state string