Elementi di Layout

Per creare pagine articolate in diverse sezioni e parti sono disponibili dei widget di tipo layout.

Identifichiamo due diverse categorie di widget di tipo layout: i pane, delle «tiles» da riempire con un contenuto, ad esempio Formbuilder o altri Widgets , testi o components , e i container, veri e propri «slot», che hanno lo scopo di ospitare uno o più pane.


La strutturazione di una pagina complessa in Genropy implicherà dunque innanzitutto di dividere o organizzare lo spazio con dei container, e successivamente di riempire questo spazio con dei pane.

Elemento Layout Può contenere
Containers Altri containers, contentPane
contentPane Altri containers, Widget, Testi, Components

Il pane principale è il contentPane, mentre i container si suddividono in tabContainer, borderContainer e stackContainer.

contentPane

Il contentPane è l’unico widget di layout che può contenere element HTML o widget. Può anche contenere un altro container ma in tal caso deve essere figlio unico.

Nell’esempio viene mostrato che gli attributi di un elemento possono essere anche settati dopo la creazione dell’elemento stesso: in particolare il background color è assegnato successivamente:

cp=root.contentPane(margin='5px',position='relative',
                          border='1px solid silver', rounded=6)
cp.attributes['background_color']='lightyellow'

Creiamo poi usando un posizionamento assoluto una serie di div:

for k in range (10):
    x=5+k*30
    cp.div(position='absolute',top='%spx'%x,left='%spx'%x,
             height='26px',width='58px',
             background='#ddd',rounded=5)

    cp.div(position='absolute',top='%spx'%x,left='%spx'%(280-x),
             height='26px',width='58px',
             background='#ddd',rounded=5)

Qui di seguito un ulteriore esempio in cui vediamo l’utilizzo tipico con all’interno un Formbuilder :

tabContainer

Il tabContainer serve a mettere nello stesso spazio più contenuti organizzati in pagine attivate da «tab». Al tabContainer è possibile aggiungere dei contentPane oppure degli altri container (borderContainer o altri tabContainer). Quando si aggiunge un figlio deve essere specificato un title.

Nell’esempio vediamo la creazione di un tabContainer più esterno e dei suoi figli.

Notiamo che possiamo mettere come figli sia dei contentPane che degli altri container. In questo caso usiamo solo tabContainer per non anticipare container che vedremo successivamente. L’attributo tabPosition consente di settare la posizione dei tab.

Nel primo contentPane mettiamo solo un div:

helloworld.div('Helloworld',font_size='30px',margin='50px')

Nel secondo contentPane mettiamo un tabContainer che riempiamo con i giorni della settimana:

wd=pane.tabContainer(margin='4px',tabPosition="left-h")
for t in ('Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'):
wd.contentPane(title=t).div(t,font_size='30px',margin='50px')

Il terzo figlio del tabContainer primario è già un tabContainer e quindi può essere popolato direttamente:

for t in ('green','red','pink','blue'):
tc.contentPane(title=t,color=t).div(t,font_size='30px',margin='50px')

Lo stesso per il quarto:

for t in ('courier','times','helvetica','cursive'):
tc.contentPane(title=t,font_family=t).div(t,font_size='30px',margin='50px')

borderContainer

Il borderContainer permette di suddividere lo spazio interno in 5 regioni:

  • top
  • bottom
  • left
  • right
  • center
../_images/schema-containers.png

Il center prende tutto lo spazio libero, mentre per le regioni left e right è possibile dare una width. Per bottom e top è possibile specificare una height. A tutte le regioni, tranne center, è possibile assegnare il parametro splitter=True per richiedere uno splitter. Ogni regione può essere o un contentPane oppure un altro container.

Per prima cosa creiamo un borderContainer:

def main(self,root,**kwargs):
    bc=root.borderContainer(margin='10px',text_align='center')

Procediamo poi a creare il pannello di sinistra specificando la larghezza:

bc.contentPane(region='left',width='100px',
            background_color='lightyellow').div('Left')

E di seguito gli altri:

bc.contentPane(region='right',width='100px',
               background_color='lightgreen').div('Right')

bc.contentPane(region='top',height='30px',
               background_color='darkred',color='white').div('Top')

bc.contentPane(region='bottom',height='30px',
               background_color='darkblue',color='white').div('Bottom')

ed infine il center:

bc.contentPane(region='center').div('Center')

L’esempio che andiamo ora ad esaminare, oltre a mostrare alcune caratteristiche del borderContainer, darà modo di vedere qualche dettaglio in più della creazione della GUI con Genropy.

Dal momento che l’esempio si propone di mostrare più borderContainers creiamo per prima cosa un tabContainer:

def main(self,root,**kwargs):
    tc=root.tabContainer(margin='5px')

Procediamo quindi a creare il primo borderContainer e, dal momento che siamo in un tabContainer, passiamo come primo parametro il titolo da mostrare nel tab:

bc=tc.borderContainer(title='Headline')

Chiamiamo quindi il metodo makeRegions (che esamineremo poi) passando come parametro il colore che vogliamo per il testo contenuto:

self.makeRegions(bc, color='darkred')

Infine prepariamo il centro del nostro borderContainer:

bc.contentPane(region='center').div('Center<br/>Headline')

Il second tab è analogo ma specifica che il design del borderContainer è sidebar, ovvero le regioni left e right sono a tutta altezza:

bc=tc.borderContainer(title='Sidebar')
self.makeRegions(bc, design='sidebar',color='darkblue')
bc.contentPane(region='center').div('Center<br/>Sidebar')

L’ultimo tab si propone di mostrare un borderContainer innestato in un altro:

bc=tc.borderContainer(title='Nested')
self.makeRegions(bc,color='darkgreen')

Come region center mettiamo ora, invece di un contentPane, un altro borderContainer:

bc=bc.borderContainer(region='center')

Procediamo quindi a creare le regioni anche per questo borderContainer:

self.makeRegions(bc,font_size='12px',color='red',
                 background_color='lightyellow'
                 margin='0px',rounded=0,border=0)

E infine creiamo il center del borderContainer innestato:

bc.contentPane(region='center').div('Center<br/>Nested')

Passiamo ora ad esaminare il codice del metodo makeRegions:

def makeRegions(self,bc, **kwargs):

Oltre al borderContainer ci arrivano dei parametri che vogliamo mettere al borderContainer stesso. Abbiamo però già costruito questo elemento e quindi dovremo agire su bc.attributes e modificarli opportunamente. Per prima cosa mettiamo dei parametri di default:

bc.attributes.update(border='1px solid silver',font_size='16px',
                margin='5px', rounded=6,text_align='center')

Poi procediamo ad aggionare nuovamente con i kwargs ricevuti:

bc.attributes.update(**kwargs)

Andiamo quindi a definire la region left specificando la width, richiedendo la creazione dello splitter, mettendo il bordo di destra e aggiungendo un div con il nome della region:

bc.contentPane(region='left',width='50px',splitter=True,
               border_right='1px solid silver').div('Left')

Procediamo in analogia per le altre regions:

bc.contentPane(region='right',width='50px',splitter=True,
               border_left='1px solid silver').div('Right')

bc.contentPane(region='top',height='30px',splitter=True,
               border_bottom='1px solid silver').div('Top')

bc.contentPane(region='bottom',height='30px',splitter=True,
               border_top='1px solid silver').div('Bottom')

stackContainer

Lo stackContainer è simile al tabContainer ma non mostra delle etichette per selezionare la pagina voluta. Presenta invece un attributo selected che assume il valore della pagina selezionata, e cambiando questo valore viene cambiata la pagina corrente. Uno degli utilizzi tipici è un wizard che guidi l’utente in una serie di passaggi.

Come prima cosa definiamo un borderContainer:

bc=root.borderContainer(border='1px solid silver',
                        margin='5px',datapath='mystack')

Definiamo poi lo stackContainer e indichiamo la locazione nel datastore dove mettere la pagina correntemente selezionata:

sc=bc.stackContainer(region='center',
                     selected='^.selected',font_size='50px')

Inizializziamo la pagina selezinata a 0 con la chiamata data:

sc.data('.selected',0)

Procediamo poi a creare 10 pagine con un ciclo for:

for p in range(10):
    sc.contentPane(padding='20px').div("Page : %s"%p)

Mettiamo ora nel bottom un bottone per passare alla pagina precedente e uno per passare a quella seguente e tra i due un numberTextBox nel quale potremmo digitare il numero di pagina:

fb=bc.contentPane(region='bottom',
           border_top='1px solid silver').formbuilder(cols=3)

fb.button(' < ',action="SET .selected= p-1",
            p='^.selected', visible='== (p > 0)')
fb.numberTextBox('^.selected',font_size='18px',width='30px')
fb.button(' > ',action="SET .selected= p+1",
            p='^.selected', visible='== (p <9)')

In questa ultima parte di codice possiamo notare che la action del bottone che porta alla pagina precedente riceve in “p” il numero di pagina e lo mette nuovamente nello store decrementato di uno.

Possiamo anche vedere che esiste un attributo visible il cui valore viene calcolato a True solo se il numero di pagina è >0. Allo stesso modo funziona il bottone di pagina seguente, con la differenza che questa volta il bottone è visibile solo se il numero di pagina è minore di 9.

Si noti che non è obbligatorio gestire le pagine con dei valori numerici facendo uso dell’attributo selected. È possibile anche assegnare identificativi differenti di tipo testuale. In quel caso allo stackContainer verrà passato l’attributo selectedPage al posto di selected, mentre ai vari contentPane verrà assegnato un pageName come vediamo nel prossimo esempio: