Kapitel 8

Alle Codedateien dieses Kapitels herunterladen

08_7_1_DieAusgabeAusschalten.cpp

Datei herunterladen

/*
 * Lösung für Übung 8.7.1 -	Die Ausgabe ausschalten
 *
 * Basierend auf dem Beispielprogramm 08_3_Callbacks.cpp
 */
#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([&]() {
    // Das Resize-Event entfernen
    window.events().resizing.clear();
  });
  // 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_7_2_DerAnAusschalter.cpp

Datei herunterladen

/*
 * Lösung für Übung 8.7.2 -	Der An- und Ausschalter
 *
 * Basierend auf dem Beispielprogramm 08_3_Callbacks.cpp
 */
#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");

  // Steuervariable für die Ausgabe
  bool output = true;

  button.events().click([&]() {
    output = !output;
    if (output)
    {
      std::cout << "Ausgabe: An" << std::endl;
    }
    else
    {
      std::cout << "Ausgabe: Aus" << std::endl;
    }
  });
  // Ein Resize-Event registrieren
  window.events().resizing(
    [&](const nana::arg_resizing& a)
    {
      if (output)
      {
        // 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_7_3_MausImHaus.cpp

Datei herunterladen

/*
 * Lösung für Übung 8.7.3 - Maus im Haus
 *
 */
#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");
  
  // Auf den Mauszeiger reagieren
  window.events().mouse_enter([]() {
    std::cout << "Maus zuhaus" << std::endl;
  });
  window.events().mouse_leave([]() {
    std::cout << "Maus au\341er Haus" << std::endl;
  });

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

  return 0;
}

08_7_4_NichtSoPedantisch.cpp

Datei herunterladen

/*
 * Lösung für Übung 8.7.4 -	Nicht so pedantisch!
 *
 * Basiert auf 08_5_2_LernkartenSpeichern.cpp
 * - Änderungen in der Methode evaluateAnswer() in der 
 *   Klasse LearnTab
 */
#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)
  {
    /*
     * Um Groß- und Kleinschreibung beim Vergleichen zu
     * ignorieren, wandeln wir alles in Kleinbuchstaben um
     */
    std::string referenceToLower = reference.answer;
    std::transform(referenceToLower.begin(), referenceToLower.end(),
      referenceToLower.begin(), tolower);
    std::string userToLower = userAnswer;
    std::transform(userToLower.begin(), userToLower.end(),
      userToLower.begin(), tolower);

    if (referenceToLower == userToLower)
    {
      nana::msgbox msg("Die Antwort ist...");
      manager.promote(reference);
      msg << "Korrekt!";
      msg.show();
    }
    else
    {
      // Keine exakte Übereinstimmung. Zeige eine Dialogbox mit Ja/Nein.
      // Der LearnTab muss mittels *this als übergeordnetes Fenster übergeben werden

      nana::msgbox msg(*this, "Rückfrage", nana::msgbox::yes_no);
      // Optional: spezielles Icon für die Rückfrage
      msg.icon(nana::msgbox::icon_question);
      msg << "Ihre Antwort stimmt nicht exakt überein. "
        << "Die richtige Antwort ist: " << std::endl
        << "- " << reference.answer << std::endl
        << "Ihre Antwort war: " << std::endl
        << "- " << userAnswer << std::endl
        << "War das korrekt?";
      if (nana::msgbox::pick_yes == msg.show())
      {
        // Der Benutzer hat die Antwort als korrekt bestätigt
        manager.promote(reference);
      }
      else
      {
        // Die Antwort war tatsächlich falsch
        manager.degrade(reference);
      }
    }
  }
};

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();
  return 0;
}