Trabalho há muitos anos desenvolvendo interface gráfica para a web, soluções desktop Java e Android. Atualmente, eu estou curioso sobre como seria criar essas soluções para Desktop sem utilizar alguma ferramenta multiplataforma.
Com o intuito de experimentar como seria desenvolver uma interface gráfica para PCs Desktop nativa em C/C++, decidi criar uma pequena aplicação usual "TODO list" (lista de afazeres) nesses moldes.
Quando eu me refiro a "Desktop nativo", estou falando de soluções que não envolvam containers web ou outras soluções multi-plataforma como o Java FX ou Swing. Apesar de soluções híbridas serem ideais para criar aplicações para várias plataformas com o mesmo código, elas possuem um GAP em questão de performance. Além disso, diferentes sistemas operacionais se comportam de maneiras distintas e um código genérico para todos eles pode apresentar comportamentos inesperados.
Os diferentes sistemas operacionais possuem diferentes APIs e frameworks para criar apps Desktop. Para a minha pequena aplicação, decidi que ela seria executada no Linux e codificada em C++ com o compilador GCC. Dessa forma, utilizo apenas software livre tanto no S.O. quanto nas ferramentas utilizadas. Caso o programa necessite ser executado em Windows, o WSL pode abrir o software. Já no MacOS, é muito fácil de emular outros sistemas.
Continuando com a utilização de software livre, a biblioteca GTK nos permite construir interfaces gráficas. Ferramenta esta utilizada em programas famosos como GIMP
(editor de imagens) e Transmission
(cliente BitTorrent).
O repositório com o código fonte do projeto encontra-se aqui: https://github.com/misabitencourt/gtk3-cpp-todolist
Criando um arquivo CMAKE para o projeto
Foi feito o uso do CMAKE para definir as configurações de build. Eu acho muito cômodo o fato de o CMAKE gerar o Makefile no Linux e também poder criar um projeto Visual Studio no Windows.
O CMAKE pode ser instalado via APT em distribuições Debian.
sudo apt install cmake
O arquivo CMakeLists
ficou da seguinte forma:
cmake_minimum_required(VERSION 3.0) project(todolist_app) set(CMAKE_CXX_STANDARD 17) # Find GTK3 package find_package(PkgConfig REQUIRED) pkg_check_modules(GTK3 REQUIRED gtk+-3.0) file(GLOB SOURCES "src/sample.cpp" ) add_compile_options(-fpermissive) include_directories(./) include_directories(${GTK3_INCLUDE_DIRS}) add_executable(todolist_app ${SOURCES}) target_link_libraries(todolist_app ${GTK3_LIBRARIES}) add_definitions(${GTK3_CFLAGS_OTHER})
Para gerar o Makefile
, basta rodar:
mkdir build cd build cmake ../
Antes de executar o script do Makefile
, é preciso instalar a lib-gtk:
sudo apt install libgtk-3-dev
Obviamente, o compilador GCC (e o build-essential) junto com o make também têm de estar instalados. Geralmente, esses pacotes já estão configurados em praticamente toda a máquina de desenvolvedores. Para compilar o projeto, basta usar o make:
cd build make ./todolist_app
Main loop
O código abaixo é o arquivo que possui o void main
do projeto. Nele, o loop principal da aplicação é iniciado. Antes disso, foram definidas algumas structs
e módulos para salvar, atualizar, listar e deletar a lista de afazeres. Como o objetivo é apenas o foco na interface, esses métodos persistem em uma lista local na memória.
Main loop:
#include <gtk/gtk.h> #include<list> #include<string> // Types #include "types/models.h" // Services #include "services/todo-service.h" // Views #include "views/dialogs.h" #include "views/main.h" int main(int argc, char *argv[]) { gtk_init(&argc, &argv); // Show all widgets MainWindow::openMainWindow(); gtk_main(); return 0; }
Antes da criação da janela da aplicação, a API da GTK requer uma chamada de inicialização passando os parâmetros dos argumentos da chamada. O método gtk_main
inicia o event loop
da interface gráfica.
O módulo MainWindow
foi definido em views/main.h
:
#define APP_MAIN_WINDOW_WIDTH 400 #define APP_MAIN_WINDOW_HEIGHT 800 #define SUCCESS_COLOR "#99DD99" namespace MainWindow { // Main widget pointers GtkWidget *mainWindow = nullptr; GtkWidget *mainWindowContainer = nullptr; GtkWidget *(*mainWindowRefresh)(); GtkWidget *todoInputText; int inEdition = 0; // Button events void mainWindowViewTodoListSaveClicked(GtkWidget *widget, gpointer data) { GtkEntry *entry = GTK_ENTRY(data); const gchar *text = gtk_entry_get_text(entry); Todo todoDto; todoDto.description = (char *)text; if (inEdition) { todoDto.id = inEdition; TodoService::todolistUpdate(&todoDto); } else { TodoService::todolistSave(&todoDto); } inEdition = 0; mainWindowRefresh(); gtk_widget_show_all(mainWindowContainer); } void mainWindowViewTodoListEditClicked(GtkWidget *widget, int id) { Todo *todo = TodoService::todolistLoad(id); inEdition = id; g_print("Editing %i %s\n", todo->id, todo->description); gtk_entry_set_text(GTK_ENTRY(todoInputText), todo->description); } void mainWindowViewTodoListDeleteClicked(GtkWidget *widget, int id) { TodoService::todolistDelete(id); mainWindowRefresh(); gtk_widget_show_all(mainWindowContainer); } // Render function GtkWidget *createMainWindowView() { GtkWidget *list_box; GtkWidget *row; GtkWidget *hbox; GtkWidget *label; mainWindowRefresh = createMainWindowView; // Creates a new window if (mainWindow == nullptr) { mainWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(mainWindow), "Todo List"); gtk_window_set_default_size(GTK_WINDOW(mainWindow), APP_MAIN_WINDOW_WIDTH, APP_MAIN_WINDOW_HEIGHT); g_signal_connect(mainWindow, "destroy", G_CALLBACK(gtk_main_quit), NULL); } else { gtk_widget_destroy(mainWindowContainer); } GtkWidget *windowContainer; windowContainer = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); mainWindowContainer = windowContainer; gtk_container_add(GTK_CONTAINER(mainWindow), windowContainer); GtkWidget *todoFormContainer; todoFormContainer = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); gtk_container_add(GTK_CONTAINER(windowContainer), todoFormContainer); GtkWidget *inputVbox; inputVbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); gtk_widget_set_size_request(inputVbox, 250, -1); gtk_container_add(GTK_CONTAINER(todoFormContainer), inputVbox); todoInputText = gtk_entry_new(); gtk_box_pack_start(GTK_BOX(inputVbox), todoInputText, TRUE, TRUE, 0); GtkWidget *saveBtn; saveBtn = gtk_button_new_with_label("Save"); gtk_widget_set_size_request(saveBtn, 80, -1); gtk_container_add(GTK_CONTAINER(todoFormContainer), saveBtn); g_signal_connect(saveBtn, "clicked", G_CALLBACK(mainWindowViewTodoListSaveClicked), todoInputText); GtkWidget *cancelBtn; cancelBtn = gtk_button_new_with_label("Cancel"); gtk_widget_set_size_request(cancelBtn, 80, -1); gtk_container_add(GTK_CONTAINER(todoFormContainer), cancelBtn); // // Creates a new GtkListBox list_box = gtk_list_box_new(); gtk_container_add(GTK_CONTAINER(windowContainer), list_box); // // Creates and add rows to the GtkListBox int i = 0; for (std::list<Todo>::iterator it = todoListRepository.begin(); it != todoListRepository.end(); ++it) { row = gtk_list_box_row_new(); hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); // Column 1 gchar *label_text1 = g_strdup_printf(it->description, i); label = gtk_label_new(label_text1); gtk_widget_set_size_request(label, 250, -1); gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0); g_free(label_text1); // Column 2 GtkWidget *editBtn = gtk_button_new_with_label("Edit"); gtk_widget_set_size_request(saveBtn, 80, -1); gtk_box_pack_start(GTK_BOX(hbox), editBtn, TRUE, TRUE, 0); g_signal_connect(editBtn, "clicked", G_CALLBACK(mainWindowViewTodoListEditClicked), it->id); // Column 3 GtkWidget *removeBtn = gtk_button_new_with_label("Remove"); gtk_widget_set_size_request(removeBtn, 80, -1); gtk_box_pack_start(GTK_BOX(hbox), removeBtn, TRUE, TRUE, 0); int index = i; g_signal_connect(removeBtn, "clicked", G_CALLBACK(mainWindowViewTodoListDeleteClicked), it->id); gtk_container_add(GTK_CONTAINER(row), hbox); gtk_container_add(GTK_CONTAINER(list_box), row); i++; } return mainWindow; } // Open function void openMainWindow() { gtk_widget_show_all(createMainWindowView()); } }
Semelhante a abordagem utilizada nas tecnologias web e no desenvolvimento Android, no GTK, temos uma interface para criar uma janela gtk_window_new
e dentro dela, adicionamos vários elementos que podem conter vários outros elementos dentro, assim, formando uma árvore. Os elementos aqui são GtkWidget
. Na documentação da lib podemos encontrar os diversos tipos de Widgets que podemos utilizar.
Em meu experimento, não utilizei nenhuma ferramenta para o design da tela. Tudo foi feito via interface de programação. Todavia, o GTK permite a criação de arquivos XML que representam a tela. E o Glade é uma interface gráfica que permite a criação destes XML.
Top comments (0)