Kapitel 8
Alle Codedateien dieses Kapitels herunterladen
08_7_1_DieAusgabeAusschalten.cpp
/*
* 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
/*
* 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
/*
* 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
/*
* 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;
}