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/
│   ├── defaults.py         <- shared imports for all screens and elements
│   └── <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)
from Screens.defaults import *

def loop():                  pass  # called every tick
def configure_menu(menubar): pass  # contribute to HostMenu when tab is active
def on_activate():           pass  # tab gained focus
def on_deactivate():         pass  # tab lost focus

def setup(parent):
    # ALL widget creation goes here
    pane = LayoutFrame(parent)
    pane.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 critical blocks:

#%Screen Elements
import Screens.MyScreen.f_header
import Screens.MyScreen.f_body

#%Screen Modules
import modules.MyScreen.m_header
import modules.MyScreen.m_body

Inside setup():

#%Build Screen Elements
Screens.MyScreen.f_header.build(pane)
Screens.MyScreen.f_body.build(pane)

Inside loop():

#%Predefined Loop Functions
if bool(getattr(modules.MyScreen.m_header, "_m_header", False)): modules.MyScreen.m_header._m_header()
if bool(getattr(modules.MyScreen.m_body, "_m_body", False)): modules.MyScreen.m_body._m_body()

#%User Defined Loop Functions
pass

stitch replaces the content under #%Screen Elements with fully-qualified imports for each Screens/<screen>/f_*.py file. #%Screen Modules is replaced with fully-qualified imports for each modules/<screen>/m_*.py file. #%Build Screen Elements is replaced with build(pane) calls, and #%Predefined Loop Functions is replaced with guarded callbacks for each module.

Import conventions:

  • Screens.* and modules.* are always imported as full dotted paths (import Screens.MyScreen.f_header), never with from or as.

  • from imports inside functions are only used in the __main__ guard (from Screens.root import root, frame).

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