Programowanie w GTK2 przy użyciu C++

Wprowadzenie
Niniejszy artykuł wprowadza Czytelnika w świat programowania w toolkicie GTK2 przy użyciu języka C++. Zakładam, że odbiorca posiadł podstawową wiedzę z zakresu systemu operacyjnego Linux, a także, że jest zaznajomiony z językiem C++ i standardowymi mechanizmami obiektowymi.
Toolkit a menedżer okien a XWindows
Można śmiało postawić tezę, że większość osób mających do czynienia z Linuksem zdobyła uprzednio pewne doświadczenie w obsłudze systemu MS Windows. System operacyjny Microsoftu nie zawraca głowy przeciętnemu użytkownikowi podziałem tego co widać na ekranie na drobniejsze elementy. Są tego zarówno dobre, jak i złe strony. Dobrą jest to, że interfejs integruje się gładko z systemem operacyjnym, co jest pożądaną cechą oprogramowania. Jednak mogą tracić na tym programiści, którzy chcąc wprowadzić własne zmiany nie tylko muszą się solidnie namęczyć, ale tworzą też rozwiązania z założenia skazane na niewydajną pracę.
Architektura interfejsu graficznego w Linuksie jest dla odmiany bardzo rozwarstwiona. Jako że oprogramowanie, które dominuje, jest na licencji Open-Source, mamy swobodny wgląd we wszystkie warstwy - nierzadko kiepsko udokumentowane. Jednak w pracy z interfejsem graficznym w Linuksie przydatna jest znajomość paru fundamentalnych konceptów:
- XWindows - jest to system wyświetlania elementów graficznych na ekranie, który jest niskopoziomowy, ale zarazem wydajny. Oferuje podstawowe elementy systemu okienkowego, czyli umieszczanie fragmentów grafiki na ekranie, odświeżanie, przyjmowanie zdarzeń z klawiatur, myszki i innych kontrolerów.
- Window manager - menedżer okien, jak sama nazwa wskazuje, zajmuje się porządkowaniem okien użytkownika i ułatwianiem mu pracy z nimi. W systemie GNOME, z którego będziemy korzystać, menedżerem okien jest Metacity.
- Toolkit - jest to zestaw elementów, z których zbudowana jest aplikacja działająca w trybie graficznym. W systemie Linux popularność zdobyły dwa toolkity: QT oraz GTK2. Toolkit QT ma jedną zasadniczą wadę (choć dla niektórych to zaleta) - warunki licencyjne ograniczają jego użycie bez opłat w projektach komercyjnych.
Wszystkie wymienione warstwy działają mniej lub bardziej niezależnie od siebie - to znaczy użycie konkretnego toolkitu nie wymusza użycia konkretnego menedżera okien. Tak więc, mimo tego, że wspomniane środowisko GNOME używa GTK2 jako podstawowego toolkitu, a jego rywal KDE używa QT, nie ma istotnych przeciwwskazań, aby w swoich aplikacjach używać QT pod GNOME lub GTK2 pod KDE. Choć na dzień dzisiejszy jest powszechną praktyką, aby programy pisane w GTK2 profilować pod kątem użycia w GNOME, analogicznie: programy korzystające z QT przeważnie pisane są z myślą o KDE jako środowisku docelowym. Należy zaznaczyć, że wybór toolkitu, w którym ma powstać aplikacja, nierzadko przysparza sporych problemów. Decyzja powinna zostać podjęta z rozwagą, po rozważeniu możliwie wielu "za" i "przeciw" związanych z projetkowanym produktem.
O GTK2 i gtkmm
GTK2 jest zestawem "widgetów", służącym do tworzenia interfejsu użytkownika. Chociaż oryginalny interfejs programistyczny (API) napisany jest w języku C, GTK2 pozwala na pracę na wielu platformach oraz w wielu językach programowania, między innymi w C++, C#, Javie, Perlu i Pythonie. Pełną listę języków programowania współpracujących z GTK można znaleźć pod adresem http://www.gtk.org/bindings.html.
Element interfejsu w GTK2 nie nazywamy "kontrolką", tak jak w Win32, a raczej "widgetem". Naturalnie GTK2 zapewnia programiście wszelkie "standardowe" widgety, takie jak: przyciski, pola do edycji tekstu, pola na bitmapy, listy, okna dialogowe, etc. Wypada wyjaśnić pewien kruczek związany z nazewnictwem: oficjalnie zestaw widgetów, o którym piszę, nazywa się GTK+, jednak w różnych publikacjach oraz w sieci bardzo często stosuje się rozgraniczenie na GTK oraz GTK2. Przyczyną tego jest prawdopodobnie fakt, że wiele osób kojarzy nazwę GTK+ z archaiczną już wersją 1 omawianego toolkitu.
Tak, jak wspomniałem wcześniej, GTK2 posiada API w języku C, które jest wyśmiewane przez wielu developerów ze względu na przesadnie długie nazwy funkcji, które są niewygodne w użyciu (np: gtk_radion_button_get_group()). Na szczęście istnieje gtkmm - interfejs programistyczny do GTK2 w języku C++ (http://www.gtkmm.org). Gtkmm, zwany niegdyś "gtk--", jest dobrze dopracowanym przeniesieniem GTK2 w świat C++, udostepniającym nie tylko wszystkie mechanizmy obecne w rodzimym API, ale także wiele ułatwień i uproszczeń związanych ze specyfiką języka C++. Dlatego też jest to logiczny wybór dla kogoś, kto ma chęć rozpocząć swoją przygodę z GTK2.
Niezbędne narzędzia
Do pracy naturalnie będzie potrzebny zestaw pakietów, który może nie być standardowo dostępny w dystrybucji Czytelnika. Będę bazował na wersji 2.4 gtk2 oraz gtkmm. Wszelkie informacje dotyczące pakietów podane są dla dystrybucji Fedora 2. Zakładam, że Czytelnik posiada zainstalowane i funkcjonujące środowisko GNOME w wersji 2.6 lub nowszej. Należy zamontować nastepujące pakiety:
- gtk2 (toolkit gtk2)
- gtk2-devel (interfejs programistyczny do gtk2)
- gtkmm24 (wrapper do gtk2)
- gtkmm24-devel (interfejs programistyczny w C++ do gtk2)
- libsigc++20 (system callbacków, z którego korzysta gtkmm)
- libsigc++20-devel
- glib2 (niskopoziomowa biblioteka GTK)
- glib2-devel
- glibmm24 (interfejs glib do C++)
- glibmm24-devel
- glade2 (budowniczy interfejsów użytkownika)
- libglademm24 (obsługa glade w gtkmm)
- libglademm24-devel
Pomocny w zlokalizowaniu opisanych paczek z pewnością będzie serwis rpm.pbone.net.
Niewątpliwie dobrym pomysłem jest ściągnięcie bogatej dokumentacji do gtkmm, która dostępna jest pod tym adresem: http://www.gtkmm.org/docs/gtkmm-2.4/gtkmm-2-4-docs.tar.gz.
Hello World
Jak dobra praktyka informatyczna nakazuje, przed rozpoczęciem poważniejszych prac zademonstruję konstrukcję programu Hello World, który tworzy puste okno z tytułem "Hello World!".
Oto hworld.cc:
#include <gtkmm.h>
int main(int argc, char *argv[]) {
Gtk::Main kit(argc, argv);
Gtk::Window okno;
okno.set_title("Hello World!");
Gtk::Main::run(okno);
return 0;
}
Gtk::Main jest obiektem typu singleton, który musi wystąpić w każdej aplikacji gtkmm. Wykonuje on czynności przygotowawcze przed uruchomieniem aplikacji. Klasa Gtk::Window jest odpowiedzialna za tworzenie i obsługę okien, a jej metoda set_title pozwala na ustawienie tytułu okna. Program uruchamiany jest poprzez klasę Main, metodą run() z obiektem okna głównego jako parametrem. Kompilację przeprowadzamy komendą:
gcc `pkg-config gtkmm-2.4 --libs --cflags` *cc
Wszelkie niewyjaśnione wątpliwości oraz pytania w sprawie powyższego przykładu powinna rozwiać lektura dokumentacji.
Efekt działania programu hworld.cc przedstawia poniższy rysunek:
Hello Widget
Spróbujmy teraz napisać kawałek bardziej zaawansowanego kodu. Niech program posiada tylko okno i przycisk, który wywołuje metody w następujących przypadkach:
- użytkownik wcisnął przycisk,
- użytkownik puścił przycisk,
- użytkownik kliknął przycisk (czyli wcisnął, a następnie puścił),
- użytkownik najechał myszką na pole przycisku,
- użytkownik opuścił myszką pole przycisku.
Praktycznie każdy, najprostszy nawet program okienkowy bazuje na interakcji z użytkownikiem. Wcześniej wspomniałem o bibliotece libsigc++, która umożliwia wysyłanie i odbieranie sygnałów w C++. Za pomocą tej biblioteki będziemy dokonywać komunikacji między naszym widgetem a aplikacją. Zakładając, że będziemy posiadać obiekt "guzik" typu Gtk::Button, możemy połączyć wydarzenie w postaci wciśnięcia przycisku z funkcją lub metodą obecną w kodzie. Można zrobić to na dwa sposoby: jeden z nich to podłączenie metody klasy, natomiast drugi - podłączenie funkcji nie związanej z obiektem.
Przykład sposobu 1, gdzie podłączenie odbywa się wewnątrz metody jakiegoś obiektu:
guzik.signal_pressed().connect(
sigc::mem_fun(*this, &wndOkno::evt_guzik_pressed)
);
Jak widać, metoda evt_guzik_pressed() należy do klasy wndOkno. Będzie ona wywoływana przy każdym naciśnięciu guzika. No dobrze, ale skąd wiemy, który sygnał odpowiada za naciśnięcie przycisku? W dokumentacji klasy Gtk::Button, tak samo jak każdej innej klasy, znajduje się lista wszystkich dostępnych sygnałów wraz z opisami.
Przykład sposobu 2:
guzik.signal_pressed().connect(
sigc::ptr_fun(funkcja)
);
Tu funkcja() nie jest związana z żadnym konkretnym obiektem. Należy wiedzieć jeszcze dwie istotne rzeczy, dotyczące obsługi sygnałów. Po pierwsze, do sygnału można podłączyć więcej niż jedną funkcję/metodę go obsługującą, a po drugie - trzeba znać ilość argumentów, którą przekazuje sygnał. Nie uda się podłączyć bezargumentowej funkcji lub metody do obsługi sygnału przekazującego jakieś argumenty.
Zdefiniujmy zatem klasę okna (wndOkno), dziedziczącą po Gtk::Window:
class wndOkno : public Gtk::Window {
public:
wndOkno();
virtual ~wndOkno();
protected:
virtual void evt_guzik_pressed();
virtual void evt_guzik_released();
virtual void evt_guzik_clicked();
virtual void evt_guzik_enter();
virtual void evt_guzik_leave();
// w oknie jeden widget: przycisk
Gtk::Button guzik;
};
Implementacja konstruktora klasy wndOkno:
// konstruktor klasy wndOkno wywoluje konstruktor Gtk::Button z
// podpisem przycisku jako parametrem
wndOkno::wndOkno() : guzik("Hello Widget!") {
// podlaczamy metody wndOkno do obslugi zdarzen przycisku
guzik.signal_pressed().connect(sigc::mem_fun(*this,
&wndOkno::evt_guzik_pressed));
guzik.signal_released().connect(sigc::mem_fun(*this,
&wndOkno::evt_guzik_released));
guzik.signal_clicked().connect(sigc::mem_fun(*this,
&wndOkno::evt_guzik_clicked));
guzik.signal_enter().connect(sigc::mem_fun(*this,
&wndOkno::evt_guzik_enter));
guzik.signal_leave().connect(sigc::mem_fun(*this,
&wndOkno::evt_guzik_leave));
// ustaw ramke naokolo wszystkich widgetow w oknie
set_border_width(20);
// dodaj przycisk 'guzik' do okna
add(guzik);
// pokaz guzik
guzik.show();
}
Implementacja obsługi sygnału jest prosta:
void wndOkno::evt_guzik_pressed() {
cout << "Wcisnieto guzik" << endl;
}
I na koniec dodajemy funkcję main:
int main(int argc, char *argv[]) {
Gtk::Main kit(argc, argv);
wndOkno okno;
Gtk::Main::run(okno);
return 0;
}
Gotowy program można pobrać tu: mainB.cc. Kompilacja przebiega dokładnie tak samo, jak w poprzednim przykładzie. Wynikiem działania są komunikaty na konsoli, które pojawiają się podczas interakcji z przyciskiem.
Przekazywanie danych przez sygnały jest możliwe i szeroko wykorzysywane. Funkcjonalność ta zostanie omówiona w ostatniej części artykułu.
Glade 2 - szybkie tworzenie interfejsu
Potrafimy już znaleźć sygnały wysyłane przez widget i podłączyć do nich funkcje obsługujące. Dodawanie widgetów z kodu jest na dłuższą metę niewygodne i bardzo męczące. Na szczęście istnieje Glade, budowniczy interfejsów użytkownika.

Przy pomocy tego narzędzia programista może zaprojektować interfejs do swojego programu, zapisać go do pliku, a później w programie wczytać hurtowo wszystkie widgety wraz z parametrami z pliku. Glade daje również możliwość wygenerowania gotowego kodu aplikacji, w którym wystarczy zaimplementować logikę i obsługę sygnałów, jednak nie skorzystamy z tej opcji. Wtyczka do Glade generująca kod w C++ potrafi czasem pozmieniać strukturę programu w taki sposób, że programista marnuje dużo czasu na znalezienie zmian i dostosowanie się do nich.
Glade uruchamiamy poleceniem "glade-2". Interfejs programu składa się z kilku okien, z których najważniejsze są: okno projektu, paleta oraz okno właściwości.
Jeśli któreś z wymienionych okien nie jest widoczne, można je uaktywnić używając stosownego polecenia z menu "Widok". Tworzymy zatem nowy projekt GTK+ (Projekt -> Nowy). Następnie klikamy na palecie widget "okno", co spowoduje pojawienie się nowego okna, w którym można umieszczać widgety. W ogólności, wszelkie elementy okna w aplikacjach GTK2 znajdują się w pojemnikach, czyli widgetach służących do przechowywania i układania innych widgetów. Do dyspozycji mamy cztery typy pojemników:
- poziomą skrzynkę - zawartość układana jest w rzędzie,
- pionową skrzynkę - zawartość układana jest w kolumnie,
- tabelę - zawartość układana jest w formie tabeli,
- stałe pozycje - położenie każdego widgetu jest definiowane niezależnie.
MS Windows przyzwyczaja programistów do modelu, w którym wszystkie kontrolki mają stałe pozycje, dlatego też wielu z nich nie rozumie, jaki sens ma stosowanie trzech pozostałych pojemników. Zapewniam jednak, że na dłuższą metę stosowanie skrzynek poziomych i pionowych może być bardzo wygodne. Dodajmy zatem do okna skrzynkę pionową, a następnie w środkowe jej pole skrzynkę poziomą. Aby to uczynić, należy wybrać z palety widget "pionowa skrzynka" i kliknąć w pole projektowanego okna, wybierając domyślną ilość pól - trzy. Następnie w palecie należy dokonać wyboru widgetu "pozioma skrzynka" i kliknąć w środkowe pole uprzednio dodanej skryznki pionowej - tu również chcemy mieć trzy pola. Powinniśmy uzyskać efekt taki, jak na rysunku:
Teraz dodamy kilka "zwykłych" widgetów: przycisk, etykietę, pole do wprowadzania tekstu, menu ("pasek menu") oraz listę (widget "widok listy lub drzewa"). Tak jak uprzednio, wybieramy z palety odpowiedni widget i poprzez kliknięcie w stosownej komórce umieszczamy go w polu okna. Chcemy uzyskać efekt taki, jak na rysunku:
Większe odstępy między elementami można uzyskać regulując w oknie "Właściwości" parametr "wyściółka", znajdujący się w zakładce "Upakowywanie". Zapisujemy projekt do dowolnie wybranego katalogu i powstały plik z rozszerzeniem .glade kopiujemy do katalogu w którym piszemy program.
Wczytywanie interfejsu
Więc mamy już interfejs, zapisany w pliku .glade, który - tak się miło składa - jest zwykłym plikiem XMLowym. Dzięki gtkmm, nasz program może wczytać ów plik i bez problemów obsłużyć widgety w nim zapisane. Całe piękno tego rozwiązania polega na tym, że nie zmieniając kodu możemy całkowicie zmienić strukturę interfejsu i używać nowego interfejsu bez rekompilacji programu! Dostęp do obiektów z pliku XML daje obiekt Gnome::Glade::Xml, do którego należy odwoływać się przez tzw. smart pointer. Smart pointer (po naszemu: sprytny wskaźnik) to wskaźnik obsługiwany przez Glib, który automatycznie śledzi wskazywaną instancję obiektu i automatycznie usuwa ją z pamięci gdy wyjdzie ona poza bieżący zakres, tzn zniszczymy wszystkie odwołania do niej. Poniższy przykład wczytuje do obiektu o nazwie xmlptr strukturę interfejsu wraz ze wszystkimi atrybutami widgetów:
Glib::RefPtr<Gnome::Glade::Xml> xmlptr = Gnome::Glade::Xml::create("spoj.glade");
Bardzo łatwo jest uzyskać referencję do widgetu z interfejsu, korzystając z metody get_widget_derived():
xmlptr->get_widget_derived("window1", okno);
Tu jest jednak mały haczyk. Okno musi być wskaźnikiem na instancję obiektu potomnego Gtk::Window, o specjalnie przygotowanym konstruktorze. Oto cała treść prostego programu (mainC.cc) wczytującego okno i jego widgety z pliku spoj.glade:
#include <gtkmm.h>
#include <libglademm.h>
#include <libglademm/xml.h>
class wndOkno : public Gtk::Window {
public:
wndOkno(BaseObjectType* base_object,
const Glib::RefPtr<Gnome::Glade::Xml>& glade_xml);
virtual ~wndOkno();
};
wndOkno::wndOkno(BaseObjectType* base_object,
const Glib::RefPtr<Gnome::Glade::Xml>& glade_xml) :
Gtk::Window(base_object) {
}
wndOkno::~wndOkno() {
}
int main (int argc, char *argv[])
{
Gtk::Main kit(argc, argv);
Glib::RefPtr<Gnome::Glade::Xml> xmlptr =
Gnome::Glade::Xml::create("spoj.glade");
wndOkno *okno;
xmlptr->get_widget_derived("window1", okno);
Gtk::Main::run(*okno);
return 0;
}
Kompilacja przebiega nieco odmiennie niż wcześniej, należy bowiem dodać biblioteki libglademm:
gcc `pkg-config gtkmm-2.4 --libs --cflags` `pkg-config --libs
--cflags libglademm-2.4` *cc
Okno widoczne po uruchomieniu programu może nieznacznie różnić się od tego, które zaprojektowaliśmy w Glade. Niestety brak pełnego zachowania "wysiwyg" jest jednym z zasadniczych minusów programu. Jednak ustawiając widgetom poprawne parametry automatycznego wypełniania i wielkości da się uzyskać w pełni przewidywalny efekt. Na koniec dobra rada: należy być ostrożnym przy wczytywaniu pliku .glade i zwracać baczną uwagę na nazwy widgetów - jeśli nasz program będzie chciał uzyskać referencję do nie istniejącego obiektu, spowoduje błąd segmentacji.
Obsługa podstawowych widgetów
W tej sekcji opiszę jak posługiwać się podstawowymi widgetami: przyciskiem oraz polem do wprowadzania tekstu. Zakładając, że interfejs został już wczytany, możemy uzyskać wskaźnik na przycisk w poniższy sposób:
Gtk::Button *btnB1;
xmlptr->get_widget("button1", btnB1);
Podłączenie sygnału odbywa się w sposób identyczny jak wcześniej:
void btn1clicked() {
cout << "Wcisnieto przycisk" << endl;
}
//...
btnB1->signal_clicked().connect(sigc::ptr_fun(btn1clicked));
Po tym zabiegu naciśnięcie przycisku powinno skutkować pojawieniem się napisu na konsoli. Podłączanie innych sygnałów odbywa się analogicznie do powyższego sposobu. Teraz zademonstruję obsługę pola tekstowego. Zmodyfikujemy procedurę btn1clicked() tak, aby wyświetlała zawartość pola tekstowego w etykiecie oraz na konsoli:
Gtk::Label *lblL1;
Gtk::Entry *entE1;
void btn1clicked() {
lblL1->set_text(entE1->get_text());
cout << "Wcisnieto przycisk" << endl;
cout << entE1->get_text().data() << endl;
}
//...
Gtk::Button *btnB1;
xmlptr->get_widget("button1", btnB1);
xmlptr->get_widget("label1", lblL1);
xmlptr->get_widget("entry1", entE1);
btnB1->signal_clicked().connect(sigc::ptr_fun(btn1clicked));
Dlaczego podczas wyświetlania na konsoli tekstu z pola wołamy metodę data()? Wszyskie ciągi znaków w gtkmm są zapisane w formacie Unicode, a typem je przechowującym jest Glib::ustring. Data() jest metodą obiektu klasy Glib::ustring, który zwraca "zwykły" ciąg znaków, const char*, czyli taki, który nadaje się do pokazania na konsoli. Źródło programu: mainD.cc. Zauważmy, że jeśli wypełnimy pole tekstowe długim ciągiem i wciśniemy przycisk, etykieta rozszerza się, a co za tym idzie, całe okno również! Jest to efektem domyślnych ustawień Glade, który pozwala na rozszerzanie się widgetów - naturalnie zachowanie to da się kontrolować poprzez odpowiednie parametry. Opisywane zjawisko pokazuje rysunek:
Obsługa prostych widgetów zapewne poszła gładko, nadszedł czas na zademonstrowanie bardziej zaawansowanych technik.
Obsługa zaawansowanych widgetów
Jednym z najczęściej wykorzystywanych widgetów jest lista. W GTK2, o dziwo, lista jest obsługiwana przez ten sam widget, co drzewo! Taki stan rzeczy spowodowany jest oddzieleniem widoku od danych. Jedną z głównych zalet tego rozwiązania jest fakt, że dzięki niemu można oglądać te same dane w dwóch różnych widokach jednocześnie. Każdy obiekt Gtk::TreeView ma przypisany obiekt Gtk::TreeModel, zawierający dane - w praktyce jest to instancja jednej z dwóch klas potomnych: albo Gtk::ListStore, albo Gtk::TreeStore. Każdy obiekt klasy Gtk::TreeModel związany jest z instancją klasy ColumnRecord, która śledzi kolumny oraz typy ich danych. Poprawną praktyką jest dodawanie do ColumnRecord instancji TreeModelColumn, aby później korzystać z nich przy manipulacji danymi w modelu. Poniższy przykład przedstawia wczytanie listy z pliku .glade oraz dodanie do niej dwóch kolumn:
class ModelColumns: public Gtk::TreeModel::ColumnRecord {
public:
// konstruktor ModelColumns dodaje kolumny do modelu kolumn
ModelColumns() {
add(m_col_id);
add(m_col_passwd);
}
// jedna kolumna zawiera dane typu unsigned int, druga ciagi znakow
Gtk::TreeModelColumn<unsigned int> m_col_id;
Gtk::TreeModelColumn<Glib::ustring> m_col_passwd;
};
//...
Gtk::TreeView *tv;
Glib::RefPtr<Gtk::ListStore> tmdl;
ModelColumns mc;
// wczytanie widoku z pliku
xmlptr->get_widget("treeview1", tv);
// stworzenie modelu danych na podstawie modelu kolumn
tmdl = Gtk::ListStore::create(mc);
// kojarzymy widok z modelem - czyli wiazemy dane z
// tym co widac na ekranie
tv->set_model(tmdl);
// dodajemy do widoku listy kolumne z numerem
tv->append_column("No.", mc.m_col_id);
// dodajemy do widoku listy kolumne ktorej zawartosc mozna zmieniac
tv->append_column_editable("Password", mc.m_col_passwd);
Jak widać, nie jest już tak łatwo i przyjemnie, jak było wcześniej. Taka niestety jest cena, jaką płaci się za efektywne rozwiązania. Jeśli dodamy powyższy fragment kodu do wcześniejszego programu (otrzymując mainE.cc), powinniśmy po skompilowaniu uzyskać mniej więcej taki efekt:
Poniższy przykład demonstruje dodawanie wpisów do listy:
// wyjecie modelu oraz ModelColumns z main(),
// bo korzystamy z obiektow w procedurze
Glib::RefPtr<Gtk::ListStore> tmdl;
ModelColumns mc;
void btn1clicked() {
lblL1->set_text(entE1->get_text());
cout << "Wcisnieto przycisk" << endl;
cout << entE1->get_text().data() << endl;
// stworzenie nowego rzedu w modelu danych i uzyskanie referencji do niego
Gtk::TreeModel::Row rw = *(tmdl->append());
// nadanie wartosci
rw[mc.m_col_id] = 1;
rw[mc.m_col_passwd] = entE1->get_text();
}
Jeśli wszystko poszło zgodnie z planem, uzyskamy możliwość dopisywania elementów do listy:
Posuniemy się jeszcze o krok dalej - dodamy bardzo użyteczną funkcjonalność, a mianowicie wykrywanie zmian pozycji na liście. Przy okazji nauczymy się również jak łączyć sygnał przekazujący parametry z właściwą procedurą. Rzut oka na dokumentację Gtk::TreeModel pozwala stwierdzić, że klasa ta daje możliwość podłączenia obsługi sygnału signal_row_changed:
Glib::SignalProxy2 <void, const TreeModel::Path&, const TreeModel::iterator&> signal_row_changed ()
Powyższy zapis oznacza:
- sygnał wysyła dwie zmienne (SignalProxy__2__),
- sygnał zwraca void (<void,),
- pierwsza zmienna jest typu const TreeModel::Path&,
- pierwsza zmienna jest typu const TreeModel::iterator&,
- sygnał nazywa się "signal_row_changed".
Sygnatura procedury lub metody podłączanej do obsługi tego sygnału musi być identyczna z powyższą specyfikacją:
void rowupdate(const Gtk::TreeModel::Path& path,
const Gtk::TreeModel::iterator& iter) {
// wczytanie wybranego rzedu
Gtk::TreeModel::Row rw = *iter;
// zbuforowanie wartosci kolumny m_col_passwd z wybranego rzedu
Glib::ustring *tempstring = new Glib::ustring(rw[mc.m_col_passwd]);
// wyswietlenie
cout << tempstring->data() << endl;
}
Samo podłączenie wygląda całkiem zwyczajnie:
tmdl->signal_row_changed().connect(sigc::ptr_fun(rowupdate));
Kod całego programu znajduje się tu: mainF.cc. Interesujące są również drzewo oraz okna dialogowe, jednak omówię je przy innej okazji.
Konkluzja
Programowanie w gtkmm już na pierwszy rzut oka wygląda łatwiej w MS Visual Studio lub w "zwykłym" gtk. Jest to efektem nie tylko zastosowania języka C++, który jest przyjazny dla programisty, dając mu jednocześnie bardzo bogate możliwości, ale również starannego przemyślenia konstrukcji biblioteki przez jej autorów. Pragnę raz jeszcze podkreślić wartość dokumentacji do gtkmm - chociaż nie można powiedzieć, żeby była to perełka dokumentacji świata Open Source, to z pewnością można określić ją jako bogatą, spójną i przede wszystkim niezwykle pomocną w pracy. Mam nadzieję, że niniejszy artykuł zachęcił Czytelnika do dalszej eksploracji bogatych możliwości opisywanego oprogramowania. Powodzenia!
Bibliografia: - [1] Dokumentacja do gtkmm - http://gtkmm.org/docs/gtkmm-2.4/docs/
Aby dodawać komentarze musisz być zalogowany!
