Logo

dev-resources.site

for different kinds of informations.

Creating a Native Desktop GUI Using C++ with GTK

Published at
10/13/2024
Categories
cpp
gcc
gui
desktop
Author
justaguyfrombr
Categories
4 categories in total
cpp
open
gcc
open
gui
open
desktop
open
Author
14 person written this
justaguyfrombr
open
Creating a Native Desktop GUI Using C++ with GTK

I've been working for many years developing graphical interfaces for the web, as well as desktop Java and Android solutions. Currently, I'm curious about how it would be to create these solutions for the desktop without using a cross-platform tool.

With the goal of experimenting with developing a native graphical interface for desktop PCs in C/C++, I decided to create a small and practical "TODO list" application using this approach.

When I say "native desktop," I'm referring to solutions that do not involve web containers or other cross-platform solutions like JavaFX or Swing. Although hybrid solutions are ideal for creating applications for various platforms with the same code, they often have a performance gap. Additionally, different operating systems behave differently, and generic code for all of them may show unexpected behavior.

Different operating systems have different APIs and frameworks for creating desktop apps. For my small application, I decided it would run on Linux and be coded in C++ using the GCC compiler. This way, I only use open-source software both in the OS and in the tools used. If the program needs to run on Windows, WSL can open the software. On macOS, it's very easy to emulate other systems.

Continuing with the use of open-source software, the GTK library allows us to build graphical interfaces. This tool is used in famous programs like GIMP (image editor) and Transmission (BitTorrent client).

Demo

The repository with the project's source code can be found here: https://github.com/misabitencourt/gtk3-cpp-todolist

Creating a CMAKE File for the Project

I used CMAKE to define the build configurations. I find it very convenient that CMAKE generates the Makefile on Linux and can also create a Visual Studio project on Windows.

CMAKE can be installed via APT on Debian distributions.

sudo apt install cmake
Enter fullscreen mode Exit fullscreen mode

The CMakeLists file looks like this:

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})
Enter fullscreen mode Exit fullscreen mode

To generate the Makefile, simply run:

mkdir build
cd build
cmake ../
Enter fullscreen mode Exit fullscreen mode

Before running the Makefile script, you need to install the GTK library:

sudo apt install libgtk-3-dev
Enter fullscreen mode Exit fullscreen mode

Of course, the GCC compiler (and build-essential) along with make must also be installed. These packages are generally already configured on almost every developer's machine. To compile the project, just use make:

cd build
make
./todolist_app
Enter fullscreen mode Exit fullscreen mode

Main loop

The code below is the file containing the project's void main. Here, the main loop of the application is started. Before that, some structs and modules were defined to save, update, list, and delete the to-do list. Since the focus is only on the interface, these methods persist in a local list in memory.

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;
}

Enter fullscreen mode Exit fullscreen mode

Before the application window is created, the GTK API requires an initialization call passing the arguments from the function call. The gtk_main method starts the graphical interface's event loop.

The MainWindow module is defined in 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());
    }
}
Enter fullscreen mode Exit fullscreen mode

Similar to the approach used in web technologies and Android development, in GTK we have an interface to create a window with gtk_window_new, and inside it, we add various elements that can contain many other elements, forming a tree structure. The elements here are GtkWidget. In the documentation of the library, we can find the various types of widgets we can use.

In my experiment, I did not use any tool for screen design. Everything was done through the programming interface. However, GTK allows the creation of XML files that represent the screen. Glade is a graphical interface that allows the creation of these XMLs.

gui Article's
30 articles in total
Favicon
Criando interface gráfica Desktop nativa utilizando C++ com GTK3
Favicon
Interview with Eson (Seven), Creator of DocKit!
Favicon
Tkinter: Python's Secret Weapon for Stunning GUIs
Favicon
Creating a Native Desktop GUI Using C++ with GTK
Favicon
FEATool Multiphysics 1.17 - Multi-CFD solver and OpenFOAM Simulation Toolbox
Favicon
Introduction to Linux GUIs: Unpacking the Basics of Desktop Environments, Window Managers, and More
Favicon
Gui version for my docker-like solution native to macOS
Favicon
How to Build an SQLite GUI (Fast & Easy Tutorial)
Favicon
Go and WebUI
Favicon
AI Image and AI Chat pocket tool | Python GUI for windows.
Favicon
From Blue To Cool: We’ve given the MIDAS UI a makeover
Favicon
My opinion on the Tauri framework
Favicon
How One Experienced Software Engineer Learns a New Programming Language
Favicon
A detailed comparison of Toad for Oracle and dbForge Studio for Oracle
Favicon
Best alternative to SQLyog
Favicon
Docker - a terminal GUI
Favicon
RSGL | Modular header-only cross-platform GUI Library for easily creating GUI software your way!
Favicon
Rust has Reignited My Love for Programming
Favicon
Code-Along: How to Develop a REST API Dashboard
Favicon
How to Create a SQL Server GUI in 4 Steps
Favicon
Experimenting with GUIs on the Pi Zero
Favicon
Mastering Qt: Complete C++ GUI Course
Favicon
Mastering Qt: Complete C++ GUI Course
Favicon
How to Create a SQL GUI in 4 Steps
Favicon
Creating a GUI in Python With TtkBootstrap
Favicon
Trying egui: building a Cistercian Clock with Rust GUI
Favicon
Building a Custom Alarm Clock with Python and Tkinter
Favicon
How To Create GUI Window Using Python's Tkinter
Favicon
Select the best GUI toolkit – part 7: pyQt
Favicon
Why won't my code open the Tkinter window?

Featured ones: