C++ GUI Applications

Building desktop applications with graphical user interfaces

🖥️ What are GUI Applications?

GUI applications provide visual interfaces with windows, buttons, and interactive elements using frameworks like Qt, wxWidgets, FLTK, and ImGui for desktop software development.


// Simple Qt application structure
#include <QApplication>
#include <QPushButton>

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    QPushButton button("Hello World!");
    button.show();
    return app.exec();
}
                                    

Output:

[Window opens with "Hello World!" button]

Popular GUI Frameworks

🎨

Qt

Cross-platform with rich widgets

QWidget window;
QPushButton button("Click me");
window.show();
🔧

wxWidgets

Native look and feel

wxFrame* frame = new wxFrame(
    nullptr, wxID_ANY, "Hello");
frame->Show();

FLTK

Lightweight and fast

Fl_Window window(300, 200);
Fl_Button button(100, 50, 
    100, 30, "Button");
window.show();
🎮

ImGui

Immediate mode for tools

ImGui::Begin("Window");
if (ImGui::Button("Click"))
    clicked = true;
ImGui::End();

🔹 Simple Calculator (Qt Style)

The Simple Calculator (Qt Style) simulates a Qt-based graphical calculator interface for performing basic arithmetic. It handles operations like addition, subtraction, multiplication, and division, displaying formatted results such as 25 and 40.000000. Built with Qt's signal-slot architecture, it provides responsive feedback and clear visual output, mimicking real widget behavior. This example demonstrates how to structure GUI applications using event-driven principles and proper layout management, essential for building interactive desktop tools in C++ with Qt.

#include <iostream>
#include <string>
#include <functional>
#include <map>

// Simplified GUI Calculator Class
class Calculator {
private:
    double currentValue;
    double storedValue;
    std::string currentOperation;
    std::string display;
    
public:
    Calculator() : currentValue(0), storedValue(0), display("0") {}
    
    void inputNumber(int number) {
        if (display == "0") {
            display = std::to_string(number);
        } else {
            display += std::to_string(number);
        }
        currentValue = std::stod(display);
        updateDisplay();
    }
    
    void inputOperation(const std::string& op) {
        if (!currentOperation.empty()) {
            calculate();
        }
        storedValue = currentValue;
        currentOperation = op;
        display = "0";
        std::cout << "Operation: " << op << std::endl;
    }
    
    void calculate() {
        if (currentOperation.empty()) return;
        
        double result = 0;
        if (currentOperation == "+") {
            result = storedValue + currentValue;
        } else if (currentOperation == "-") {
            result = storedValue - currentValue;
        } else if (currentOperation == "*") {
            result = storedValue * currentValue;
        } else if (currentOperation == "/") {
            if (currentValue != 0) {
                result = storedValue / currentValue;
            } else {
                std::cout << "Error: Division by zero!" << std::endl;
                return;
            }
        }
        
        currentValue = result;
        display = std::to_string(result);
        currentOperation.clear();
        updateDisplay();
    }
    
    void clear() {
        currentValue = 0;
        storedValue = 0;
        currentOperation.clear();
        display = "0";
        updateDisplay();
    }
    
    std::string getDisplay() const {
        return display;
    }
    
private:
    void updateDisplay() {
        std::cout << "Display: " << display << std::endl;
    }
};

// Simulate button clicks
int main() {
    Calculator calc;
    
    std::cout << "=== Calculator Demo ===" << std::endl;
    
    // Simulate: 15 + 25 = 40
    calc.inputNumber(1);
    calc.inputNumber(5);
    calc.inputOperation("+");
    calc.inputNumber(2);
    calc.inputNumber(5);
    calc.calculate();
    
    std::cout << "Final result: " << calc.getDisplay() << std::endl;
    
    return 0;
}

Output:

=== Calculator Demo ===

Display: 1

Display: 15

Operation: +

Display: 2

Display: 25

Display: 40.000000

Final result: 40.000000

🔹 Event-Driven Programming

Event-Driven Programming is a core paradigm for creating responsive GUI applications where user actions trigger events. It involves components like windows and buttons that emit events such as Button Click or Window Close. For example, clicking buttons labeled 'Save', 'Load', or 'Exit' executes corresponding handlers, while closing a window like My Application triggers cleanup routines. This non-blocking approach uses event loops to listen for and dispatch actions, ensuring smooth, interactive user experiences in frameworks like Qt, Windows Forms, or web browsers.

#include <iostream>
#include <vector>
#include <functional>
#include <string>

enum class EventType {
    BUTTON_CLICK,
    TEXT_CHANGED,
    WINDOW_CLOSE
};

struct Event {
    EventType type;
    std::string data;
    
    Event(EventType t, const std::string& d = "") : type(t), data(d) {}
};

class EventHandler {
public:
    using EventCallback = std::function<void(const Event&)>;
    
private:
    std::vector<std::pair<EventType, EventCallback>> handlers;
    
public:
    void subscribe(EventType type, EventCallback callback) {
        handlers.emplace_back(type, callback);
    }
    
    void emit(const Event& event) {
        std::cout << "Event emitted: " << eventTypeToString(event.type) << std::endl;
        
        for (const auto& handler : handlers) {
            if (handler.first == event.type) {
                handler.second(event);
            }
        }
    }
    
private:
    std::string eventTypeToString(EventType type) {
        switch (type) {
            case EventType::BUTTON_CLICK: return "Button Click";
            case EventType::TEXT_CHANGED: return "Text Changed";
            case EventType::WINDOW_CLOSE: return "Window Close";
            default: return "Unknown";
        }
    }
};

class SimpleWindow {
private:
    EventHandler eventHandler;
    std::string title;
    bool isOpen;
    
public:
    SimpleWindow(const std::string& windowTitle) : title(windowTitle), isOpen(true) {
        // Subscribe to events
        eventHandler.subscribe(EventType::BUTTON_CLICK, 
            [this](const Event& e) { onButtonClick(e); });
        
        eventHandler.subscribe(EventType::WINDOW_CLOSE, 
            [this](const Event& e) { onWindowClose(e); });
        
        std::cout << "Window created: " << title << std::endl;
    }
    
    void simulateButtonClick(const std::string& buttonName) {
        if (isOpen) {
            eventHandler.emit(Event(EventType::BUTTON_CLICK, buttonName));
        }
    }
    
    void simulateClose() {
        if (isOpen) {
            eventHandler.emit(Event(EventType::WINDOW_CLOSE, title));
        }
    }
    
    bool getIsOpen() const { return isOpen; }
    
private:
    void onButtonClick(const Event& event) {
        std::cout << "Button '" << event.data << "' was clicked!" << std::endl;
        
        if (event.data == "Exit") {
            simulateClose();
        }
    }
    
    void onWindowClose(const Event& event) {
        std::cout << "Closing window: " << event.data << std::endl;
        isOpen = false;
    }
};

int main() {
    SimpleWindow window("My Application");
    
    // Simulate user interactions
    window.simulateButtonClick("Save");
    window.simulateButtonClick("Load");
    window.simulateButtonClick("Exit");
    
    std::cout << "Window is " << (window.getIsOpen() ? "open" : "closed") << std::endl;
    
    return 0;
}

Output:

Window created: My Application

Event emitted: Button Click

Button 'Save' was clicked!

Event emitted: Button Click

Button 'Load' was clicked!

Event emitted: Button Click

Button 'Exit' was clicked!

Event emitted: Window Close

Closing window: My Application

Window is closed

🔹 Simple Layout Manager

The Simple Layout Manager automates the positioning and arrangement of GUI elements within defined geometric bounds. It organizes widgets like buttons (OK, Cancel) and labels (Status) using layouts such as Horizontal Layout, specifying each element's rectangle (e.g., Rect(10, 10, 96, 40)). This manager handles dynamic resizing, alignment, and spacing, ensuring consistent interfaces across different screen sizes. By abstracting manual positioning, it simplifies UI development, improves visual consistency, and is foundational in toolkits like Qt, GTK, or custom GUI frameworks.

#include <iostream>
#include <vector>
#include <string>
#include <memory>

struct Rectangle {
    int x, y, width, height;
    
    Rectangle(int x = 0, int y = 0, int w = 0, int h = 0) 
        : x(x), y(y), width(w), height(h) {}
    
    void print() const {
        std::cout << "Rect(" << x << ", " << y << ", " 
                  << width << ", " << height << ")" << std::endl;
    }
};

class Widget {
protected:
    Rectangle bounds;
    std::string name;
    
public:
    Widget(const std::string& n) : name(n) {}
    virtual ~Widget() = default;
    
    virtual void setBounds(const Rectangle& rect) {
        bounds = rect;
    }
    
    Rectangle getBounds() const { return bounds; }
    std::string getName() const { return name; }
    
    virtual void draw() const {
        std::cout << name << " at ";
        bounds.print();
    }
};

class Button : public Widget {
public:
    Button(const std::string& text) : Widget("Button: " + text) {}
};

class Label : public Widget {
public:
    Label(const std::string& text) : Widget("Label: " + text) {}
};

class HorizontalLayout {
private:
    std::vector<std::unique_ptr<Widget>> widgets;
    Rectangle containerBounds;
    int spacing;
    
public:
    HorizontalLayout(const Rectangle& bounds, int s = 5) 
        : containerBounds(bounds), spacing(s) {}
    
    void addWidget(std::unique_ptr<Widget> widget) {
        widgets.push_back(std::move(widget));
        updateLayout();
    }
    
    void updateLayout() {
        if (widgets.empty()) return;
        
        int widgetWidth = (containerBounds.width - spacing * (widgets.size() - 1)) / widgets.size();
        int currentX = containerBounds.x;
        
        for (auto& widget : widgets) {
            Rectangle widgetBounds(currentX, containerBounds.y, 
                                 widgetWidth, containerBounds.height);
            widget->setBounds(widgetBounds);
            currentX += widgetWidth + spacing;
        }
    }
    
    void draw() const {
        std::cout << "=== Horizontal Layout ===" << std::endl;
        for (const auto& widget : widgets) {
            widget->draw();
        }
    }
};

int main() {
    // Create a horizontal layout
    HorizontalLayout layout(Rectangle(10, 10, 300, 40));
    
    // Add widgets
    layout.addWidget(std::make_unique<Button>("OK"));
    layout.addWidget(std::make_unique<Button>("Cancel"));
    layout.addWidget(std::make_unique<Label>("Status"));
    
    // Draw the layout
    layout.draw();
    
    return 0;
}

Output:

=== Horizontal Layout ===

Button: OK at Rect(10, 10, 96, 40)

Button: Cancel at Rect(111, 10, 96, 40)

Label: Status at Rect(212, 10, 96, 40)

🧠 Test Your Knowledge

Which GUI framework is known for immediate mode rendering?