Z czasem, gdy pisany przez nas kod będzie większy i większy, umieszczanie go w jednym pliku spowoduje trudniejszą analizę kodu. Dlatego rozbijmy to co napisaliśmy na klasy, żeby zwiększyć czytelność kodu. Przyjmijmy, że obiekt buildera oraz funkcje pomocnicze interfejsu użytkownika umieścimy w klasie Gui. Natomiast każde okno utworzone przez buildera będzie miało własną odrębną klasę.
Program podzielimy na pliki: Gui.h, Gui.cpp, Window.h, Window.cpp, MainWindow.h, MainWindow.cpp, main.cpp.
Nasz prosty program nieco się rozrośnie, ale korzyść z takiego podziału docenimy później w przypadku bardziej
zaawansowanych aplikacji.
Omówmy teraz poszczególne pliki.
Gui.h
#ifndef __GUI_H__
#define __GUI_H__
#include <gtk/gtk.h>
class Gui {
public:
GtkBuilder *builder;
Gui();
virtual ~Gui();
gboolean loadInterface(const gchar *filename);
GtkWidget* getWidget(const gchar *widgetName);
GtkResponseType msgBoxYesNo(const gchar *message);
};
#endif
Gui.cpp
#include "Gui.h"
Gui::Gui() {
this->builder = gtk_builder_new();
}
Gui::~Gui() {
g_object_unref(this->builder);
this->builder = NULL;
}
gboolean Gui::loadInterface(const gchar *filename) {
GError *error;
error = NULL;
gtk_builder_add_from_file(this->builder, filename, &error);
if (error) {
g_print("Wystąpił błąd: %s\n", error->message);
g_error_free(error);
return FALSE;
}
return TRUE;
}
GtkWidget* Gui::getWidget(const gchar *widgetName) {
return GTK_WIDGET(gtk_builder_get_object(this->builder, widgetName));
}
GtkResponseType Gui::msgBoxYesNo(const gchar *message) {
GtkWidget *dialog;
dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, message);
gtk_window_set_title(GTK_WINDOW(dialog), "Pytanie");
GtkResponseType result = (GtkResponseType)gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
return result;
}
W plikach Gui.h i Gui.cpp zdefiniowaliśmy klasę pomocniczą Gui. Ma ona następujące metody:
Window.h
#ifndef __WINDOW_H__
#define __WINDOW_H__
#include <gtk/gtk.h>
class Window {
public:
GtkWidget *wnd;
Window(GtkWidget *wnd);
virtual ~Window();
void show();
};
#endif
Window.cpp
#include "Window.h"
Window::Window(GtkWidget *wnd) {
this->wnd = wnd;
}
Window::~Window() {
gtk_widget_destroy(this->wnd);
this->wnd = NULL;
}
void Window::show() {
gtk_widget_show_all(this->wnd);
}
W plikach Window.h i Window.cpp zdefiniowaliśmy ogólną klasę Window przeznaczoną
do generycznych operacji na oknie.
Ma ona następujące metody:
MainWindow.h
#ifndef __MAIN_WINDOW_H__
#define __MAIN_WINDOW_H__
#include <gtk/gtk.h>
#include "Window.h"
class MainWindow : public Window {
public:
MainWindow(GtkWidget *wnd);
virtual ~MainWindow();
static void onDestroy(GtkWidget* widget, gpointer data);
static gboolean onDeleteEvent(GtkWidget* widget, GdkEvent* event, gpointer data);
};
#endif
MainWindow.cpp
#include "MainWindow.h"
#include "Gui.h"
extern Gui *gui;
MainWindow::MainWindow(GtkWidget *wnd)
: Window(wnd) {
g_signal_connect(G_OBJECT(this->wnd), "delete-event", G_CALLBACK(MainWindow::onDeleteEvent), NULL);
g_signal_connect(G_OBJECT(this->wnd), "destroy", G_CALLBACK(MainWindow::onDestroy), NULL);
}
MainWindow::~MainWindow() {
}
// ==================================================================================================
// Static functions - event handlers
// ==================================================================================================
void MainWindow::onDestroy(GtkWidget* widget, gpointer data) {
gtk_main_quit();
}
gboolean MainWindow::onDeleteEvent(GtkWidget* widget, GdkEvent* event, gpointer data) {
if (gui->msgBoxYesNo("Czy na pewno chcesz opuścić program?") == GTK_RESPONSE_YES) {
return FALSE;
}
return TRUE;
}
W plikach MainWindow.h i MainWindow.cpp zdefiniowaliśmy szczegółową klasę
MainWindow przeznaczoną do obsługi naszego okna głównego. Dziedziczy ono po klasie
Window.
Ma następujące metody:
Uwaga! Jako funkcje obsługi zdarzeń możemy podpinać metody klas, ale należy pamiętać że muszą to być metody statyczne.
main.cpp
#include <gtk/gtk.h>
#include "Gui.h"
#include "MainWindow.h"
Gui *gui;
int main(int argc, char** argv) {
gui = new Gui();
MainWindow *window;
gtk_init(&argc, &argv);
if (!gui->loadInterface("main_window.ui")) {
return -1;
}
window = new MainWindow(gui->getWidget("wndHelloWorld"));
window->show();
gtk_main();
delete window;
delete gui;
return 0;
}
Na koniec został do omówienia nasz plik z głównym programem. Widzimy, że stał się bardziej przejrzysty. Do załadowania okna z pliku main_window.ui służy metoda gui->loadInterface(). Jeśli się nie udało załadować interfejsu, to program kończy działanie. W przeciwnym razie tworzony jest obiekt klasy HelloWorldWindow wraz ze wskaźnikiem na nasze okno pobranym z obiektu gui metodą gui->getWidget().
Po wyjściu z pętli gtk_main() musimy jeszcze zwolnić pamięć zajmowaną przez obiekt okna i gui. Zauważ, że obiekt gui został zadeklarowany na zewnątrz funkcji main() - ma on zasięg globalny. Zrobiliśmy tak, żeby łatwo można było sięgać do metod klasy gui z innych miejsc w kodzie, w tym w metodach statycznych, które obsługują sygnały okna HelloWorldWindow.
Program kompilujemy poleceniem: make. Cały projekt utworzony w tym rozdziale będziemy traktować jako punkt wyjścia do tworzenia nowych programów.