Skip to content

Getting Started#

Creating an Application#

Typical usage will begin by creating a Application object.

from app_model import Application

my_app = Application('my-app')

Registering Actions#

Most applications will have some number of actions that can be invoked by the user. Actions are typically callable objects that perform some operation, such as "open a file", "save a file", "copy", "paste", etc. These actions will usually be exposed in the application's menus and toolbars, and will usually have associated keybindings. Sometimes actions hold state, such as "toggle word wrap" or "toggle line numbers".

app-model provides a high level Action object that comprises a pointer to a callable object, along with placement in menus, keybindings, and additional metadata like title, icons, tooltips, etc...

from app_model.types import Action, KeyBindingRule, KeyCode, KeyMod, MenuRule

def open_file():
    print('open file!')

def close_window():
    print('close window!')

ACTIONS: list[Action] = [
    Action(
        id='open',
        title="Open",
        icon="fa6-solid:folder-open",
        callback=open_file,
        menus=['File'],
        keybindings=[KeyBindingRule(primary=KeyMod.CtrlCmd | KeyCode.KeyO)],
    ),
    Action(
        id='close',
        title="Close",
        icon="fa-solid:window-close",
        callback=close_window,
        menus=['File'],
        keybindings=[KeyBindingRule(primary=KeyMod.CtrlCmd | KeyCode.KeyW)],
    ),
    # ...
]

Actions are registered with the application using the Application.register_action() method.

for action in ACTIONS:
    my_app.register_action(action)

Registries#

The application maintains three internal registries.

  1. Application.commands is an instance of CommandsRegistry. It maintains all of the commands (the actual callable object) that have been registered with the application.
  2. Application.menus is an instance of MenusRegistry. It maintains all of the menus and submenu items that have been registered with the application.
  3. Application.keybindings is an instance of KeyBindingsRegistry. It maintains an association between a KeyBinding and a command id in the CommandsRegistry.

Note

Calling Application.register_action with a single Action object is just a convenience around independently registering objects with each of the registries using:

Registry events#

Each of these registries has a signal that is emitted when a new item is added.

You can connect callbacks to these events to handle them as needed.

@my_app.commands.registered.connect
def on_command_registered(command_id: str):
    print(f'Command {command_id!r} registered!')

my_app.commands.register_command('new-id', lambda: None, title='No-op')
# Command 'new-id' registered!

Executing Commands#

Registered commands may be executed on-demand using execute_command method on the command registry:

my_app.commands.execute_command('open')
# prints "open file!" from the `open_file` function registered above.

Command Arguments and Dependency Injection#

The execute_command function does accept *args and **kwargs that will be passed to the command. However, very often in a GUI application you may wish to infer some of the arguments from the current state of the application. For example, if you have menu item linked to a "close window", you likely want to close the current window. For this, app-model uses a dependency injection pattern, provided by the in-n-out library.

The application has a injection_store attribute that is an instance of an in_n_out.Store. A Store is a collection of:

  • providers: Functions that can be called to return an instance of a given type. These may be used to provide arguments to commands, based on the type annotations in the command function definition.
  • processors: Functions that accept an instance of a given type and do something with it. These are used to process the return value of the command function at execution time, based on command definition return type annotations.

See in-n-out getting started for more details on the use of providers/processos in the Store.

Here's a simple example. Let's say an application has a User object with a name() method:

class User:
    def name(self):
        return 'John Doe'

Assume the application has some way of retrieving the current user:

def get_current_user() -> User:
    # ... get the current user from somewhere
    return User()

We register this provider function with the application's injection store:

my_app.injection_store.register_provider(get_current_user)

Now commands may be defined that accept a User argument, and used for callbacks in actions registered with the application.

def print_user_name(user: User) -> None:
    print(f"Hi {user.name()}!")

action = Action(
    id='greet',
    title="Greet Current User",
    callback=print_user_name,
)

my_app.register_action(action)
my_app.commands.execute_command('greet')
# prints "Hi John Doe!"

Connecting a GUI framework#

Of course, most of this is useless without some way to connect the application to a GUI framework. The app_model.backends module provides functions that map the app-model model onto various GUI framework models.

erm... someday 😂

Well, really it's just Qt for now, but the abstraction is valuable for the ability to swap backends. And we hope to add more backends if the demand is there.

Qt#

Currently, we don't have a generic abstraction for the application window, so users are encouraged to directly use the classes in the app_model.backends.qt module. One of the main classes is the QModelMainWindow object: a subclass of QMainWindow that knows how to map an Application object onto the Qt model.

from app_model.backends.qt import QModelMainWindow
from qtpy.QtWidgets import QApplication

app = QApplication([])

# create the main window with our app_model.Application
main = QModelMainWindow(my_app)

# pick menus for main menu bar,
# using menu ids from the application's MenusRegistry
main.setModelMenuBar(['File'])

# add toolbars using menu ids from the application's MenusRegistry
# here we re-use the File menu ... but you can have menus
# dedicated for toolbars, or just exclude items from the menu
main.addModelToolBar('File')
main.show()

app.exec_()

You should now have a QMainWindow with a menu bar and toolbar populated with the actions you registered with the application with icons, keybindings, and callbacks all connected.

QMainWindow with menu bar and toolbar

Once objects have been registered with the application, it becomes very easy to create Qt objects (such as QMainWindow, QMenu, QMenuBar, QAction, QToolBar, etc...) with very minimal boilerplate and repetitive procedural code.

See all objects in the Qt backend API docs.

Tip

Application registries are backed by psygnal, and emit events when modified. These events are connected to the Qt objects, so QModel... objects such as QModelMenu and QCommandAction will be updated when the application's registry is updated.