Link Search Menu Expand Document

Kapitel 8

Alle Codedateien dieses Kapitels herunterladen

08_2_1_HelloWorldGUI.cpp

Datei herunterladen

#include <nana/gui.hpp>
#include <nana/gui/widgets/label.hpp>

int main()
{
  // Ein Nana-Formular als Basis des Fensters anlegen
  nana::form window;
  window.caption("C++ Schnelleinstieg");

  // Ein Text-Label anlegen. Es soll zentriert sein.
  nana::label textLabel(window, "Hallo Welt!");
  textLabel.text_align(nana::align::center, nana::align_v::center);

  // Das Label dem Layout hinzufügen
  window.div("<myText>");
  window["myText"] << textLabel;
  window.collocate();

  // Fenster anzeigen und Nana starten
  window.show();
  nana::exec();
  return 0;
}

08_2_2_HelloWorldButton.cpp

Datei herunterladen

#include <nana/gui.hpp>
#include <nana/gui/widgets/label.hpp>
#include <nana/gui/widgets/button.hpp>
int main()
{
  // Ein Nana-Formular als Basis des Fensters anlegen
  nana::form window;
  window.caption("C++ Schnelleinstieg");

  // Ein Text-Label anlegen. Es soll zentriert sein.
  nana::label textLabel(window, "Hallo Welt!");
  textLabel.text_align(nana::align::center, nana::align_v::center);

  // Eine Schaltfläche mit Click-Funktion anlegen
  nana::button button(window, "Beenden");
  button.events().click([&]() {
      window.close();
    });

  // Beide Elemente dem Layout hinzufügen
  window.div("vertical <myText><button>");
  window["myText"] << textLabel;
  window["button"] << button;
  window.collocate();

  // Fenster anzeigen und Nana starten
  window.show();
  nana::exec();
  return 0;
}

08_3_Callbacks.cpp

Datei herunterladen

#include <iostream>
#include <nana/gui.hpp>
#include <nana/gui/widgets/label.hpp>
#include <nana/gui/widgets/button.hpp>
int main()
{
  // Ein Nana-Formular als Basis des Fensters anlegen
  nana::form window;
  window.caption("C++ Schnelleinstieg");

  // Ein Text-Label anlegen. Es soll zentriert sein.
  nana::label textLabel(window, "Hallo Welt!");
  textLabel.text_align(nana::align::center, nana::align_v::center);

  // Eine Schaltfläche mit Click-Funktion anlegen
  nana::button button(window, "Beenden");
  button.events().click([&]() {
      window.close();
    });
  // Ein Resize-Event registrieren
  window.events().resizing(
    [](const nana::arg_resizing& a)
    {
      // arg_resizing ist ein struct, in dem die Größe steht
      std::cout << a.width << "x" << a.height << std::endl;
    });

  // Beide Elemente dem Layout hinzufügen
  window.div("vertical <myText><button>");
  window["myText"] << textLabel;
  window["button"] << button;
  window.collocate();

  // Fenster anzeigen und Nana starten
  window.show();
  nana::exec();

  return 0;
}

08_4_1_Lernkarten.cpp

Datei herunterladen

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

#include <nana/gui.hpp>
#include <nana/gui/msgbox.hpp>
#include <nana/gui/place.hpp>
#include <nana/gui/widgets/button.hpp>
#include <nana/gui/widgets/label.hpp>
#include <nana/gui/widgets/panel.hpp>
#include <nana/gui/widgets/tabbar.hpp>
#include <nana/gui/widgets/textbox.hpp>

struct Question
{
  Question(const std::string& q, const std::string& a, int c)
    : question(q), answer(a), category(c)
  {
  }
  std::string question;
  std::string answer;
  int category = 0;
};

class QuestionManager
{
 public:
  void promote(const Question& question)
  {
    // Frage wurde richtig beantwortet und soll eins weiter
    move(question, question.category + 1);
  }
  void degrade(const Question& question)
  {
    // Falsch beantwortet - zurück in die erste Kategorie
    move(question, 0);
  }
  std::vector<Question> getQuestions(int category)
  {
    // Suche alle Fragen aus der gewünschten Kategorie
    std::vector<Question> result;
    for (Question stored : questions)
    {
      if (stored.category == category)
      {
        result.push_back(stored);
      }
    }
    return result;
  }
  void addQuestion(const std::string& question,
    const std::string& answer)
  {
    questions.push_back(Question(question, answer, 0));
  }
 private:
  void move(const Question& question, int newCategory)
  {
    // Wichtig: Per Referenz iterieren. & nicht vergessen!
    for (Question& stored : questions)
    {
      if (stored.question == question.question)
      {
        stored.category = newCategory;
        if (stored.category > maxCategories)
        {
          // Darf nicht höher als das Maximum liegen
          stored.category = maxCategories;
        }
        if (stored.category < 0)
        {
          // Der kleinste Index muss null sein
          stored.category = 0;
        }
        // Frage wurde gefunden und verschoben
        return;
      }
    }
    std::cerr << "changeCategory: Could not find '"
      << question.question << "'" << std::endl;
  }
  const int maxCategories = 2;  // Drei Kategorien (inkl 0)
  std::vector<Question> questions;
};

class LearnTab : public nana::panel<false>
{
 private:
  nana::place content;
  nana::label header;  // Frage X von Y
  nana::label questionHeader;  // Frage:
  nana::label question;
  nana::label answerHeader;  // Antwort:
  nana::textbox answer;  // Eingabefeld für die Antwort
  nana::button button;  // Antworten
  QuestionManager& manager;
  const int category;
  std::vector<Question> questions;
  int currentQuestionIndex = 0;
 public:
  LearnTab(nana::window window, QuestionManager& m, int c)
    : nana::panel<false>(window),
      manager(m), category(c),
      content(*this), header(*this),
      questionHeader(*this), question(*this),
      answerHeader(*this), answer(*this), button(*this)
  {
    content.div("vertical margin=10"
      "<header>"
      "<<questionHeader><question>>"
      "<height=30 <answerHeader fit><answer><button fit>>");
    content["header"] << header;
    content["questionHeader"] << questionHeader;
    content["question"] << question;
    content["answerHeader"] << answerHeader;
    content["answer"] << answer;
    content["button"] << button;

    header.text_align(nana::align::center, nana::align_v::center);

    questionHeader.caption("Frage:");
    answerHeader.caption("Antwort:");
    answer.multi_lines(false);
    button.caption("Antworten");

    events().expose(
      [&](nana::arg_expose arg)
      {
        if (arg.exposed)  // Element ist sichtbar geworden
        {
          // Die Fragen neu einlesen und die erste anzeigen
          refresh();
        }
      });
    button.events().click([&]()
      {
        if (currentQuestionIndex < 0
          || currentQuestionIndex >= questions.size())
        {
          // Aktuell ist keine Frage im Tab aktiv
          return;
        }
        evaluateAnswer(questions.at(currentQuestionIndex),
          answer.text());
        layoutNextQuestion();
      });
    // Die Fragen einlesen und die erste anzeigen
    refresh();
  }
  void refresh()
  {
    // Lade die Fragen für diese Kategorie
    questions = manager.getQuestions(category);
    currentQuestionIndex = -1;
    layoutNextQuestion();
  }
  // Klassen können auch mehrere private Abschnitte haben:
 private:
  void layoutNextQuestion()
  {
    currentQuestionIndex++;
    if (currentQuestionIndex >= questions.size())
    {
      // Die Fragen neu laden und vom Anfang beginnen
      questions = manager.getQuestions(category);
      currentQuestionIndex = 0;
    }
    if (questions.size() == 0)
    {
      header.caption("Keine Fragen in dieser Kategorie");
      question.caption("");
      return;
    }
    std::string headline = "Frage "
      + std::to_string(currentQuestionIndex + 1)
      + " von " + std::to_string(questions.size());
    header.caption(headline);
    Question q = questions.at(currentQuestionIndex);

    // Formatmodus aktivieren und mit <bold></> fett machen
    question.format(true);
    question.caption("<bold>" + q.question + "</>");
  }
  void evaluateAnswer(const Question& reference,
    const std::string& userAnswer)
  {
    nana::msgbox msg("Die Antwort ist...");
    if (userAnswer == reference.answer)
    {
      manager.promote(reference);
      msg << "Korrekt!";
    }
    else
    {
      manager.degrade(reference);
      msg.icon(nana::msgbox::icon_error);
      msg << "Leider Falsch!";
    }
    msg.show();
  }
};

int main()
{
  QuestionManager manager;
  manager.addQuestion("Schleife", "Loop");
  manager.addQuestion("Klasse", "Class");
  manager.addQuestion("Zeichenkette", "String");

  // (1) Ein Nana-Formular als Basis des Fensters anlegen
  //     Das Fenster ist 500x250 Pixel groß.
  // ===================================================
  nana::form window(nana::API::make_center(500, 250));
  window.caption("Lernkarten");

  // (2) Grundlayout festlegen und Elemente verknüpfen
  // ===================================================
  LearnTab tab(window, manager, 0);
  window.div("<tab>");
  window["tab"] << tab;

  // (3) Fenster anzeigen und Nana starten
  // ===================================================
  window.collocate();
  window.show();
  nana::exec();
  return 0;
}

08_4_2_TextFormat.cpp

Datei herunterladen

#include <nana/gui.hpp>
#include <nana/gui/widgets/label.hpp>

int main()
{
  // Ein Nana-Formular als Basis des Fensters anlegen
  nana::form window;
  window.caption("Textformatierung");

  nana::label labelA(window);
  nana::label labelB(window);
  nana::label labelC(window);
  nana::label labelD(window);

  labelA.format(true);
  labelA.caption("Teilweise <bold>fett</>");

  labelB.format(true);
  labelB.caption("<blue>Blau</> <red>Rot</> Schwarz");

  labelC.format(true);
  labelC.caption("<size=20>Riesig</> Klein");

  labelD.format(true);
  labelD.caption("<bold blue>Fettes Blau</>");

  // Die Label dem Layout hinzufügen
  window.div("vertical margin=10 <a><b><c><d>");
  window["a"] << labelA;
  window["b"] << labelB;
  window["c"] << labelC;
  window["d"] << labelD;
  window.collocate();

  // Fenster anzeigen und Nana starten
  window.show();
  nana::exec();

  return 0;
}

08_4_3_LernkartenMehrereTabs.cpp

Datei herunterladen

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

#include <nana/gui.hpp>
#include <nana/gui/msgbox.hpp>
#include <nana/gui/place.hpp>
#include <nana/gui/widgets/button.hpp>
#include <nana/gui/widgets/label.hpp>
#include <nana/gui/widgets/panel.hpp>
#include <nana/gui/widgets/tabbar.hpp>
#include <nana/gui/widgets/textbox.hpp>

struct Question
{
  Question(const std::string& q, const std::string& a, int c)
    : question(q), answer(a), category(c)
  {
  }
  std::string question;
  std::string answer;
  int category = 0;
};

class QuestionManager
{
 public:
  void promote(const Question& question)
  {
    // Frage wurde richtig beantwortet und soll eins weiter
    move(question, question.category + 1);
  }
  void degrade(const Question& question)
  {
    // Falsch beantwortet - zurück in die erste Kategorie
    move(question, 0);
  }
  std::vector<Question> getQuestions(int category)
  {
    // Suche alle Fragen aus der gewünschten Kategorie
    std::vector<Question> result;
    for (Question stored : questions)
    {
      if (stored.category == category)
      {
        result.push_back(stored);
      }
    }
    return result;
  }
  void addQuestion(const std::string& question,
    const std::string& answer)
  {
    questions.push_back(Question(question, answer, 0));
  }
 private:
  void move(const Question& question, int newCategory)
  {
    // Wichtig: Per Referenz iterieren. & nicht vergessen!
    for (Question& stored : questions)
    {
      if (stored.question == question.question)
      {
        stored.category = newCategory;
        if (stored.category > maxCategories)
        {
          // Darf nicht höher als das Maximum liegen
          stored.category = maxCategories;
        }
        if (stored.category < 0)
        {
          // Der kleinste Index muss null sein
          stored.category = 0;
        }
        // Frage wurde gefunden und verschoben
        return;
      }
    }
    std::cerr << "changeCategory: Could not find '"
      << question.question << "'" << std::endl;
  }
  const int maxCategories = 2;  // Drei Kategorien (inkl 0)
  std::vector<Question> questions;
};

class LearnTab : public nana::panel<false>
{
 private:
  nana::place content;
  nana::label header;  // Frage X von Y
  nana::label questionHeader;  // Frage:
  nana::label question;
  nana::label answerHeader;  // Antwort:
  nana::textbox answer;  // Eingabefeld für die Antwort
  nana::button button;  // Antworten
  QuestionManager& manager;
  const int category;
  std::vector<Question> questions;
  int currentQuestionIndex = 0;
 public:
  LearnTab(nana::window window, QuestionManager& m, int c)
    : nana::panel<false>(window),
      manager(m), category(c),
      content(*this), header(*this),
      questionHeader(*this), question(*this),
      answerHeader(*this), answer(*this), button(*this)
  {
    content.div("vertical margin=10"
      "<header>"
      "<<questionHeader><question>>"
      "<height=30 <answerHeader fit><answer><button fit>>");
    content["header"] << header;
    content["questionHeader"] << questionHeader;
    content["question"] << question;
    content["answerHeader"] << answerHeader;
    content["answer"] << answer;
    content["button"] << button;

    header.text_align(nana::align::center, nana::align_v::center);

    questionHeader.caption("Frage:");
    answerHeader.caption("Antwort:");
    answer.multi_lines(false);
    button.caption("Antworten");

    events().expose(
      [&](nana::arg_expose arg)
      {
        if (arg.exposed)  // Element ist sichtbar geworden
        {
          // Die Fragen neu einlesen und die erste anzeigen
          refresh();
        }
      });
    button.events().click([&]()
      {
        if (currentQuestionIndex < 0
          || currentQuestionIndex >= questions.size())
        {
          // Aktuell ist keine Frage im Tab aktiv
          return;
        }
        evaluateAnswer(questions.at(currentQuestionIndex),
          answer.text());
        layoutNextQuestion();
      });
    // Die Fragen einlesen und die erste anzeigen
    refresh();
  }
  void refresh()
  {
    // Lade die Fragen für diese Kategorie
    questions = manager.getQuestions(category);
    currentQuestionIndex = -1;
    layoutNextQuestion();
  }
  // Klassen können auch mehrere private Abschnitte haben:
 private:
  void layoutNextQuestion()
  {
    currentQuestionIndex++;
    if (currentQuestionIndex >= questions.size())
    {
      // Die Fragen neu laden und vom Anfang beginnen
      questions = manager.getQuestions(category);
      currentQuestionIndex = 0;
    }
    if (questions.size() == 0)
    {
      header.caption("Keine Fragen in dieser Kategorie");
      question.caption("");
      return;
    }
    std::string headline = "Frage "
      + std::to_string(currentQuestionIndex + 1)
      + " von " + std::to_string(questions.size());
    header.caption(headline);
    Question q = questions.at(currentQuestionIndex);

    // Formatmodus aktivieren und mit <bold></> fett machen
    question.format(true);
    question.caption("<bold>" + q.question + "</>");
  }
  void evaluateAnswer(const Question& reference,
    const std::string& userAnswer)
  {
    nana::msgbox msg("Die Antwort ist...");
    if (userAnswer == reference.answer)
    {
      manager.promote(reference);
      msg << "Korrekt!";
    }
    else
    {
      manager.degrade(reference);
      msg.icon(nana::msgbox::icon_error);
      msg << "Leider Falsch!";
    }
    msg.show();
  }
};

int main()
{
  QuestionManager manager;
  manager.addQuestion("Schleife", "Loop");
  manager.addQuestion("Klasse", "Class");
  manager.addQuestion("Zeichenkette", "String");

  // (1) Ein Nana-Formular als Basis des Fensters anlegen
  // ===================================================
  nana::form window(nana::API::make_center(500, 250));
  window.caption("Lernkarten");

  // (2) Grundlayout festlegen und Elemente verknüpfen
  // ===================================================
  window.div("vertical "
    "<height=30 <>|20%<button>>"
    "<tabBar height=30>"
    "<tabFrame>");
  // Eine Schaltfläche anlegen. Aktuell noch ohne Funktion
  nana::button button(window);
  window["button"] << button;

  // Navigationsleiste für Tabs hinzufügen
  nana::tabbar<std::string> tabBar(window);
  window["tabBar"] << tabBar;

  // (3) Nun die Elemente mit Leben füllen
  // ===================================================
  // Drei Tabs für die drei Lernkategorien anlegen
  LearnTab learnCategory1(window, manager, 0);
  LearnTab learnCategory2(window, manager, 1);
  LearnTab learnCategory3(window, manager, 2);

  window["tabFrame"]
    .fasten(learnCategory1)
    .fasten(learnCategory2)
    .fasten(learnCategory3);
  tabBar
    .append("Kategorie 1", learnCategory1)
    .append("Kategorie 2", learnCategory2)
    .append("Kategorie 3", learnCategory3);

  // Aktiviere die erste Kategorie
  tabBar.activated(0);

  // (4) Fenster anzeigen und Nana starten
  // ===================================================
  window.collocate();
  window.show();
  nana::exec();
  return 0;
}

08_5_1_Dateiarbeit.cpp

Datei herunterladen

#include <fstream>
#include <string>
#include <iostream>
int main()
{
  const std::string filePath = "test.txt";
  // ofstream steht für "output file stream"
  std::ofstream outputFile(filePath);
  outputFile << "Eine Testausgabe";
  // Die Datei muss geschlossen werden, damit sie
  // geschrieben wird
  outputFile.close();
  std::cout << "-- Datei geschrieben --" << std::endl;

  // ifstream steht für "input file stream"
  std::ifstream inputFile(filePath);
  if (!inputFile.good())
  {
    std::cerr << "Konnte Datei " << filePath
      << " nicht lesend \224ffnen!" << std::endl;
    return 1;
  }
  std::string textFromFile;
  std::getline(inputFile, textFromFile);
  std::cout << "Inhalt: " << textFromFile << std::endl;
  return 0;
}

08_5_2_LernkartenSpeichern.cpp

Datei herunterladen

#include <fstream>
#include <iostream>
#include <string>
#include <vector>

#include <nana/gui.hpp>
#include <nana/gui/msgbox.hpp>
#include <nana/gui/place.hpp>
#include <nana/gui/widgets/button.hpp>
#include <nana/gui/widgets/label.hpp>
#include <nana/gui/widgets/panel.hpp>
#include <nana/gui/widgets/tabbar.hpp>
#include <nana/gui/widgets/textbox.hpp>

struct Question
{
  Question(const std::string& q,const std::string& a,int c)
    : question(q), answer(a), category(c)
  {
  }
  std::string question;
  std::string answer;
  int category = 0;
};

class QuestionManager
{
 public:
  void promote(const Question& question)
  {
    // Frage wurde richtig beantwortet und soll eins weiter
    move(question, question.category + 1);
  }
  void degrade(const Question& question)
  {
    // Falsch beantwortet - zurück in die erste Kategorie
    move(question, 0);
  }
  std::vector<Question> getQuestions(int category)
  {
    // Suche alle Fragen aus der gewünschten Kategorie
    std::vector<Question> result;
    for (Question stored : questions)
    {
      if (stored.category == category)
      {
        result.push_back(stored);
      }
    }
    return result;
  }
  void addQuestion(const std::string& question,
    const std::string& answer)
  {
    questions.push_back(Question(question, answer, 0));
  }
  void save(const std::string& filePath)
  {
    // ofstream steht für "output file stream"
    std::ofstream file(filePath);
    for (Question question : questions)
    {
      file << question.question << "\n"
        << question.answer << "\n"
        << question.category;
      // Eine leere Zeile trennt die Einträge
      file << "\n\n";
    }
  }
  void load(const std::string& filePath)
  {
    // ifstream steht für "input file stream"
    std::ifstream file(filePath);
    if (!file.good())
    {
      std::cerr << "Fragendatei " << filePath
        << " existiert nicht" << std::endl;
      return;
    }
    // Fragen löschen und neu einlesen
    questions.clear();

    std::string question;
    std::string answer;
    std::string category;
    while (true)
    {
      std::getline(file, question);
      std::getline(file, answer);
      std::getline(file, category);
      if (!file.good())
      {
        // Wenn das Einlesen fehlgeschlagen ist, sind wir
        // entweder am Ende der Datei, oder es gab einen
        // anderen Fehler. Daher verlassen wir die Schleife
        break;
      }
      questions.push_back(Question(question, answer,
        std::stoi(category)));
      // Leere Zeile am Ende jeden Eintrags überspringen
      std::getline(file, question);
    }
  }
 private:
  void move(const Question& question, int newCategory)
  {
    // Wichtig: Per Referenz iterieren. & nicht vergessen!
    for (Question& stored : questions)
    {
      if (stored.question == question.question)
      {
        stored.category = newCategory;
        if (stored.category > maxCategories)
        {
          // Darf nicht höher als das Maximum liegen
          stored.category = maxCategories;
        }
        if (stored.category < 0)
        {
          // Der kleinste Index muss null sein
          stored.category = 0;
        }
        // Frage wurde gefunden und verschoben
        return;
      }
    }
    std::cerr << "changeCategory: Could not find '"
      << question.question << "'" << std::endl;
  }
  const int maxCategories = 2;  // Drei Kategorien (inkl 0)
  std::vector<Question> questions;
};

class LearnTab : public nana::panel<false>
{
 private:
  nana::place content;
  nana::label header;  // Frage X von Y
  nana::label questionHeader;  // Frage:
  nana::label question;
  nana::label answerHeader;  // Antwort:
  nana::textbox answer;  // Eingabefeld für die Antwort
  nana::button button;  // Antworten
  QuestionManager& manager;
  const int category;
  std::vector<Question> questions;
  int currentQuestionIndex = 0;
 public:
  LearnTab(nana::window window, QuestionManager& m, int c)
    : nana::panel<false>(window),
      manager(m), category(c),
      content(*this), header(*this),
      questionHeader(*this), question(*this),
      answerHeader(*this), answer(*this), button(*this)
  {
    content.div("vertical margin=10"
      "<header>"
      "<<questionHeader><question>>"
      "<height=30 <answerHeader fit><answer><button fit>>");
    content["header"] << header;
    content["questionHeader"] << questionHeader;
    content["question"] << question;
    content["answerHeader"] << answerHeader;
    content["answer"] << answer;
    content["button"] << button;

    header.text_align(nana::align::center,
      nana::align_v::center);

    questionHeader.caption("Frage:");
    answerHeader.caption("Antwort:");
    answer.multi_lines(false);
    button.caption("Antworten");

    events().expose(
      [&](nana::arg_expose arg)
      {
        if (arg.exposed)  // Element ist sichtbar geworden
        {
          // Die Fragen neu einlesen und die erste anzeigen
          refresh();
        }
      });
    button.events().click([&]()
      {
        if (currentQuestionIndex < 0
          || currentQuestionIndex >= questions.size())
        {
          // Aktuell ist keine Frage im Tab aktiv
          return;
        }
        evaluateAnswer(questions.at(currentQuestionIndex),
          answer.text());
        layoutNextQuestion();
      });
    // Die Fragen einlesen und die erste anzeigen
    refresh();
  }
  void refresh()
  {
    // Lade die Fragen für diese Kategorie
    questions = manager.getQuestions(category);
    currentQuestionIndex = -1;
    layoutNextQuestion();
  }
  // Klassen können auch mehrere private Abschnitte haben:
 private:
  void layoutNextQuestion()
  {
    currentQuestionIndex++;
    if (currentQuestionIndex >= questions.size())
    {
      // Die Fragen neu laden und vom Anfang beginnen
      questions = manager.getQuestions(category);
      currentQuestionIndex = 0;
    }
    if (questions.size() == 0)
    {
      header.caption("Keine Fragen in dieser Kategorie");
      question.caption("");
      return;
    }
    std::string headline = "Frage "
      + std::to_string(currentQuestionIndex + 1)
      + " von " + std::to_string(questions.size());
    header.caption(headline);
    Question q = questions.at(currentQuestionIndex);

    // Formatmodus aktivieren und mit <bold></> fett machen
    question.format(true);
    question.caption("<bold>" + q.question + "</>");
  }
  void evaluateAnswer(const Question& reference,
    const std::string& userAnswer)
  {
    nana::msgbox msg("Die Antwort ist...");
    if (userAnswer == reference.answer)
    {
      manager.promote(reference);
      msg << "Korrekt!";
    }
    else
    {
      manager.degrade(reference);
      msg.icon(nana::msgbox::icon_error);
      msg << "Leider Falsch!";
    }
    msg.show();
  }
};

class AddingWindow : public nana::form
{
 public:
  AddingWindow(nana::window owner, QuestionManager& manager)
    : nana::form(owner, nana::size(300, 90)),
      question(*this), answer(*this), button(*this)
  {
    caption("Frage hinzufügen");
    div("vertical"
      "<question>"
      "<answer>"
      "<button>");
    (*this)["question"] << question;
    (*this)["answer"] << answer;
    (*this)["button"] << button;

    question.tip_string("Frage:");
    question.multi_lines(false);
    answer.tip_string("Antwort:");
    answer.multi_lines(false);
    button.caption("Hinzufügen");

    button.events().click([&]()
      {
        std::string questionText = question.text();
        std::string answerText = answer.text();
        if (questionText.empty() || answerText.empty())
        {
          // Eine Fehlernachricht anzeigen
          nana::msgbox msg("Eingabefehler!");
          msg.icon(nana::msgbox::icon_error);
          msg << "Geben Sie Frage und Antwort ein";
          msg.show();
        }
        else
        {
          // Frage hinzufügen und die Eingabemaske leeren
          manager.addQuestion(questionText, answerText);
          question.caption("");
          answer.caption("");
        }
      });
  }
  void show()
  {
    collocate();
    // Genau wie show() zeigt modality() das Fenster an,
    // der Unterschied ist jedoch, dass die Ausführung
    // wartet, bis das Fenster wieder geschlossen wurde.
    // Nur bei der msgbox wartet show() automatisch, bei
    // Fenstern nicht.
    modality();
  }
 private:
  nana::form window;
  nana::textbox question;
  nana::textbox answer;
  nana::button button;
};

int main()
{
  const std::string questionFilePath = "questions.txt";
  QuestionManager manager;
  manager.load(questionFilePath);

  // (1) Ein Nana-Formular als Basis des Fensters anlegen
  // ===================================================
  nana::form window(nana::API::make_center(500, 250));
  window.caption("Lernkarten");

  // (2) Grundlayout festlegen und Elemente verknüpfen
  // ===================================================
  window.div("vertical "
    "<height=30 <>|20%<button>>"
    "<tabBar height=30>"
    "<tabFrame>");
  // Eine Schaltfläche anlegen
  nana::button button(window);
  window["button"] << button;

  // Navigationsleiste für Tabs hinzufügen
  nana::tabbar<std::string> tabBar(window);
  window["tabBar"] << tabBar;

  // (3) Nun die Elemente mit Leben füllen
  // ===================================================
  // Drei Tabs für die drei Lernkategorien anlegen
  LearnTab learnCategory1(window, manager, 0);
  LearnTab learnCategory2(window, manager, 1);
  LearnTab learnCategory3(window, manager, 2);

  window["tabFrame"]
    .fasten(learnCategory1)
    .fasten(learnCategory2)
    .fasten(learnCategory3);
  tabBar
    .append("Kategorie 1", learnCategory1)
    .append("Kategorie 2", learnCategory2)
    .append("Kategorie 3", learnCategory3);

  // Aktiviere die erste Kategorie
  tabBar.activated(0);

  // Click-Event für den Button hinzufügen
  button.caption("Hinzufügen");
  button.events().click([&]()
    {
      AddingWindow addingWindow(window, manager);
      addingWindow.show();
      // Neue Fragen hinzugefügt. Lade erste Kategorie neu
      learnCategory1.refresh();
      manager.save(questionFilePath);
    });

  // (4) Fenster anzeigen und Nana starten
  // ===================================================
  window.collocate();
  window.show();
  nana::exec();
  // (5) Vor Beenden des Programms Fortschritt speichern
  // ===================================================
  manager.save(questionFilePath);
  return 0;
}