Hlavní navigace

Modelujeme s pravítkem

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

Minule jsem zmínil, že můžeme chtít také v modelu spravovat vybranou položku. Tady už nastává oboustraná komunikace mezi view (neboli tím vlastním oknem nebo ovládacím prvkem) a modelem. View musí modelu sdělit, že se změnila vybraná položka, naopak model musí „odpálit“ zprávu když se změní vybraná položka. Jenže tady nám vzniká potenciální problém. View řekne modelu: nastav výběr na pátou položku. Na základě toho model odpálí změnu výběru, což odchytne view, a sdělí modelu, ať nastaví výběr na pátou položku, a tak pořád až do nekonečné rekurze. Samozřejmě ta náprava tkví v tom, že v modelu odpalujeme změnu výběru pouze, pokud se výběr opravdu změnil, a stejně tak ve view v handleru změny výběru nastavujeme výběr pouze, pokud se změnil. Tak, jak je v github/modellerkit step02, a vypadá to asi takto:

# model:
    def select(self, index):
        if self.selection == index:
            return
        self.selection = index
        self._fire("select", index)
# view:
    def handle_select(self, event_source, event_name, data):
        selected = self.GetFirstSelected()
        if data == selected:
            return
        if data > -1:
            self.Select(data)
        elif -1 == selected:
            pass
        else:
            self.Select(selected, False)
To je ovšem jen takový implementační detail.

namedtuple

Teď udělám malou odbočku. collections.namedtuple je geniální nápad jak dát nám programátorům k dispozici snadnou možnost nadefinovat si vlastní třídu, která je potomek tuple, a k jejíž prvkům se dá přistupovat jak přes index, tak přes jméno. Zároveň je to tuple, takže prvkům nelze přiřadit jinou hodnotu. Např.:

from collections import namedtuple

SampleStruct = namedtuple("SampleStruct", ["id", "name", "mktm",])
item = SampleStruct(1, "passwd", "2013-12-4 5:32:11")
item.id
item.name
item.mktm = "2013-12-4 12:43" # tohle nelze

Jako by to na mě křičelo: Použij mě pro modelářskou sadu! Tak dobře. Ve github/modellerkit step03/hotlist.py máme novou třídu TypedHotList, která je potomkem HotList, a která jen kontroluje, že přiřazované hodnoty jsou toho typu, který jsme si na začátku určili:

class TypedHotList(HotList):
    def __init__(self, type_constraint, init_iterable=None):
        super(TypedHotList, self).__init__(init_iterable)
        self.type_constraint = type_constraint

    def _validate_value(self, val):
        if not isinstance(val, self.type_constraint):
            raise TypeError("Only %s allowed here." % self.type_constraint)
        if isinstance(val, tuple) or isinstance(val, frozenset):
            for i in val:
                self._validate_sub_value(i)
        return val
    def _validate_sub_value(self, val):
        if type(val) in (int, long, float, str, unicode, ):
            return val
        if isinstance(val, tuple) or isinstance(val, frozenset):
            for i in val:
                self._validate_sub_value(i)
            return val
        raise TypeError("Only number/strings and tuples/frozensets allowed here.")

TypedHotList není typově omezen jen na namedtuple, ikdyž by to asi bylo přijatelné omezení.

Edit: Jestliže moje původní myšlenka byla, že HotList (nebo TypedHotList) nesmí obsahovat mutable hodnoty, tak tady mi to nefunguje. Berte to, jako takovou pracovní verzi.

Co dál

Co je vlastně model? Máme pro jedno složitější view, které má třeba 2 seznamy a ještě něco k tomu zvlášť dva modely pro seznamy a ještě model pro to něco k tomu, nebo chceme raději jeden větší model, ve kterém budou všechna data pohromadě? Zároveň s tím se budeme muset podívat na pořádný dispatch.