23. Objekt

GObject är den grundläggande typen som tillhandahåller de gemensamma attributen och metoderna för alla objekttyper i GTK+, Pango och andra bibliotek baserade på GObject. Klassen GObject.GObject tillhandahåller metoder för skapande och förstörande av objekt, åtkomstmetoder för egenskaper samt stöd för signaler.

Detta avsnitt kommer introducera några viktiga aspekter om GObject-implementationen i Python.

23.1. Ärv från GObject.GObject

Ett inhemskt GObject är åtkomligt via GObject.GObject. Det instansieras sällan direkt, vi använder i allmänhet ärvda klasser. En Gtk.Widget är en ärvd klass av ett GObject.GObject. Det kan vara intressant att göra en ärvd klass för att skapa en ny komponent, exempelvis en inställningsdialog.

För att ärva från GObject.GObject måste du anropa GObject.GObject.__init__() i din konstruktor (om klassen exempelvis ärver från Gtk.Button så måste den anropa Gtk.Button.__init__()), som i exemplet nedan:

from gi.repository import GObject

class MyObject(GObject.GObject):

    def __init__(self):
        GObject.GObject.__init__(self)

23.2. Signaler

Signaler ansluter godtyckliga programspecifika händelser till ett valfritt antal lyssnare. Till exempel så tas i GTK+ varje användarhändelse (tangenttryckning eller musrörelse) emot från X-servern och genererar en GTK+-händelse i form av en signalutsändning på en given objektinstans.

Varje signal registreras i typsystemet tillsammans med typen på vilken den kan sändas ut: användare av typen sägs ansluta till signalen på en given typinstans när de registrerar en funktion som ska anropas vid signalutsändning. Användare kan också sända ut signalen själva eller stoppa utsändningen av signalen inifrån en av funktionerna som är anslutna till signalen.

23.2.1. Ta emot signaler

Se Huvudslinga och signaler

23.2.2. Skapa nya signaler

Nya signaler kan skapas genom att lägga till dem till GObject.GObject.__gsignals__, en ordbok:

Då en ny signal skapas kan en metodhanterare också definieras, den kommer anropas varje gång signalen sänds ut. Den kallas do_signalnamn.

class MyObject(GObject.GObject):
    __gsignals__ = {
        'my_signal': (GObject.SIGNAL_RUN_FIRST, None,
                      (int,))
    }

    def do_my_signal(self, arg):
        print("method handler for `my_signal' called with argument", arg)

GObject.SIGNAL_RUN_FIRST indikerar att denna signal kommer anropa objektets metodhanterare (här do_my_signal()) i det första utsändningssteget. Alternativ är GObject.SIGNAL_RUN_LAST (metodhanteraren kommer anropas i det tredje utsändningssteget) och GObject.SIGNAL_RUN_CLEANUP (anropa metodhanteraren i det sista utsändningssteget).

Den andra delen, None, indikerar returtypen för signalen, vanligen None.

(int,) indikerar signalargumenten, här kommer signalen bara ta ett argument, vars typ är int. Typer av argument som krävs av signalen deklareras som en sekvens, här är det en tupel med ett argument.

Signaler kan sändas ut med GObject.GObject.emit():

my_obj.emit("my_signal", 42) # emit the signal "my_signal", with the
                             # argument 42

23.3. Egenskaper

En av de trevliga funktionerna med GObject är dess allmäna get/set-mekanism för objektegenskaper. Varje klass som ärver från GObject.GObject kan definiera nya egenskaper. Varje egenskap har en typ som aldrig ändras (t.ex. str, float, int…). De används exempelvis för Gtk.Button där det finns en egenskap ”label” som innehåller knappens text.

23.3.1. Använd befintliga egenskaper

Klassen GObject.GObject tillhandahåller flera användbara funktioner för att hantera befintliga egenskaper, GObject.GObject.get_property() och GObject.GObject.set_property().

Vissa egenskaper har också avsedda get- och set-funktioner. För egenskapen ”label” för en knapp finns det två funktioner för att erhålla och ställa in den, Gtk.Button.get_label() och Gtk.Button.set_label().

23.3.2. Skapa nya egenskaper

En egenskap definieras med ett namn och en typ. Även om Python i sig är dynamiskt typat så kan du inte ändra typen för en egenskap efter att den definierats. En egenskap kan skapas med GObject.Property.

from gi.repository import GObject

class MyObject(GObject.GObject):

    foo = GObject.Property(type=str, default='bar')
    property_float = GObject.Property(type=float)
    def __init__(self):
        GObject.GObject.__init__(self)

Egenskaper kan också vara skrivskyddade om du vill att några egenskaper ska vara läsbara men inte skrivbara. För att göra detta kan du lägga till några flaggor till egenskapsdefinitionen för att styra läs/skriv-åtkomst. Flaggor är GObject.ParamFlags.READABLE (endast läsåtkomst för extern kod), GObject.ParamFlags.WRITABLE (endast skrivåtkomst), GObject.ParamFlags.READWRITE (öppen):

foo = GObject.Property(type=str, flags = GObject.ParamFlags.READABLE) # not writable
bar = GObject.Property(type=str, flags = GObject.ParamFlags.WRITABLE) # not readable

Du kan också definiera nya skrivskyddade egenskaper med en ny metod dekorerad med GObject.Property:

from gi.repository import GObject

class MyObject(GObject.GObject):

    def __init__(self):
        GObject.GObject.__init__(self)

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

Du kan få denna egenskap med:

my_object = MyObject()
print(my_object.readonly)
print(my_object.get_property("readonly"))

API:t för GObject.Property liknar den inbyggda property(). Du kan skapa egenskapsinställare på ett sätt liknande Python-egenskaper:

class AnotherObject(GObject.Object):
    value = 0

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

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

    @propInt.setter
    def propInt(self, value):
        self.value = value

Det finns också ett sätt att definiera minsta och största värden för tal, med en mer utförlig form:

from gi.repository import GObject

class MyObject(GObject.GObject):

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

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

    def do_get_property(self, prop):
        if prop.name == 'int-prop':
            return self.int_prop
        else:
            raise AttributeError('unknown property %s' % prop.name)

    def do_set_property(self, prop, value):
        if prop.name == 'int-prop':
            self.int_prop = value
        else:
            raise AttributeError('unknown property %s' % prop.name)

Egenskaper måste definieras i GObject.GObject.__gproperties__, en ordbok, och hanteras i do_get_property och do_set_property.

23.3.3. Bevaka egenskaper

När en egenskap ändras sänds en signal ut, vars namn är ”notify::egenskapsnamn”:

my_object = MyObject()

def on_notify_foo(obj, gparamstring):
    print("foo changed")

my_object.connect("notify::foo", on_notify_foo)

my_object.set_property("foo", "bar") # on_notify_foo will be called

Observera att du måste använda det kanoniska egenskapsnamnet då du ansluter till notify-signalerna, som förklaras i GObject.Object.signals.notify(). För en Python-egenskap foo_bar_baz skulle du exempelvis ansluta till signalen notify::foo-bar-baz med

my_object = MyObject()

def on_notify_foo_bar_baz(obj, gparamstring):
    print("foo_bar_baz changed")

my_object.connect("notify::foo-bar-baz", on_notify_foo_bar_baz)

23.4. API

class GObject.GObject
get_property(property_name)

Erhåller ett egenskapsvärde.

set_property(property_name, value)

Ställer in egenskapen property_name till value.

emit(signal_name, ...)

Sänd ut signalen signal_name. Signalargument måste följa, t.ex. om din signal är av typen (int,) så måste den sändas ut med:

self.emit(signal_name, 42)
freeze_notify()

Denna metod fryser alla ”notify::”-signalerna (som sänds ut då någon egenskap ändras) till metoden thaw_notify() anropas.

Det rekommenderas att använda with-satsen vid anrop av freeze_notify(), på det viset säkerställs att thaw_notify() anropas implicit i slutet på blocket:

with an_object.freeze_notify():
    # Do your work here
    ...
thaw_notify()

Tina alla ”notify::”-signalerna som frystes av freeze_notify().

Det rekommenderas att inte anropa thaw_notify() explicit utan anropa freeze_notify() tillsammans med with-satsen.

handler_block(handler_id)

Blockerar en hanterare av en instans så att den inte kommer att anropas under några signalutsändningar om inte handler_unblock() anropas för detta handler_id. Att ”blockera” en signalhanterare betyder därmed att tillfälligt inaktivera den, en signalhanterare behöver avblockeras exakt det antal gånger som den har blockerats innan den blir aktiv igen.

Det rekommenderas att använda handler_block() tillsammans med with-satsen som kommer anropa handler_unblock() implicit i slutet på blocket:

with an_object.handler_block(handler_id):
    # Do your work here
    ...
handler_unblock(handler_id)

Gör effekten av handler_block() ogjord. En blockerad hanterare hoppas över under signalutsändningar och kommer inte anropas förrän den har avblockerats exakt det antal gånger som den tidigare blockerats.

Det rekommenderas att inte anropa handler_unblock() explicit utan anropa handler_block() tillsammans med with-satsen.

__gsignals__

En ordbok där en ärvd klass kan definiera nya signaler.

Varje element i ordboken är en ny signal. Nyckeln är signalnamnet. Värdet är en tupel med formen:

(GObject.SIGNAL_RUN_FIRST, None, (int,))

GObject.SIGNAL_RUN_FIRST kan ersättas med GObject.SIGNAL_RUN_LAST eller GObject.SIGNAL_RUN_CLEANUP. None är returtypen för signalen. (int,) är parametertupeln för signalen.

__gproperties__

Ordboken __gproperties__ är en klassegenskap där du definierar ditt objekts egenskaper. Detta är inte det rekommenderade sättet att skapa nya egenskaper, metoden ovan är mycket kortare. Fördelarna med denna metod är att en egenskap kan definieras med fler inställningar, som minsta eller största värde för tal.

Nyckeln är namnet på egenskapen

Värdet är en tupel som beskriver egenskapen. Antalet element i denna tupel beror på dess första element, men tupeln kommer alltid innehålla åtminstone följande objekt:

Det första elementet är egenskapens typ (t.ex. int, float…).

Det andra elementet är egenskapens smeknamn, vilket är en sträng med en kort beskrivning av egenskapen. Detta används vanligen av program med starka introspektionsförmågor, som den grafiska användargränssnittsbyggaren Glade.

Det tredje är egenskapens beskrivning eller blurb, vilket är en annan sträng med en längre beskrivning av egenskapen. Används också av Glade och liknande program.

Det sista (vilket inte nödvändigtvis är det fjärde som vi kommer se senare) är egenskapens flaggor: GObject.PARAM_READABLE, GObject.PARAM_WRITABLE, GObject.PARAM_READWRITE.

Den absoluta längden på tupeln beror på egenskapstypen (tupelns första element). Därmed har vi följande situationer:

Om typen är bool eller str så är det fjärde elementet standardvärdet för egenskapen.

Om typen är int eller float så är det fjärde elementet det minsta accepterade värdet, det femte elementet det största accepterade värdet, och det sjätte elementet är standardvärdet.

Om typen inte är någon av dessa så finns det inget extra element.

GObject.SIGNAL_RUN_FIRST

Anropa objektets metodhanterare i det första utsändningssteget.

GObject.SIGNAL_RUN_LAST

Anropa objektets metodhanterare i det tredje utsändningssteget.

GObject.SIGNAL_RUN_CLEANUP

Anropa objektets metodhanterare i det sista utsändningssteget.

GObject.ParamFlags.READABLE

Egenskapen är läsbar.

GObject.ParamFlags.WRITABLE

Egenskapen är skrivbar.

GObject.ParamFlags.READWRITE

Egenskapen är läsbar och skrivbar.