Home > Gtk3, PyGObject, python > PyGObject: Properties

PyGObject: Properties

13 Aprile 2020

torna all’indice appunti

Properties

Le Properties descrivono la configurazione e lo stato dei widgets.
Come per i segnali, ogni widget ha il proprio set di properties.
Ad esempio un Button ha la property “label” che contiene il testo della label dentro al button.
Quando creiamo un’istanza di un widget, possiamo specificare il nome e il valore di una property,
passandola come keyword argument.
Ad esempio per creare una label allineata sulla destra con il testo “Hello World” ed un angolo
di 25 gradi, useremo la sintassi:

label = Gtk.Label(label="Hello World", angle=25, halign=Gtk.Align.END)

che è l’equivalente di scrivere:

label = Gtk.Label()
label.set_label("Hello World")
label.set_angle(25)
label.set_halign(Gtk.Align.END)

Invece di usare i getters e i setters, possiamo ottenere e settare le properties attraverso la
property props, con la sintassi:

widget.props.prop_name = value

Per sapere quali properties sono disponibili per un widget, possiamo scrivere:

>>> for prop in dir(button.props):
...     print(prop)
...     
action_name
action_target
always_show_image
app_paintable
...

>>> button.props.label
'Ok'
>>> button.props.label = "Click"
>>> button.props.label
'Click'

Utilizzo di properties esistenti

La classe GObject.GObject fornisce le seguenti funzioni per gestire le properties esistenti:
GObject.GObject.get_property() e GObject.GObject.set_property().
Come accennato in precedenza, alcune properties hanno funzioni ad esse dedicate (getter e setter).
Ad esempio per la property “label” di un Button, ci sono 2 funzioni, la getter Gtk.Button.get_label()
e la setter Gtk.Button.set_label(label).

Creare nuove properties

Una property viene definita tramite “name” e “type”.
Nonostante Python stesso sia tipizzato dinamicamente, non potremo modificare una property,
una volta che questa è stata definita. Per creare una property utilizzaiamo GObject.Property:

from gi.repository import GObject

class CustomObject(GObject.GObject):

    pr_string = GObject.Property(type=str, default='bancaldo')
    pr_float = GObject.Property(type=float)
    def __init__(self):
        super().__init__()

Possiamo decidere che una property sia “readable”, “writable”, o “readwrite”.
Per farlo è necessario settare alcuni flags, in fase di definizione della property stessa.
Queste Flags sono:
GObject.ParamFlags.READABLE: solo accesso in lettura;
GObject.ParamFlags.WRITABLE: solo accesso in scrittura;
GObject.ParamFlags.READWRITE: property pubblica, con accesso in
lettura e scrittura.

pr_1 = GObject.Property(type=str, flags = GObject.ParamFlags.READABLE) # non scrivibile
pr_2 = GObject.Property(type=str, flags = GObject.ParamFlags.WRITABLE) # non leggibile

Per definire una nuova property read-only possiamo utilizzare un metodo
con decoratore GObject.Property:

from gi.repository import GObject

class CustomObject(GObject.GObject):

    def __init__(self):
        super().__init__()

    @GObject.Property
    def readonly(self):
        return 'This is read-only property.'

Per accedere a questa property:

>>> myobj = CustomObject()
>>> myobj.readonly
'This is read-only property.'
>>> myobj.get_property("readonly")
'This is read-only property.'

La API di GObject.Property è molto simile alla built-in property() di python.
Possiamo creare i setter in maniera simile:

class CustomObject(GObject.Object):
    value = 0

    @GObject.Property
    def readonly(self):
        'Read only property.'
        return 1

    @GObject.Property(type=int)
    def my_int(self):
        'Read-write integer property.'
        return self.value

    @my_int.setter
    def my_int(self, value):
        self.value = value
>>> myobj = CustomObject()
>>> myobj.readonly
1
>>> myobj.my_int
0
>>> myobj.my_int = 2
>>> myobj.my_int
2

Ad esempio è possibile anche definire un valore minimo e massimo per i numeri:

from gi.repository import GObject

class CustomObject(GObject.GObject):

    __gproperties__ = {
        "int-prop": (int, # type
                     "integer prop", # nick
                     "A property that contains an integer", # desc
                     1, # min
                     5, # max
                     2, # default
                     GObject.ParamFlags.READWRITE # flags
                    ),
    }

    def __init__(self):
        super().__init__()
        self.int_prop = 2

    def do_get_property(self, prop):
        return self.int_prop

    def do_set_property(self, prop, value):
        self.int_prop = value

Le properties, come si nota, vanno definite nel dizionario __gproperties__ di
GObject.GObject, e gestite con i metodi:
do_get_property e do_set_property.

>>> obj = CustomObject()
>>> obj.get_property("int-prop")
2

>>> obj.set_property("int-prop", 10)
<string>:1: Warning: value "10" of type 'gint' is invalid or out of range for property 'int-prop' of type 'gint'
>>> obj.set_property("int-prop", -2)
<string>:1: Warning: value "-2" of type 'gint' is invalid or out of range for property 'int-prop' of type 'gint'
>>> obj.set_property("int-prop", 4)
>>> obj.get_property("int-prop")
4

se la property richiesta non è esistente, ovvero non fa parte del dizionario __gproperties__,
verrà sollevata una eccezione TypeError:

obj = CustomObject()
myprop = "int-propp"
try:
    obj.get_property(myprop)
except TypeError:
    print("ERROR: %s property not found!" % myprop)
    
ERROR: int-propp property not found!

Segnale modifica property

Quando una property viene modificata, viene emesso un segnale il cui nome è
“notify::property-name”.
E’ quindi possibile connettere il segnale di modifica della property, del nostro oggetto, ad una
callback, con il metodo connect(signal, callback):

>>> def on_notify_int_prop(obj, string):
...     print("INFO: 'int-prop' property changed")
...     
>>> obj.connect("notify::int-prop", on_notify_int_prop)
1
>>> obj.set_property("int-prop", 2)
INFO: 'int-prop' property changed

Nota:
Per convenzione il nome della callback da usare con il metodo connect(), per un segnale
“notify::property-name”, sarà “on_notify_property_name”.

API

Ricapitolando vediamo i metodi principali della classe GObject.GObject:

get_property(property_name)

Recupera il valore di una property di nome “property_name”.

set_property(property_name, value)

Setta il valore di una property di nome “property_name” al valore “value”, passato come argomento.

emit(signal_name, …)

Emette un segnale di nome “signal_name”. Gli argomenti opzionali da passare al segnale devono
seguire “signal_name”. Se ad esempio il nostro segnale è di tipo (int,), viene emesso come segue:

self.emit(signal_name, 42)

freeze_notify()

Questo metodo congela tutti i segnali “notify::” (che vengono cioè emessi ogni volta che una
property viene modificata), finchè non viene invocato il metodo thaw_notify().
E’ consigliato utilizzare il metodo freeze_notify(), all’interno di un
with statement, in modo da assicurarsi che il metodo thaw_notify(),
sia invocato implicitamente alla fine del blocco with utilizzato.

with an_object.freeze_notify():
    # Do stuff
    ...

thaw_notify()

“scongela” tutti i segnali “notify::” che erano stati congelati dal metodo freeze_notify().
Come detto in precedenza non è consigliato utilizzare thaw_notify() esplicitamente,
ma utilizzare freeze_notify() con un with statement.

handler_block(handler_id)

Blocca un handler di un’istanza che non potrà essere chiamato durante l’emissione di un segnale
che lo coinvolga, fino a chè non sarà chiamato il complementare handler_unblock().
Questo blocco significa disattivare l’handler temporaneamente.
Un signal-handler deve essere sbloccato lo stesso numero di volte che è stato bloccato, prima di
tornare attivo. Come per freeze/thaw, è raccomandato utilizzare handler_block()
all’interno di un with statement, che, raggiunta la fine, chiamerà implicitamente
handler_unblock():

with an_object.handler_block(handler_id):
    # Do stuff
    ...

handler_unblock(handler_id)

Annulla l’effetto di handler_block(). Un handler boccato viene saltato durante
l’emissione di un segnale e non sarà invocato fino a quando non verrà sbloccato lo stesso numero
di volte per cui è stato bloccato.
Come già visto non è consigliabile chiamare handler_unblock() esplicitamente,
ma usare handler_block() all’interno di un with statement.

__gsignals__

Un dizionario dove le classi ereditate possono definire nuovi segnali.
Ogni elemento nel dizionario è un nuovo segnale. La chiave è il nome del segnale, il valore
invece, una tupla con la forma (GObject.SIGNAL_RUN_FIRST, None, (int,))
GObject.SIGNAL_RUN_FIRST: può essere rimpiazzato con
GObject.SIGNAL_RUN_LAST o GObject.SIGNAL_RUN_CLEANUP.
None: ciò che ritorna quel segnale;
(int,): lista dei parametri del segnale, deve finire con virgola.

__gproperties__

Un dizionario dove è possibile definire le properties del nostro oggetto.
Questo non è il metodo raccomandato per definire nuove proprietà, ma è utile per aggiungere
caratteristiche supplementari come valore minimo e massimo ammessi.
La chiave è il nome della property, mentre il valore è una tupla che descrive tale property.
Il numero degli elementi di questa tupa è variabile e dipende dal primo elemento, ma la tuple
conterrà sempre, almeno i seguenti elementi in questo ordine:

property’s type esmepio int, float,
property’s nick name: una breve descrizione della property
(utilizzato da programmi come Glade);
property’s description (blurb): un’altra stringa con descrizione
più esaustiva (usata da Glade e programmi simili);
property’s flags: indica il tipo di accesso alla property,
GObject.PARAM_READABLE, GObject.PARAM_WRITABLE o GObject.PARAM_READWRITE;
In base al primo elemento della tupla (property’s type), possono seguire altri elementi.
gli scenari sono:
– il primo elemento è di tipo bool o str, l’elemento successivo sarà:
default: valore di default della property;
– il type è int o float:
minimum: il minimo valore accettato,
maximum: il valore massimo accettato;
default: valore di default della property;
– il type non è nessuno di essi: nessun extra elemento

GObject.SIGNAL_RUN_FIRST

Invoca il method handler dell’oggetto nel primo stadio dell’emissione;

GObject.SIGNAL_RUN_LAST

Invoca il method handler dell’oggetto nel terzo stadio dell’emissione;

GObject.SIGNAL_RUN_CLEANUP

Invoca il method handler dell’oggetto nell’ultimo stadio dell’emissione;

GObject.ParamFlags.READABLE

La property è in sola lettura e non soggetta a modifica;

GObject.ParamFlags.WRITABLE

La property è in scrittura e quindi soggetta a modifica;

GObject.ParamFlags.READWRITE

La property è in lettura e scrittura (pubblica);

Unicode

Da Python3.0, tutte le stringhe sono memorizzate come Unicode in un’istanza di str type.
Le Encoded strings, d’altro canto, sono rappresentate come binary data, in forma di istanza di byte type.
In parole povere str fa riferimento a text, mentre bytes
fa riferimento a data.
Per convertire da str a bytes, usare str.encode(),
da bytes a str, usare bytes.decode().
Inoltre non è più possibile mixare strings con encoded strings:

>>> s = "àòè"
>>> s.encode()
b'\xc3\xa0\xc3\xb2\xc3\xa8'
>>> s.encode("utf-8")
b'\xc3\xa0\xc3\xb2\xc3\xa8'
>>> b = s.encode("utf-8")
>>> b
b'\xc3\xa0\xc3\xb2\xc3\xa8'
>>> b.decode("utf-8")
'àòè'

In GTK+ le cose sono molto più comode perchè PyGObject fa automaticamente l’encode/decode da/in
UTF-8, quando passeremo una stringa ad un metodo, o un metodo ritornerà una stringa.
In ogni caso verrà sempre rappresentato un testo/stringa come instanza di str:

>>> import gi
>>> gi.require_version("Gtk", "3.0")
>>> from gi.repository import Gtk
>>> label = Gtk.Label()
>>> text = "\xc3\xa7\xc3\xa7\xc3\xa7"
>>> label.set_text(text)
>>> txt = label.get_text()
>>> type(txt), txt
(<class 'str'>, 'ççç')
>>> txt == text
True

link di riferimento:
torna all’indice degli appunti
python gtk3 tutorial
Gtk3

Categorie:Gtk3, PyGObject, python Tag: , ,
I commenti sono chiusi.