Menüs & Speicherdialog mit ImGui erstellen – Ein einfaches TXT-Notizbuch-Beispiel

In dieser Lektion lernen wir, Menüs darzustellen. Wir erstellen ein Programm ähnlich dem Windows-Notepad mit einer oberen Menüleiste und einem Untermenü „Speichern“. Klickt man auf Speichern, wird der gesamte Text aus dem unteren Eingabefeld in eine Datei geschrieben. Zusätzlich beheben wir das Problem mit verstümmelten Sonderzeichen und zeigen, wie man Schriftarten konfiguriert: ohne diese Einstellung werden nicht-englische Zeichen nur als Fragezeichen angezeigt.

Beim Speichern öffnet sich ein systemeigener Datei-Speicherdialog. Der Nutzer wählt ein Speicherverzeichnis, gibt einen Dateinamen ein und kann anschließend die Textdatei abspeichern.

Hier sehen Sie einen Screenshot des laufenden Programms:

Das Programm bietet grundlegende Bearbeitungs- und Speicherfunktionen wie das Windows-eigene Notepad, die kompilierte Ausführungsdatei ist extrem klein.

Bei Kompilierung im Release-Modus beträgt die Dateigröße nur 645 KB.

Sehen wir uns nun den vollständigen Quellcode an:

#pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup")
#pragma execution_character_set("utf-8")

#include <GLFW/glfw3.h>
#include "imgui.h"
#include "backends/imgui_impl_glfw.h"
#include "backends/imgui_impl_opengl3.h"
#include <cstdio>
#include <string>
#include <fstream>
#include <windows.h>
#include <io.h>
#include <commdlg.h>  // Benötigter Header für Dateidialoge

// Maximale Größe des Textpuffers
const int TEXT_BUFFER_MAX = 4096;
std::string editor_text;

// Text in Datei schreiben mit UTF-8 BOM
// Schreibt den Textinhalt in die angegebene Datei
bool SaveTextToFile(const char* filepath, const std::string& text)
{
    std::ofstream out_file(filepath, std::ios::out | std::ios::binary);
    if (!out_file.is_open())
        return false;
    const unsigned char utf8_bom[] = { 0xEF, 0xBB, 0xBF };
    out_file.write((char*)utf8_bom, sizeof(utf8_bom));
    out_file << text.c_str(); // Nur gültigen Text bis zum Nullterminator ausgeben
    out_file.close();
    return true;
}

// System-Speicherdialog anzeigen, gibt den gewählten Dateipfad zurück
bool ShowSaveFileDialog(char* out_path, DWORD out_size)
{
    OPENFILENAMEA ofn;   // Nutze die ANSI-Version A
    ZeroMemory(&ofn, sizeof(ofn));
    ofn.lStructSize = sizeof(ofn);
    ofn.hwndOwner = nullptr;
    ofn.lpstrFilter = "Textdateien\0*.txt\0Alle Dateien\0*.*\0"; // Filter für TXT oder alle Formate
    ofn.lpstrFile = out_path;
    ofn.nMaxFile = out_size;
    ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_OVERWRITEPROMPT;
    ofn.lpstrDefExt = "txt";

    return GetSaveFileNameA(&ofn);   // ANSI-Dialogfunktion aufrufen
}

// Schrift einrichten, um verstümmelte Fragezeichen zu beheben
// Passenden Schriftpfad für Ihre Umgebung eintragen oder Systemstandardschrift verwenden
void SetupFont(ImGuiIO& io)
{
    // Gängige chinesische Schrift unter Windows
    const char* font_path = "C:/Windows/Fonts/msyh.ttc"; // Microsoft YaHei
    float font_size = 18.0f;

    // Bereich für häufig genutzte vereinfachte chinesische Zeichen laden
    ImVector<ImWchar> ranges;
    ImFontGlyphRangesBuilder builder;
    builder.AddRanges(io.Fonts->GetGlyphRangesChineseSimplifiedCommon());
    builder.BuildRanges(&ranges);

    // Benutzerdefinierte Schrift laden
    io.Fonts->AddFontFromFileTTF(font_path, font_size, nullptr, ranges.Data);

    // Bei mehrsprachiger Unterstützung weitere Bereiche hinzufügen:
    // io.Fonts->AddFontFromFileTTF(font_path, font_size, nullptr, io.Fonts->GetGlyphRangesJapanese());
    // io.Fonts->AddFontFromFileTTF(font_path, font_size, nullptr, io.Fonts->GetGlyphRangesKorean());
}


int main()
{
    SetConsoleOutputCP(65001);
    // Wichtig: Puffer vergrößern und komplett mit Null füllen, um Speichermüll zu entfernen und ???-Zeichen zu reparieren
    editor_text.resize(TEXT_BUFFER_MAX, '\0');

    if (!glfwInit())
    {
        printf("GLFW Initialisierung fehlgeschlagen\n");
        return -1;
    }

    const char* glsl_version = "#version 330";
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    GLFWwindow* window = glfwCreateWindow(800, 600, "Text Notiz Editor", nullptr, nullptr);
    if (!window)
    {
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSwapInterval(1);

    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGuiIO& io = ImGui::GetIO();
    ImGui::StyleColorsLight();

    SetupFont(io); // Schrift laden, um Zeichenverstümmelung zu beheben

    ImGui_ImplGlfw_InitForOpenGL(window, true);
    ImGui_ImplOpenGL3_Init(glsl_version);

    bool show_save_success = false;
    bool show_save_fail = false;

	char path[MAX_PATH] = "";// Puffer für den gespeicherten Dateipfad

    while (!glfwWindowShouldClose(window))
    {
        glfwPollEvents();

        // Strg+S Speicher-Tastenkürzel
        if (io.KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_S))
        {
            if (ShowSaveFileDialog(path, MAX_PATH))
            {
                if (SaveTextToFile(path, editor_text))
                    show_save_success = true;
                else
                    show_save_fail = true;
            }
        }

        ImGui_ImplOpenGL3_NewFrame();
        ImGui_ImplGlfw_NewFrame();
        ImGui::NewFrame();

        ImGui::SetNextWindowPos(ImVec2(0, 0));
        ImGui::SetNextWindowSize(io.DisplaySize);

        ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration
            | ImGuiWindowFlags_NoMove
            | ImGuiWindowFlags_NoResize
            | ImGuiWindowFlags_NoSavedSettings
            | ImGuiWindowFlags_MenuBar;   // Pflichtflag, um die Menüleiste anzuzeigen

        ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
        ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);

        ImGui::Begin("Text Notiz Editor", nullptr, window_flags);

        // Oberste Menüleiste rendern
        if (ImGui::BeginMenuBar())
        {
            if (ImGui::BeginMenu("Datei"))
            {
				if (ImGui::MenuItem("Speichern", "Strg+S")) // Untermenü Speichern: Text + Tastenkürzel-Hinweis
                {
					// Gleiche Logik wie bei Buttons aus vorherigen Lektionen, Code läuft bei Menüklick
					if (ShowSaveFileDialog(path, MAX_PATH)) // Speicherdialog öffnen
                    {
						if (SaveTextToFile(path, editor_text)) // Datei speichern
                            show_save_success = true; // Erfolgsflag setzen für spätere Benachrichtigung
                        else
                            show_save_fail = true;
                    }
                }

                ImGui::Separator();

                if (ImGui::MenuItem("Beenden", "Esc")) // Menüpunkt zum Schließen des Programms
                {
					glfwSetWindowShouldClose(window, true); // Schließflag setzen, Programm beenden
                }
                ImGui::EndMenu();
                                

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert