Gtk# kezdő

Bevezető: 

Ezt az útmutatót egy Windows-ból jött programozó szemével mutatom be, aki gyakorlott a System.Windows.Forms
programozásában, ugyanakkor nem csak a hasonszőrű embereknek szól,
hiszen hasznos lehet a C/C++ fejlesztőknek is akik a Gtk+-t direktben
programozzák vagy akár Gtk-ban teljesen kezdőknek is.

Írta: Zac Bowling
Forrás: http://www.mono-project.com/GtkSharpBeginnersGuide

Ezt az útmutatót egy Windows-ból jött programozó szemével mutatom be, aki gyakorlott a System.Windows.Forms
programozásában, ugyanakkor nem csak a hasonszőrű embereknek szól,
hiszen hasznos lehet a C/C++ fejlesztőknek is akik a Gtk+-t direktben
programozzák vagy akár Gtk-ban teljesen kezdőknek is.

Bevezetés

Mi is az a GTK#?

A válasz a kérdésre talán sokkal jobban megválaszolásra került a
Gtk# oldalán. Legegyszerűbben: egy burkolat a Gimp Toolkit-en vagy
GTK+-on, a keresztplatformos GUI keretrendszeren.
Fut bármely X szerveren, direkt Framebuffer-en vagy éppen a natív
Windows felhasználói felületen. Kis túlzással fut mindenen, de Linuxon
néz ki a legjobban, rendben van Windowson és mondjuk úgy, hogy
működőképes MacOSX-en. A GTK+ több éve elérhető, így elég stabil és jól
letesztelt, valamint majd minden asztali Linux-on megtalálható.

Az egyik legnagyobb félreértés, hogy a Gtk#-hoz szükség van a
Mono-ra. Ez távol áll az igazságtól. Amire a GTK#-nak szüksége van az
egy .NET kompatibilis futtató környezet. Az általam ismert futtató
környezetek az MS.NET, a Mono és a DotGNU. A GTK# tökéletesen tesztelve lett MS.NET-el
és Mono-val egyaránt. Ezidáig nem teszteltem DotGNU-val, de amennyiben
a futtató környezet 100 %-ig .NET kompatibilis, úgy kell, hogy működjön
DotGNU-val is. Ez tehát azt jelenti, hogy amennyiben az alkalmazásodat
GTK#-ban fejleszted és eltökélt szándékod, hogy az fusson Windowson is
akkor választhatsz, hogy az alkalmazást csak GTK#-al fordítod és a
Microsoft futtatókörnyezetével futtatod vagy a Windows-os Mono
futtatókörnyezetre támaszkodsz ugyanilyen módon.

A GTK# a megfelelő eszközkészlet?

Nézz utána a GUI eszközkészleteknek, hogy
eldöntsd. Jóllehet a Gtk# a kedvencem és szolgáltatásokban nagyon
gazdag, néha a GTK# nem pont az az eszköz melyet keresel mikor az
alkamazásodat tervezed és egyébként sem akarom erőltetni.

Telepítés

GTK# letöltése/telepítése

Az első dolog a GTK# letöltése és telepítése.

Linux, MacOSX, FreeBSD és mások: Legelőször nézz utána, hogy a
disztribúciódhoz létezik-e hivatalos GTK# csomag vagy Mono csomag.
Amennyiben nem, akkor látogass el a Letöltések oldalra, hogy letölthesd
a számodra legideálisabb csomagot. Legrosszabb esetben kénytelen leszel
saját magadnak lefordítani.

Windows: amennyiben Windows-on dolgozol, töltsd le az integrált
telepítőt a letöltési oldalról. Szedd le a Gtk# integrációs csomagot is
Visual Studióhoz, amely lehetővé teszi a GTK# használatát Mono nélkül.

Én a GTK# 2.0-t (az írás idejében v1.9.3) amely a GTK+ 2.4-en
alapul. Választhatod a GTK# 1.0-t (GTK+ 2.2 alapú) is, de a GTK# 2.0
használata erősen javasolt.

Betekintés a GTK# könyvtárakba

A GTK# különböző csomagokból épül fel.

  • Gtk# - Az ablakozó és vezérlő eszközkeretrendszer alapja.
  • Pango# - A GTK# betűkészlet megjelenítő csomagja.
  • Gdk# - Pimitív absztrakt grafikus keretrendszer az ablakozáshoz.
  • GLib# - Egy egész csomag konstans és kisegítő funkció. Ezek
    legtöbbjére nincs is szükség, hiszen implementálva van a .NET
    keretrendszerben.
  • Gda# - Általános adat elérési könyvtátak.
  • GConf# - Hasonló a Windows-os registry-hez, egyszerű XML alapú beállítástároló. (Nincs Win32 port)
  • GtkHTML# - Egyszerű HTML megjelenítő eszköz GTK+-hoz. (Nincs Win32 port)
  • GnomeVFS# - Hozzáférést szolgáltat a MIME típusinformációkhoz, ikonokhoz, ikon listákhoz és hasonlókhoz Gnome-on. (Nincs Win32 port)
  • Glade# - XML alapú felület leíró motor GTK+-hoz.
  • vte# - Virtuális Terminál Emuláció. (Nincs Win32 port)
  • libart# - kapcsoló a libart-hoz grafikák betöltéséhez, rajzoláshoz stb.
  • librsvg# - SVG grafikus könyvtár
  • Atk# - hozzáférhetőség keretrendszer Gtk#-hoz.
  • Gnome# - Gnome elérő könyvtárak. (Nincs Win32 port)
  • GnomeDB# - Gnome adabázis elérő könyvtárak. (Nincs Win32 port)
  • GtkDotNet - Gdk grafikus környezetet és System.Drawing környezet között oda-vissza konvertáló könyvtárak.

Vannak más könyvtárak is melyeket gyakran a Gtk# keretrendszer
részeként tudatosítunk - pl. Gecko#, Gtksourceview#, Gsf#, Guile# és
Gst# - de nincsenek a Gtk# kiadásokhoz csomagolva, de a legtöbb esetben
ugyanazon csata résztvevői. Ezek nagyon hasznosak és speciális dolgokat
végeznek, de jelenleg nem nagyon érintjük őket.

Gtk# vagy Glade#

Néha, mikor a fejlesztő nekikezd a programozásnak, összezavarodik,
hogy ugyan melyiket használja, a Gtk#-ot vagy a Glade#-ot. Ehhez
azonban meg kell érteni, hogy mi is az a Glade# és mi is az a Gtk#. A
Gtk# az ablakozó és widget rendszer magja (a widget olyan mint a
control az SWF-ben). A Glade# a Gtk#-ból öröklődik, tehát
tulajdonképpen egy részhalmaza a Gtk#-nak, de autómatikusan tervezi meg
a felületet egy XML alapú erőforrásfile-ból. Az XML file a WYSIWYG gui szerkesztő glade eszközzel generálható.

A legtöbb esetben a Glade# tökéletes eszköz. Rengeteg időt lehet
vele megtakarítani azáltal, hogy maga generálja a kódot, és a felület a
jövőben is könnyen módosítható lesz. A probléma attol függ, hogy mit
szeretnénk megvalósítnai, mivel a Glade# könnyen idegesítővé válhat
azáltal, hogy néha lehetetlen vele magoldani azt amit igazából
szeretnénk, különösen akkor, ha az ablak valami különleges dolgot
csinál (elemek elrejtése, részek dinamikus betöltése, widget-ek
szülőjének módosítása stb.) Igazából csak a gyakorlat segíthet abban,
hogy eldöntsük, hogy adott esetben melyiket érdemes használni.

Az első GTK# alkalmazás

Helyezkedj el kényelmesen

Helyezkedjünk el kényelmesen. Vegyönk magunkhoz egy kis üdítőt
(esetleg kávét - a fordító) és tegyünk be valami kellemes zenét.
Rendben, készen is állunk!

Könyvtárak és file-ok előkészítése

Először is el kell készítenünk egy könyvtára a mi kis projektünknek.
(Windows felhasználóknak: kerüld a szóközök használatát a
könyvtárnevekben a későbbi fejfájás elkerülése érdekében!) Ezután
nyissunk egy shell-t. (Windows-on nyisd meg a Start menüt, majd
“Programs→Mono 1.x.x→Mono Command Prompt”. Ezáltal az elérési utak
megfelelőek lesznek, szóval nem kell semmi extrát sem csinálni.) Ezután
menjünk az újonnan létrehozott mappába. Még sokszor fogjuk ezt a
shell-t használni, úgyhogy maradjon aktív a háttérben. Most menjünk
tovább, indítsuk el kedvenc editorunkat (MonoDevelop, vi, emacs,
notepad stb.) majd állítsunk be egy üres projektet (amennyiben ez
kivitelezhető az adott editorral) és készítsünk egy üres file-t.
Mentsük el a file-t “helloworld.cs” néven.

Szerkesszük meg

Feltételezem, hogy már rendelkezel némi C# ismerettel, így a
következők nem jelentenek újdonságot. Készítenünk kell egy hivatkozást
a Gtk#-ra, valamint csinálunk egy új osztályt és egy belépési pontot az
alkalmazásunknak. Ennek valahogy így kell kinéznie:

  1. using System; using Gtk; public class GtkHelloWorld { public static void Main() { Console.WriteLine("HelloWorld"); } }

Ennek ismerősnek kell lennie. Ezek után használjuk a parancssori
fordítót, miután elmentettük a kódot. Menjünk vissza a korábban nyitott
shell-be és futtassuk a következő parancsot:

mcs -pkg:gtk-sharp-2.0 helloword.cs

A rendszer figyelmeztethet, hogy a “-pkg:” nem szokványos paraméter,
ha csc fordítót Window-on használjuk. Ez nem létezik a csc-ben mert a
Mono a Linux világából jön. Ez azt csinálja, hogy utánnanéz egy csomag
konfigurációs file-nak ez alatt a név alatt. A csomag konfigurációs
könyvtárban van egy file “gtk-sharp-2.0.pc” néven, amely - sok egyéb
mellett - információt tartalmaz arról, hogy hol találhatóak a csomaghoz
tartozó library-k. Ezáltal nem szükséges kézzel beírnünk a
következőket: “-r:gtk-sharp-2.0.dll -r:atk-sharp-2.0.dll
-r:pango-sharp-2.0.dll ….”.

gui hozzáadása

Most térjünk vissza a kódunkhoz és töröljük ki a “Console.WriteLine”
utasítást. Az első dolog, hogy készíteni fogunk egy új ablakot. Ezt úgy
csináljuk, hogy hozzáadunk egy új ablak utasítást valamint egy
alkalmazás blokkot (hogy elinduljon a fő szál). Valahogy így:

  1. using System; using Gtk; public class GtkHelloWorld { public static void Main() { Application.Init(); //Create
    the Window Window myWin = new Window("My first GTK#
    Application!"); myWin.Resize(200,200); //Create a
    label and put some text in it. Label myLabel = new Label();
    myLabel.Text = "Hello World!!!!"; //Add the label to the
    form myWin.Add(myLabel); //Show Everything
    myWin.ShowAll(); Application.Run(); } }

Ezután lefordítjuk az elkészült kódot ugyanúgy ahogyan legutóbb is
tettük, majd futtatjuk a `mono HelloWorld.exe` paranccsal, melynek
hatására valami ilyesmit kell kapnunk:

Ugye milyen egyszerű? Az első dolog ami bizonyára feltűnt, ha eddig System.Windows. Forms-t használtál, hogy nem adtunk meg igazítást a Label-nek. Például nem mondtunk, hogy myLabel.Left = 100 vagy myLabel.Width = 200 vagy bármi hasonlót, egyszerűen csak hozzáadtuk a Label-t a Form-hoz: myWin.Add(…). Ez azért van mert a Gtk.Window egy Widget, amely a Bin osztályból öröklődik. A másik érdekes dolog lehet, hogy mit jelent az Application.Init() és Application.Run(). Ez sok szempontból ugyanaz mint az Application.Run() a System.Windows.Forms-ban. Normál esetben, ha az alkalmazás az összes kódot lefuttatta a fő szálban, akkor kilépne. A példában a “ShowAll()”-tól semmi sem gátolná a program további futását, tehát az Application.Run() nélkül befejeződne. Az Application.Init() mondja meg a futtatókörnyezetnek, hogy figyeljen bármely Gtk.Windows indítására, majd a Run()
metódus meghívását követően elinful a fő ciklus ezeken az ablakokon. Ez
a fő ciklus tartja “életben” az alkalmazást mindaddig, amig az összes
ablak be nem csukódik. További információk a Monodoc-ban találhatóak az
Application objektumról.

Pakoljuk tele az ablakot

Most talán azt kérdezed magadtól, “Hogyan lehetne több Widget-et
adni egy ablakhoz, ha az csak egy Widgetet tud tartalmazni?”. Nos
ahogyan korábban mondtuk a Window egy widget, egy egyszerű widget
tárolóval. Vannak azonban más widgetek, melyek képesek több widget-et
tartalmazni egy időben. Néhány ezek közül a Gtk.Bin tároló widget-ből
vagy a container widgetből közvetlenül öröklődik. A Bin is a
container-ből öröklődik mint minden más tároló is, de a Bin csak egy
widget-et tud tárolni.

Ahhoz hogy több widget is megjelenjen az ablakunkon, ahhoz azok
közül a widgetek közül kell valamelyiket használnunk, amelyik képes
több widget megjelenítésére. Rengeteg olyan vezérlőelem van amely képes
erre, jelen esetben azonban elegendő, ha megismerkedünk a HBox-al a
VBox-al és esetleg a Table-el.

Események hozzáadása

Minden osztály mely a Widget-ből van származtatva, birtokol egy nagy
csokor eseményt melyekhez saját metódusokat csatolhatunk. Ezek a
következők:

ButtonPressEvent
  • ButtonReleaseEvent
  • ScrollEvent
  • MotionNotifyEvent
  • DeleteEvent
  • DestroyEvent
  • ExposeEvent
  • KeyPressEvent
  • KeyReleaseEvent
  • EnterNotifyEvent
  • LeaveNotifyEvent
  • ConfigureEvent
  • FocusInEvent
  • FocusOutEvent
  • MapEvent
  • UnmapEvent
  • PropertyNotifyEvent
  • SelectionClearEvent
  • SelectionRequestEvent
  • SelectionNotifyEvent
  • ProximityInEvent
  • ProximityOutEvent
  • VisibilityNotifyEvent
  • ClientEvent
  • NoExposeEvent
  • WindowStateEvent

Ahhoz, hogy egy eljárást csatoljunk a kiválasztott eseményhez,
egyszerűen csak hozzá kell csatolnuk az eseményt ahhoz a metódushoz
melyet akkor akarunk futtatni amikor a kívánt esemény bekövetkezik. Ez
az eljárás az esemény típusától függően némiképp eltérő lehet, de
általánosan így néz ki:

public static void HandlerMetod(object obj, EventArgs args)

Néhány esetben a változások az eljárásban magukkal vonják a
változásokat az EventArgs paraméterben is. Rendszerint ezek az
EventArgs osztályból származtatott változások. Például, hogy
lekezeljünk egy eseményt - melyet egy gomb egérrel történt lenyomásával
idéztünk elő - a következő eljárást kell alkalmaznunk:

  1. public static void ButtonPressHandler(object obj, ButtonPressEventArgs args)

A ButtonPressEventArga az EventArgs-ból származtatott osztály, amely
- sok más osztályhoz hasonlóan - egy új Gtk.Event tulajdonságot ad az
EventArgs class-hoz.

A Gdk.Event az az osztály melynek Type tulajdonsága pontosabban
meghatározza, hogy melyik esemény következett be. Más tulajdonságok is
hozzáadhatóak a Gdk.Event-ből való származtatással, mint a
Gdk.Eventbutton esetében is, amely - többek közt - definiálja a Button
tulajdonságot így megadva, hogy az egér melyik gombja lett lenyomva. A
típusok a következők:

  • EventType.Nothing
  • EventType.Delete
  • EventType.Destroy
  • EventType.Expose
  • EventType.MotionNotify
  • EventType.ButtonPress
  • EventType.TwoButtonPress
  • EventType.ThreeButtonPress
  • EventType.ButtonRelease
  • EventType.KeyPress
  • EventType.KeyRelease
  • EventType.EnterNotify
  • EventType.LeaveNotify
  • EventType.FocusChange
  • EventType.Configure
  • EventType.Map
  • EventType.Unmap
  • EventType.PropertyNotify
  • EventType.SelectionClear
  • EventType.SelectionRequest
  • EventType.SelectionNotify
  • EventType.ProximityIn
  • EventType.ProximityOut
  • EventType.DragEnter
  • EventType.DragLeave
  • EventType.DragMotion
  • EventType.DragStatus
  • EventType.DropStart
  • EventType.DropFinished
  • EventType.ClientEvent
  • EventType.VisibilityNotify
  • EventType.NoExpose
  • EventType.Scroll
  • EventType.WindowState
  • EventType.Setting

Ahhoz, hogy egy Gdk.Event eseményt használjunk, valami ilyesmit kell tennünk:

  1. using Gdk; … widget.ButtonPressEvent += new ButtonPressEventHandler(ButtonPressHandler); … private void ButtonPressHandler(object obj, ButtonPressEventArgs args) { //
    si el evento es un click simple if (args.Event.Type ==
    EventType.ButtonPress) { … } // si
    es un doble click if (args.Event.Type == EventType.TwoButtonPress)
    { … } // si se presionA3 el botA3n izquierdo if
    (args.Event.Button == 1) { … } }

Ezáltal, ha az egér egy gombját megnyomják, sokkal több részletet
megtudhatunk az eseményről. Például: egy egyszerű vagy egy
duplakattintás volt-e, az egér jobb vagy bal gombjával.

Az első Glade# alkalmazás

Mi az a Glade#?

A Glade# egy libglade összerendelés a grafikus felhasználói felületű
alkalmazások egyszerű tervezéséhez. Jelenleg két eszköz létezik
glade-formátumú file-ok előállításához: a Glade és természetesen a
Stetic.

Mik azok a glade file-ok?

XML-ben íródott file-ok, melyek a megtervezett
grafikus felületet reprezentálják; elmentve annak minden attribútumát
és tulajdonságát, ezáltal elérhetővé téve bármely programozási
nyelvből.

Hogyan néz ki egy glade file?

Nézzük a következő példát, amely tartalmazza szükséges dolgokat a teszteléshez. file: gui.glade

  1. <?xml version="1.0" standalone="no"?> <!–*- mode: xml -*–> <!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd"> <glade-interface> <widget class="GtkWindow" id="window1"> <property name="visible">True</property> <property name="title" translatable="yes">Glade Window</property> <property name="type">GTK_WINDOW_TOPLEVEL</property> <property name="window_position">GTK_WIN_POS_CENTER</property> <property name="modal">False</property> <property name="default_width">256</property> <property name="default_height">256</property> <property name="resizable">True</property> <property name="destroy_with_parent">False</property> <property name="decorated">True</property> <property name="skip_taskbar_hint">False</property> <property name="skip_pager_hint">False</property> <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property> <property name="gravity">GDK_GRAVITY_NORTH_WEST</property> <property name="focus_on_map">True</property> <child> <widget class="GtkScrolledWindow" id="scrolledwindow1"> <property name="visible">True</property> <property name="can_focus">True</property> <property name="hscrollbar_policy">GTK_POLICY_ALWAYS</property> <property name="vscrollbar_policy">GTK_POLICY_ALWAYS</property> <property name="shadow_type">GTK_SHADOW_IN</property> <property name="window_placement">GTK_CORNER_TOP_LEFT</property> <child> <widget class="GtkLayout" id="layout1"> <property name="visible">True</property> <property name="width">400</property> <property name="height">400</property> <property name="hadjustment">0 0 400 10 212.4 236</property> <property name="vadjustment">0 0 400 10 212.4 236</property> <child> <widget class="GtkLabel" id="label1"> <property name="width_request">38</property> <property name="height_request">17</property> <property name="visible">True</property> <property name="label" translatable="yes">label1</property> <property name="use_underline">False</property> <property name="use_markup">False</property> <property name="justify">GTK_JUSTIFY_LEFT</property> <property name="wrap">False</property> <property name="selectable">False</property> <property name="xalign">0.5</property> <property name="yalign">0.5</property> <property name="xpad">0</property> <property name="ypad">0</property> <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> <property name="width_chars">-1</property> <property name="single_line_mode">False</property> <property name="angle">0</property> </widget> <packing> <property name="x">96</property> <property name="y">88</property> </packing> </child> <child> <widget class="GtkButton" id="button1"> <property name="width_request">60</property> <property name="height_request">27</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="label" translatable="yes">button1</property> <property name="use_underline">True</property> <property name="relief">GTK_RELIEF_NORMAL</property> <property name="focus_on_click">True</property> </widget> <packing> <property name="x">88</property> <property name="y">168</property> </packing> </child> </widget> </child> </widget> </child> </widget> </glade-interface>

Ez egy kicsit túl sok információnak tűnhet, de a libglade
library-nek mindezekre szüksége van a felhasználói felület
megrajzolásához.

Glade file-ok importálása a programunkba

Tekintsük úgy, hogy a glade GUI file-ja
valamilyen úton-módon már el lett készítve, akár Glade-el akár
Stetic-kel. Egy video a Stetic-ről megtekinthető itt. Tekintettel erre
a példára a GUI-t a gui.glade file-ba kell menteni,
és kell, hogy tartalmazzon egy ablak definíciót melynek neve window1,
egy nyomógombot button1 néven valamint egy cimkét label1 néven.
Készítenünk kell egy új hivatkozást a Gtk#-ra és Glade#-ra, majd ezután
egy új osztályt és belépési pontot az alkalmazásunknak. Ennek valahogy
így kell kinéznie:

  1. //
    file: glade.cs using System; using Gtk; using Glade; public class
    GladeApp { public static void Main (string[] args) {
    new GladeApp (args); } public GladeApp (string[] args)
    { Application.Init(); Glade.XML gxml = new Glade.XML
    (null, "gui.glade", "window1", null); gxml.Autoconnect (this);
    Application.Run(); } }
Hogyan fordítsuk le?

Ezután a glade.cs forrásfile-t le kell fordítanunk, hivatkozva a
glade névtérre mely a glade-sharp.dll-ben foglal helyet. A fordításhoz
használt parancs a következő:

  1. mcs -pkg:glade-sharp -resource:gui.glade glade.cs

A parancs hatására létrejön a glade.exe program. Amennyiben a
glade.exe program könyvtára nem tartalmazza a gui.glade file-t, akkor
az alkalmazás hibával áll le. Ha próbáljuk futtatni az alkalmazást
akkor a program kivétel generálásával kilép arra hivatkozva, hogy
bizonyos függvények - melyek a gui.glade-ben lettek definiálva -
hiányoznak. A következő részben ez a hiba javításra kerül.

Glade# használata a saját programban

Hogyan érjük el a glade file-ban definiált objektumokat?

Ahhoz, hogy bármely glade file-ban definiált objektumot elérjünk a
programkódból, tudnunk kell annak pontos nevét és típusát, majd a
megfelelő C# kódot kell hozzáadni az alábbi formában:

  1. [Widget] Type name;

Alkalmazva ezt a mi példánkban:

  1. using System; using Gtk; using Glade; public class GladeApp { public static void Main (string[] args) { new GladeApp (args); } public GladeApp (string[] args) { Application.Init(); Glade.XML gxml = new Glade.XML (null, "gui.glade", "window1", null); gxml.Autoconnect (this); Application.Run(); } [Widget] Button button1; [Widget] Label label1; }
Hogyan adjunk hozzá eseményeket?

Események hozzáadásához ugyanazon lépéseket kell követni minden más eseményhez, melyek akár a Glade-ből is hozzáadhatóak.

  1. using System; using Gtk; using Glade; public class GladeApp { public static void Main (string[] args) { new GladeApp (args); } public GladeApp (string[] args) { Application.Init(); Glade.XML gxml = new Glade.XML (null, "gui.glade", "window1", null); gxml.Autoconnect (this); button1.Clicked += OnPressButtonEvent; Application.Run(); } [Glade.Widget] Button button1; [Glade.Widget] Label label1; public void OnPressButtonEvent( object o, EventArgs e) { Console.WriteLine("Button press"); label1.Text = "Mono"; } }

Következésképpen láthatjuk a teljes kódot a gui.glade-ben definiált
események kezeléséhez. A fenti kód javítja azt a hibát mely az előző
részben bemutatott példában volt.