Model v modelu aneb kdo si ze mě střílí?

18. 8. 2014 19:48 (aktualizováno) Petr Blahoš

Minule jsme už měli horké objekty uvnitř horkých objektů – např. HotListHotObject, ale když přišlo na to, pověsit si event listenery, tak jsme se dostali do slepé uličky. Připomeňme si, co vlastně děláme. Náš model vypadá logicky takto:

class ProductModel(object):
    def __init__(self, server):
        self.server = server
        self.article = ""
        self.sn = 0
        self.process = []
        self.operations = []

ale chceme od něj, aby každá změna hodnot způsobila vyvolání nějaké události.

    model.article = "PCB647"  # vyvolá událost (event)
    model.sn = 736            # vyvolá událost (event)
To dělám pomocí jakési instrumentace, kvůli které ten objekt vypadá, jak se někdo vyjádřil v komentářích, jako nádor. V minulém díle nám ty objekty sice generovaly události, jenže jaksi každý za sebe. Takže když jsem měl např. HotList v HotObjectu, a změnil se HotList, tak vyšla událost z HotListu a ne z toho HotObjectu. Dnes si to celé upravíme tak, aby objekt znal svého rodiče, a abychom nastavili jeden listener, k kořenu té hierarchie, a tento listener aby používaly i všechny jeho „podobjekty“. Tak jdeme na to: github/modellerkit, step05.

Hierarchie

Kromě toho, že objekty budou v hierarchii (což je celkem banální věc), si taky konečně musíme říct, jak bude vypadat to vystřelení události. Musí obsahovat jméno události (jako update, insert, reset), a nějaká data k události. Např. index prvku v poli. Taky budeme potřebovat cestu k objektu v té hierarchii, a vlastně nakonec proč ne i ten objekt. Takže hlavička funkce obsluhující událost by mohla vypadat třeba takto:

def event_handler(model, path, event_name, key)
    """
    model je ten objekt, který způsobil událost
    path je cesta k modelu v rámci naší hierarchie
    event_name je jméno eventu
    key jsou data k události
    """

Zjednodušeně pak např.:

def event_handler(model, path, event_name, key):
    if "/process" == path and "reset" == event_name:
        process_view.Clear()
        for i in model:
            process_view.Append(str(i))

Směrování

Když stavíme UI v desktopových frameworcích (wx, GTK, …), tak si nějak navěsíme na ty události obslužnou funkci, a ta událost pak nějak prochází hierarchií widgetů/ovládacích prvků, a jakmile cestou narazí na patřičnou obslužnou funkci, tak se ta funkce zavolá. V našem modelu to ale máme jinak. My nenavěsíme obslužnou funkci přímo na tu část modelu, která nás zajímá, ale pro celý model máme centrální bod, přes který jdou všechny události, a potom se, pomocí nějakého směrování či mapování, rozhodne, kdo (a jestli vůbec někdo) si událost obslouží. Znamená to samozřejmně, že při generování události si musím nejprve posbírat tu cestu k objektu, který ji vyvolal (path), a potom znovu provést to směrování – tuto cestu (spolu s typem události) si převést na volání konkrétní obslužné funkce.

Vybral jsem si, že cesta bude řetězec znaků. Úplne klidně jsem si ale mohl říct, že to bude pole řetězců, nebo dokonce pole objektů s nějakým významem. Chci, aby se s tím snadno pracovalo, a zatím to směruji k mapování událostí na funkce pomocí jednoduchého porovnávání řetězců tak. Takže mám pomocnou třídu Mapper, která jednoduše namapuje cestu v rámci modelu (spolu se jménem události) na obslužnou funkci. Např. takto:

MAPPER = hotmodel.Mapper()
MAPPER.add_route("/process", "reset", handle_process_reset,)
MAPPER.add_route("/operations", "update", handle_operations_update,)
MAPPER.add_route("/process", "", update_process_counter,)
MAPPER.add_route("", "", handle_everything,)
MODEL.add_listener(MAPPER.listener)

A vlastní handler pak může vypadat takto:

def update_process_counter(model, fqname, event_name, key):
    process_counter.SetLabel(str(len(model))

Konečně moře

V příštím díle konečně uvidíte ukázku s nějakým UI.

Sdílet