Hlavní navigace

Programování pro X Window System (3)

Martin Beran

V dnešním dílu si ukážeme hierarchické uspořádání widgetů. Dále probereme základní manipulaci s widgety - vytvoření, zobrazení, zrušení. S tím souvisí i správa paměti pomocí počítání referencí. Nakonec se podíváme na kontejnerové widgety, především na boxy a tabulky.

Hierarchické uspořádání widgetů a tříd

Widgety v GTK+ jsou hierarchicky uspořádané. Existují dvě hierarchie – widgetů a tříd. Widgety v programu jsou uspořádány do stromové struktury. V kořeni stromu je vždy top-level okno. Je to většinou hlavní okno aplikace nebo dialog. Program má tolik stromů widgetů, kolik má top-level oken. V uzlech stromu jsou widgety uvnitř top-level okna. Všechny uzly kromě listů jsou takzvané kontejnery, tedy widgety, do nichž lze vložit další jeden nebo více widgetů. Vzhled oken na obrazovce přímo odpovídá hierarchii widgetů. Okna potomků leží vždy uvnitř okna rodičovského widgetu. Na obr. 1 je příklad top-level okna programu a odpovídající strom widgetů je na obr2.

top-level okno
Obr. 1: Příklad top-level oknastrom widgetů Obr. 2: Příklad stromu widgetů

Obrázek 2 není úplně přesný, protože objekty typu GtkButton a GtkCheckButton nejsou pravé listy. Ve skutečnosti jsou to kontejnerové widgety, které obvykle obsahují nápis typuGtkLabel.

Hierarchie widgetů je v každém programu jiná a dynamicky se mění podle toho, jak vznikají a zanikají jednotlivé widgety. Druhý typ hierarchie je daný vztahy dědičnosti mezi třídami widgetů. Společným předkem všech tříd je GObject definovaný ve stejnojmenné knihovně. V knihovně GTK+ je od GObject odvozená třída GtkObject a z ní dále GtkWidget, což je bázová třída pro všechny widgety. Základ stromu widgetů je vždy stejný, odlišnosti vznikají, pokud program definuje vlastní nové typy widgetů. Malý výřez hierarchie typů widgetů je na obr. 3.

strom tříd
Obr. 3: Část stromu tříd widgetů

Manipulace s widgety

Pro každou třídu existuje jedna nebo více funkcí pro vytvoření objektů této třídy. Např. prázdné tlačítko se vytvoří voláním gtk_button_new. Funkce gtk_button_new_wit­h_label vyrobí tlačítko s nápisem. Další možnosti jsou tlačítko s akcelerátorem

gtk_button_new_wit­h_mnemonic nebo předdefinované tlačítko s nápisem a ikonou gtk_button_new_from­_stock. Zavoláním funkce pro vytvoření widgetu vznikne objekt uvnitř programu – X klienta. Aby byl widget vidět na obrazovce, je třeba ho realizovat, tj. vytvořit pro něj GDK okno a dále X okno, které existuje v X serveru. Realizaci provádí funkce gtk_widget_realize. Následně je widget zobrazen pomocí gtk_widget_show, popř. gtk_widget_show_all zobrazí widget včetně všech potomků. Tyto dvě funkce se postarají i o vytvoření X oken, proto není nutné explicitně volat gtk_widget_realize. Existující widget lze opakovaně zobrazovat a schovávat pomocí

gtk_widget_show a gtk_widget_hide. Existenci widgetu – včetně GDK a X okna – ukončí funkce gtk_widget_destroy.

GTK+ používá při správě paměti počítání referencí na objekty (reference counting). U každého objektu se pamatuje počet referencí na něj, tedy počet ukazatelů na objekt. Při uložení odkazu na objekt do nějakého ukazatele je třeba voláním funkce g_object_ref zvýšit o 1 počet referencí. Naopak, pokud se hodnota ukazatele změní, je nutné snížit počet referencí pomocí g_object_unref. Když počet referencí klesne na nulu, znamená to, že objekt nadále není dostupný přes žádný ukazatel. GTK+ takový objekt automaticky zruší. Reference counting nefunguje dobře pro cyklické datové struktury. Jestliže existuje několik objektů, které tvoří cyklus vzájemných odkazů, počet referencí na každý objekt v cyklu je vždy aspoň jedna, i když „zvenku“ na žádný z objektů neexistuje žádný odkaz a datová struktura jako celek je nedostupná. V takovém případě je možné některé ukazatele nezahrnovat do počtu referencí. Potom ovšem při zrušení objektu zbude neplatný ukazatel. Lepším řešením je používání tzv. slabých referencí (weak reference). Při nastavení hodnoty ukazatele se volág_object_we­akref. Tím se zaregistruje funkce, která bude automaticky zavolána při zrušení objektu. Tato funkce může např. vynulovat ukazatel. Při změně hodnoty ukazatele je možné odregistrovat slabou referenci voláním g_object_weakunref.

Mechanismus počítání referencí poněkud komplikují tzv. plovoucí reference. Funkce pro vytvoření widgetu vrátí ukazatel na objekt, který představuje jednu referenci. Obvykle je nový widget následně vložen do kontejneru, který přidá další referenci. Při zrušení kontejneru většinou chceme automaticky zrušit i všechny v něm vložené widgety. Abychom nemuseli vždy po vložení widgetu do kontejneru volat g_object_unref, je první reference na objekt vytvořena jako plovoucí (floating). Kontejner tuto referenci zruší pomocí gtk_object_sink poté, co přidá vlastní referenci. Je třeba dávat pozor na to, že při vyjmutí widgetu z kontejneru (funkcígtk_con­tainer_remove) zruší kontejner svou referenci. Pokud nechceme, aby se vyjmutý widget zrušil, musíme před voláním gtk_container_re­move použít g_object_ref. Takto vytvořená reference už není plovoucí, proto zůstane platná i při opětovném vložení widgetu do kontejneru. Plovoucí je vždy pouze první reference na objekt. První volání gtk_object_sink ji odstraní, opakovaná volání této funkce pro stejný objekt už nedělají nic.

Kontejnery

Kontejnery jsou widgety, do nichž lze vkládat jiné widgety (včetně dalších kontejnerů). Všechny kontejnery vycházejí ze společného předka GtkContainer. Dají se rozdělit do dvou hlavních skupin. Jednu skupinu tvoří třídy odvozené z GtkBin a vyznačují se tím, že mohou obsahovat maximálně jeden synovský widget, přístupný přes položku GtkBin.child. Asi nejdůležitějším zástupcem této skupiny je třída GtkWindow, tedy top-level okno. Do kontejnerů z druhé skupiny lze vložit více widgetů. Jejími nejpoužívanějšími členy jsou boxy (GtkHBox a GtkVBox) obsahující widgety uspořádané do řádku nebo sloupce a tabulky (GtkTable), které umísťují synovské widgety do dvojrozměrné mřížky.

Kontejnery zajišťují automatické přidělování místa pro jednotlivé widgety. Než se zobrazí top-level okno, zjistí GTK+ požadovanou velikost okna a všech jeho potomků. K tomu se používá metoda gtk_widget_si­ze_request, která vrátí minimální widgetem požadovanou velikost. Kontejnery mají tuto metodu předefinovanou tak, že se nejprve zavolá pro všechny synovské widgety a z jejich požadavků kontejner spočítá, jak musí být velký, aby se do něj všichni potomci vešli. Např. horizontální box vrátí požadovanou šířku rovnou součtu šířek synovských widgetů plus velikost mezer podle parametrů boxu a výšku rovnou maximu výšek synovských widgetů. Následně GTK+ ve spolupráci s window managerem nastaví velikost top-level okna. Skutečná velikost se může lišit od požadované, např. pokud uživatel pomocí window manageru změnil rozměry okna. Nakonec GTK+ oznámí oknu skutečně přidělenou velikost voláním metody gtk_widget_si­ze_allocate. V kontejnerech tato metoda rozdělí oblast widgetu mezi jednotlivé synovské widgety a výsledek rozdělení jim oznámí opět voláním gtk_widget_si­ze_allocate.

Některé funkce jsou společné pro všechny kontejnery a jsou definovány ve třídě GtkContainer.

void gtk_container_ad­d(GtkContainer *container, GtkWidget *widget);
Vloží widget do kontejneru. Obvykle se používá pro kontejnery odvozené z GtkBin. Každá kontejnerová třída má navíc definované vlastní funkce pro vkládání widgetů.
void gtk_container_re­move(GtkConta­iner *container, GtkWidget *widget);
Odebere widget z kontejneru. Sníží o 1 počet referencí na widget, takže pokud jedinou referenci držel kontejner, bude widget zrušen.
void gtk_container_set_bor­der_width(GtkCon­tainer *container, guint border_width);
Nastaví šířku okraje (v pixelech), tj. oblasti, do níž nebudou zasahovat vložené widgety.
void gtk_widget_set_si­ze_request(GtkWid­get *widget, gint width, gint height);
Nastaví minimální velikost widgetu. Během procesu přidělování místa widgetům se bude rodičovský kontejner snažit nezmenšit widget pod tuto velikost. Funkce je definovaná pro všechny widgety, nejen kontejnery.

Horizontální (GtkHBox) a vertikální (GtkVBox) boxy jsou odvozené ze společného předka GtkBox. Pro vytvoření boxu slouží funkce

GtkWidget* gtk_hbox_new(gboolean homogeneous, gint spacing);
GtkWidget* gtk_vbox_new(gboolean homogeneous, gint spacing); 

Parametr homogeneous říká, zda všechny synovské hodnoty budou stejně velké. Parametr spacing nastavuje velikost mezery (v pixelech) ponechané mezi každou dvojicí widgetů. Pro vkládání widgetů do boxů jsou definovány funkce

void gtk_box_pack_start(GtkBox *box, GtkWidget *child, gboolean expand,
                        gboolean fill, guint padding);
void gtk_box_pack_end(GtkBox *box, GtkWidget *child, gboolean expand,
                      gboolean fill, guint padding); 

První z nich vkládá widgety od levého/horního okraje boxu směrem doprava/dolů, druhá vkládá od pravého/dolního okraje směrem doleva/nahoru. Parametr expand říká, zda widget může zabírat více místa, než je jeho minimální velikost. Pokud je nastaven na TRUE a fill je takéTRUE, bude widget zvětšen tak, aby zaplnil veškeré dostupné místo. Při fill rovném FALSE nebude widget zvětšen a případné nadbytečné místo se stane součástí mezer kolem widgetu. Parametr padding definuje šířku prázdného místa po obou stranách widgetu. Parametry ovlivňují pouze jeden rozměr vkládaných widgetů. Výška všech synů hboxu, resp. šířka synů vboxu, je stejná a je rovna výšce (šířce) boxu zmenšené o okraj.

Význam jednotlivých parametrů boxu je zobrazen na obr. 4. Pro experimentování s boxy lze využít program boxes.c, který nejdříve přečte ze standardního vstupu specifikace boxu a jeho synovských widgetů a následně box zobrazí.

box
Obr. 4: Parametry boxu

Tabulka (GtkTable) se vytvoří funkcí

GtkWidget* gtk_table_new(guint rows, guint columns, gboolean homogeneous); 

Parametry definují počet řádků a sloupců a to, zda všechna políčka tabulky mají být stejně velká. Při vkládání widgetů pomocí

void gtk_table_attach_defaults(GtkTable *table, GtkWidget *widget,
                               guint left_attach, guint right_attach,
                               guint top_attach, guint bottom_attach); 

se definuje, ve kterých sloupcích a řádcích budou ležet jednotlivé strany widgetu. Existuje i funkce gtk_table_attach, která umožňuje specifikovat ještě další parametry. Fungování tabulky si lze představit tak, že sloupce a řádky jsou pevné tyče, které se mohou volně pohybovat, ale nesmí si vyměnit pořadí. Jednotlivé widgety se připevňují svými okraji k tyčím. Každý widget je gumový obdélník, může být libovolně roztažen, ale nedá se stlačit pod svou minimální velikost. Po vložení všech widgetů se tyče představující sloupce a řádky rozmístí tak, aby žádný widget nebyl menší, než je jeho povolené minimum, a aby zároveň žádný widget nebyl větší, než je nezbytně nutné. Na obr. 5 jsou vlevo znázorněny minimální velikosti čtyř widgetů. Vpravo je tabulka, jež vznikne posloupností příkazů

table = gtk_table_new(3, 3, FALSE);
gtk_table_attach_defaults(table, widget1, 0, 3, 0, 1);
gtk_table_attach_defaults(table, widget2, 0, 1, 1, 3);
gtk_table_attach_defaults(table, widget3, 1, 2, 1, 2);
gtk_table_attach_defaults(table, widget4, 2, 3, 2, 3); 

tabulka
Obr. 5: Vytvoření tabulky

Automatické řízení rozměrů a pozic widgetů je výhodné, protože se programátor nemusí zabývat přesným umístěním jednotlivých widgetů. Navíc, když uživatel změní velikost okna, GTK+ na to zareaguje a rozumným způsobem upraví rozmístění widgetů v okně. Pro seznámení se s fungováním kontejnerů je užitečné spustit si program Glade a v něm si vyzkoušet vkládání widgetů do kontejnerů. Jeho velkou výhodou je, že lze interaktivně měnit parametry kontejnerů a okamžitě vidět na obrazovce, jak se změní vzhled okna.

Našli jste v článku chybu?

16. 4. 2004 10:35

Marek (neregistrovaný)

Diky za vynikajici serial - perfektni uroven !!!

Nesouhlasim s predchozim prispevkem, ze je tema probirano prilis do hloubky ! Neni, pokud chce clovek programovat, musi vedet, jak veci pracuji..

Diky moc





15. 4. 2004 17:53

Anselm (neregistrovaný)

Pěknej článek, jen co je pravda. Ale není to trochu moc do hloubky? Já programuju v GTK už celkem dlouho, ale nepamatuju se, že bych někdy použil gtk_widget_realize, gtk_widget_destroy, natož pak cokoliv ze správy paměti... Nováček by mohl nabýt dojmu, že jsou tyto fce běžně potřeba. Možná by příští díl mohl obsahovat něco o tvorbě vlastních widgetů, kde se tyto informace budou hodit...?

Lupa.cz: Kdo pochopí vtip, může jít do ČT vyvíjet weby

Kdo pochopí vtip, může jít do ČT vyvíjet weby

DigiZone.cz: „Black Friday 2016“: závěrečné zhodnocení

„Black Friday 2016“: závěrečné zhodnocení

DigiZone.cz: Flix TV startuje i na Slovensku

Flix TV startuje i na Slovensku

Podnikatel.cz: 1. den EET? Problémy s pokladnami

1. den EET? Problémy s pokladnami

Podnikatel.cz: Přehledná titulka, průvodci, responzivita

Přehledná titulka, průvodci, responzivita

Vitalia.cz: „Připluly“ z Německa a možná obsahují jed

„Připluly“ z Německa a možná obsahují jed

Lupa.cz: Teletext je „internetem hipsterů“

Teletext je „internetem hipsterů“

Vitalia.cz: Jsou čajové sáčky toxické?

Jsou čajové sáčky toxické?

Měšec.cz: mBank cenzuruje, zrušila mFórum

mBank cenzuruje, zrušila mFórum

Vitalia.cz: Paštiky plné masa ho zatím neuživí

Paštiky plné masa ho zatím neuživí

DigiZone.cz: Recenze Westworld: zavraždit a...

Recenze Westworld: zavraždit a...

Podnikatel.cz: Udávání kvůli EET začalo

Udávání kvůli EET začalo

Podnikatel.cz: Podnikatelům dorazí varování od BSA

Podnikatelům dorazí varování od BSA

Lupa.cz: Propustili je z Avastu, už po nich sahá ESET

Propustili je z Avastu, už po nich sahá ESET

Měšec.cz: U levneELEKTRO.cz už reklamaci nevyřídíte

U levneELEKTRO.cz už reklamaci nevyřídíte

Vitalia.cz: Znáte „černý detox“? Ani to nezkoušejte

Znáte „černý detox“? Ani to nezkoušejte

Vitalia.cz: Proč vás každý zubař posílá na dentální hygienu

Proč vás každý zubař posílá na dentální hygienu

Vitalia.cz: Když přijdete o oko, přijdete na rok o řidičák

Když přijdete o oko, přijdete na rok o řidičák

Podnikatel.cz: EET: Totálně nezvládli metodologii projektu

EET: Totálně nezvládli metodologii projektu

Vitalia.cz: I církev dnes vyrábí potraviny

I církev dnes vyrábí potraviny