This commit is contained in:
Asecave
2021-10-05 16:55:26 +02:00
parent 6559ef501d
commit 9facddedf3
12 changed files with 148 additions and 154 deletions

View File

@@ -0,0 +1,148 @@
import java.sql.*;
/**
* <p>
* Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
* </p>
* <p>
* Klasse DatabaseConnector
* </p>
* <p>
* Ein Objekt der Klasse DatabaseConnector ermoeglicht die Abfrage und Manipulation
* einer SQLite-Datenbank.
* Beim Erzeugen des Objekts wird eine Datenbankverbindung aufgebaut, so dass
* anschließend SQL-Anweisungen an diese Datenbank gerichtet werden koennen.
* </p>
*
* @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
* @version 2016-01-24
*/
public class DatabaseConnector{
private Connection connection;
private QueryResult currentQueryResult = null;
private String message = null;
/**
* Ein Objekt vom Typ DatabaseConnector wird erstellt, und eine Verbindung zur Datenbank
* wird aufgebaut. Mit den Parametern pIP und pPort werden die IP-Adresse und die
* Port-Nummer uebergeben, unter denen die Datenbank mit Namen pDatabase zu erreichen ist.
* Mit den Parametern pUsername und pPassword werden Benutzername und Passwort fuer die
* Datenbank uebergeben.
*/
public DatabaseConnector(String pIP, int pPort, String pDatabase, String pUsername, String pPassword){
//Eine Impementierung dieser Schnittstelle fuer SQLite ignoriert pID und pPort, da die Datenbank immer lokal ist.
//Auch pUsername und pPassword werden nicht verwendet, da SQLite sie nicht unterstuetzt.
try {
//Laden der Treiberklasse
Class.forName("org.sqlite.JDBC");
//Verbindung herstellen
connection = DriverManager.getConnection("jdbc:sqlite:"+pDatabase);
} catch (Exception e) {
message = e.getMessage();
}
}
/**
* Der Auftrag schickt den im Parameter pSQLStatement enthaltenen SQL-Befehl an die
* Datenbank ab.
* Handelt es sich bei pSQLStatement um einen SQL-Befehl, der eine Ergebnismenge
* liefert, so kann dieses Ergebnis anschließend mit der Methode getCurrentQueryResult
* abgerufen werden.
*/
public void executeStatement(String pSQLStatement){
//Altes Ergebnis loeschen
currentQueryResult = null;
message = null;
try {
//Neues Statement erstellen
Statement statement = connection.createStatement();
//SQL Anweisung an die DB schicken.
if (statement.execute(pSQLStatement)) { //Fall 1: Es gibt ein Ergebnis
//Resultset auslesen
ResultSet resultset = statement.getResultSet();
//Spaltenanzahl ermitteln
int columnCount = resultset.getMetaData().getColumnCount();
//Spaltennamen und Spaltentypen in Felder uebertragen
String[] resultColumnNames = new String[columnCount];
String[] resultColumnTypes = new String[columnCount];
for (int i = 0; i < columnCount; i++){
resultColumnNames[i] = resultset.getMetaData().getColumnLabel(i+1);
resultColumnTypes[i] = resultset.getMetaData().getColumnTypeName(i+1);
}
//Queue fuer die Zeilen der Ergebnistabelle erstellen
Queue<String[]> rows = new Queue<String[]>();
//Daten in Queue uebertragen und Zeilen zaehlen
int rowCount = 0;
while (resultset.next()){
String[] resultrow = new String[columnCount];
for (int s = 0; s < columnCount; s++){
resultrow[s] = resultset.getString(s+1);
}
rows.enqueue(resultrow);
rowCount = rowCount + 1;
}
//Ergebnisfeld erstellen und Zeilen aus Queue uebertragen
String[][] resultData = new String[rowCount][columnCount];
int j = 0;
while (!rows.isEmpty()){
resultData[j] = rows.front();
rows.dequeue();
j = j + 1;
}
//Statement schließen und Ergebnisobjekt erstellen
statement.close();
currentQueryResult = new QueryResult(resultData, resultColumnNames, resultColumnTypes);
} else { //Fall 2: Es gibt kein Ergebnis.
//Statement ohne Ergebnisobjekt schliessen
statement.close();
}
} catch (Exception e) {
//Fehlermeldung speichern
message = e.getMessage();
}
}
/**
* Die Anfrage liefert das Ergebnis des letzten mit der Methode executeStatement an
* die Datenbank geschickten SQL-Befehls als Ob-jekt vom Typ QueryResult zurueck.
* Wurde bisher kein SQL-Befehl abgeschickt oder ergab der letzte Aufruf von
* executeStatement keine Ergebnismenge (z.B. bei einem INSERT-Befehl oder einem
* Syntaxfehler), so wird null geliefert.
*/
public QueryResult getCurrentQueryResult(){
return currentQueryResult;
}
/**
* Die Anfrage liefert null oder eine Fehlermeldung, die sich jeweils auf die letzte zuvor ausgefuehrte
* Datenbankoperation bezieht.
*/
public String getErrorMessage(){
return message;
}
/**
* Die Datenbankverbindung wird geschlossen.
*/
public void close(){
try{
connection.close();
} catch (Exception e) {
message = e.getMessage();
}
}
}

View File

@@ -0,0 +1,76 @@
/**
* <p>
* Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
* </p>
* <p>
* Klasse QueryResult
* </p>
* <p>
* Ein Objekt der Klasse QueryResult stellt die Ergebnistabelle einer Datenbankanfrage mit Hilfe
* der Klasse DatabaseConnector dar. Objekte dieser Klasse werden nur von der Klasse DatabaseConnector erstellt.
* Die Klasse verfuegt ueber keinen oeffentlichen Konstruktor.
* </p>
*
* @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
* @version 2015-01-31
*/
public class QueryResult{
private String[][] data;
private String[] columnNames;
private String[] columnTypes;
/**
* Paketinterner Konstruktor.
*/
QueryResult(String[][] pData, String[] pColumnNames, String[] pColumnTypes){
data = pData;
columnNames = pColumnNames;
columnTypes = pColumnTypes;
}
/**
* Die Anfrage liefert die Eintraege der Ergebnistabelle als zweidimensionales Feld
* vom Typ String. Der erste Index des Feldes stellt die Zeile und der zweite die
* Spalte dar (d.h. Object[zeile][spalte]).
*/
public String[][] getData(){
return data;
}
/**
* Die Anfrage liefert die Bezeichner der Spalten der Ergebnistabelle als Feld vom
* Typ String zurueck.
*/
public String[] getColumnNames(){
return columnNames;
}
/**
* Die Anfrage liefert die Typenbezeichnung der Spalten der Ergebnistabelle als Feld
* vom Typ String zurueck. Die Bezeichnungen entsprechen den Angaben in der MySQL-Datenbank.
*/
public String[] getColumnTypes(){
return columnTypes;
}
/**
* Die Anfrage liefert die Anzahl der Zeilen der Ergebnistabelle als Integer.
*/
public int getRowCount(){
if (data != null )
return data.length;
else
return 0;
}
/**
* Die Anfrage liefert die Anzahl der Spalten der Ergebnistabelle als Integer.
*/
public int getColumnCount(){
if (data != null && data.length > 0 && data[0] != null)
return data[0].length;
else
return 0;
}
}

142
Stundenplan/src/Queue.java Normal file
View File

@@ -0,0 +1,142 @@
/**
* <p>
* Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
* </p>
* <p>
* Generische Klasse Queue<ContentType>
* </p>
* <p>
* Objekte der generischen Klasse Queue (Warteschlange) verwalten beliebige
* Objekte vom Typ ContentType nach dem First-In-First-Out-Prinzip, d.h., das
* zuerst abgelegte Objekt wird als erstes wieder entnommen. Alle Methoden haben
* eine konstante Laufzeit, unabhaengig von der Anzahl der verwalteten Objekte.
* </p>
*
* @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
* @version Generisch_02 2014-02-21
*/
public class Queue<ContentType> {
/* --------- Anfang der privaten inneren Klasse -------------- */
private class QueueNode {
private ContentType content = null;
private QueueNode nextNode = null;
/**
* Ein neues Objekt vom Typ QueueNode<ContentType> wird erschaffen.
* Der Inhalt wird per Parameter gesetzt. Der Verweis ist leer.
*
* @param pContent das Inhaltselement des Knotens vom Typ ContentType
*/
public QueueNode(ContentType pContent) {
content = pContent;
nextNode = null;
}
/**
* Der Verweis wird auf das Objekt, das als Parameter uebergeben wird,
* gesetzt.
*
* @param pNext der Nachfolger des Knotens
*/
public void setNext(QueueNode pNext) {
nextNode = pNext;
}
/**
* Liefert das naechste Element des aktuellen Knotens.
*
* @return das Objekt vom Typ QueueNode, auf das der aktuelle Verweis zeigt
*/
public QueueNode getNext() {
return nextNode;
}
/**
* Liefert das Inhaltsobjekt des Knotens vom Typ ContentType.
*
* @return das Inhaltsobjekt des Knotens
*/
public ContentType getContent() {
return content;
}
}
/* ----------- Ende der privaten inneren Klasse -------------- */
private QueueNode head;
private QueueNode tail;
/**
* Eine leere Schlange wird erzeugt.
* Objekte, die in dieser Schlange verwaltet werden, muessen vom Typ
* ContentType sein.
*/
public Queue() {
head = null;
tail = null;
}
/**
* Die Anfrage liefert den Wert true, wenn die Schlange keine Objekte enthaelt,
* sonst liefert sie den Wert false.
*
* @return true, falls die Schlange leer ist, sonst false
*/
public boolean isEmpty() {
return head == null;
}
/**
* Das Objekt pContentType wird an die Schlange angehaengt.
* Falls pContentType gleich null ist, bleibt die Schlange unveraendert.
*
* @param pContent
* das anzuhaengende Objekt vom Typ ContentType
*/
public void enqueue(ContentType pContent) {
if (pContent != null) {
QueueNode newNode = new QueueNode(pContent);
if (this.isEmpty()) {
head = newNode;
tail = newNode;
} else {
tail.setNext(newNode);
tail = newNode;
}
}
}
/**
* Das erste Objekt wird aus der Schlange entfernt.
* Falls die Schlange leer ist, wird sie nicht veraendert.
*/
public void dequeue() {
if (!this.isEmpty()) {
head = head.getNext();
if (this.isEmpty()) {
head = null;
tail = null;
}
}
}
/**
* Die Anfrage liefert das erste Objekt der Schlange.
* Die Schlange bleibt unveraendert.
* Falls die Schlange leer ist, wird null zurueckgegeben.
*
* @return das erste Objekt der Schlange vom Typ ContentType oder null,
* falls die Schlange leer ist
*/
public ContentType front() {
if (this.isEmpty()) {
return null;
} else {
return head.getContent();
}
}
}

View File

@@ -0,0 +1,13 @@
/**
* Listener-INterface, um auf Änderungen der Auswahlboxen im GUI zu reagieren.
*/
public interface SelectionListener {
/**
* Wird aufgerufen, wenn sich die Auswahl ändert.
* @param pList Name der Auswahlbox
* @param pNewValue Neuer Wert
*/
public void selectionChanged(String pList, String pNewValue );
}

View File

@@ -0,0 +1,101 @@
import javax.swing.*;
import java.awt.*;
public class Stundenplan implements SelectionListener {
/**
* Main-Methode um das Programm (außerhalb von BlueJ) zu starten.
*
* @param args
*/
public static void main(String[] args) {
// Setzen des "Look & Feel" des Programms
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
}
// Programm starten
new Stundenplan();
}
// Instanz des GUI des Programms
private StundenplanGUI gui;
// Instanz der Datenbankverbindung
private DatabaseConnector dbc;
/**
* Konstruktor des Hauptprogramms
*/
public Stundenplan() {
// Erstellen des GUIs
gui = new StundenplanGUI();
// Aufbau der Datenbankverbindung
dbc = new DatabaseConnector("", 0, "stundenplan.db", "", "");
QueryResult r;
// Lehrer abfragen und ein Auswahlfeld erstellen, dass in der
// GUI angezeigt wird, um den angezeigten Stundenplan anzupassen
dbc.executeStatement("SELECT kuerzel FROM lehrer");
System.out.println(dbc.getErrorMessage());
r = dbc.getCurrentQueryResult();
String[] teachers = new String[r.getRowCount()];
for (int i = 0; i < r.getRowCount(); i++) {
teachers[i] = r.getData()[i][0];
}
gui.addFilter("Lehrer", teachers);
// GUI anzeigen und Verhalten bei Interaktion festlegen
gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
gui.addSelectionListener(this); // GUI ruft Methode selectionChanged auf, wenn sich eine Auswahlbox ändert.
gui.setLocationRelativeTo(null);
gui.setVisible(true);
}
/**
* Methode um auf Änderungen in der GUi zu reagieren. Sobald der nutzer die
* Auswahl ändert, wird diese Methode aufgerufen. Der erste Parameter ist der
* Name der Auswahlliste, die sich geändert hat (erster Parameter von
* {@link StundenplanGUI#addFilter(String, String[])} oben) und der zweite ist
* der neue Wert der Auswahl.
*
* @param pList Name der Auswahlliste
* @param pNewValue Neuer Wert der Auswahl
*/
@Override
public void selectionChanged(String pList, String pNewValue) {
if (pList == "Lehrer") {
// Zuerst alle bisherigen Stunden aus der GUI entfernen
gui.removeAllLessons();
// Neue Daten aus der Datenbank abfragen.
// Hier gefakte Daten, die Struktur der Datenbank ist nicht vorgegeben.
dbc.executeStatement("SELECT 0,'Q2','405',kuerzel,0,0,'Informatik',-16711936 FROM lehrer WHERE kuerzel = '" + pNewValue + "'");
QueryResult r = dbc.getCurrentQueryResult();
// Falls es ein Ergebnis gibt ...
if (r != null) {
String[][] data = r.getData();
// Neue Stunden in die GUI schreiben
for (int i = 0; i < data.length; i++) {
gui.addLesson(
Integer.parseInt(data[i][4]), // Spalte im Plan (0=Montag, ... ,4=Freitag)
Integer.parseInt(data[i][5]), // Zeile im Plan (0=1. Stunde, ... ,9=10. Stunde)
data[i][6], // Titel
"Raum " + data[i][2], // Untertitel
data[i][1], // Beschreibungstext
data[i][3], // Fußzeile
new Color(Integer.parseInt(data[i][7])) // Farbe (Objekt der Klasse Color)
);
}
// GUI-Fenster neu zeichnen, um neue Elemente anzuzeigen.
gui.revalidate();
gui.repaint();
}
}
}
}

View File

@@ -0,0 +1,265 @@
import javax.swing.*;
import javax.swing.border.LineBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Hashtable;
/**
* GUI für das Stundenplanprogramm.
*/
public class StundenplanGUI extends JFrame implements ActionListener {
// Initiale Größe des Fensters
public static final int FRAME_WIDTH = 800;
public static final int FRAME_HEIGHT = 600;
// Schriftarten
public static Font fHeader = new Font(Font.SANS_SERIF, Font.BOLD, 12);
public static Font fTitle = new Font(Font.SANS_SERIF, Font.BOLD, 11);
public static Font fSubtitle = new Font(Font.SANS_SERIF, Font.PLAIN, 8);
public static Font fText = new Font(Font.SANS_SERIF, Font.PLAIN, 9);
public static Font fFooter = new Font(Font.SANS_SERIF, Font.ITALIC, 8);
// Der Listener, der bei Änderungen informiert wird.
private SelectionListener selectionListener;
// Caches für Stunden und Filter
private Hashtable<String, JPanel> filters;
private Hashtable<Point, JPanel> lessons;
// Panels für die Bereiche des GUIs
private JPanel jpPlan, jpSidebar;
/**
* Erstellt ein neues GUI, zeigt es aber noch nicht an. Das Hauptprogramm
* muss dies explizit mit {@link #setVisible(boolean)} tun.
*/
public StundenplanGUI() {
filters = new Hashtable<>();
lessons = new Hashtable<>();
this.setSize(FRAME_WIDTH, FRAME_HEIGHT);
this.addComponents();
}
/**
* Fügt der Seitenleister eine neue Auswahlbox hinzu. Der Name wird bei
* Änderungen an den {@link SelectionListener} übergeben, um verschiedene
* Boxen unterscheiden zu können.
* @param pName Name des Filters
* @param pOptions Mögliche Optionen
*/
public void addFilter(String pName, String[] pOptions) {
JPanel jpFilter = new JPanel();
jpFilter.setLayout(new BorderLayout());
jpFilter.setBackground(jpSidebar.getBackground());
jpFilter.setPreferredSize(new Dimension(jpSidebar.getPreferredSize().width, 50));
jpFilter.add(makeLabel(pName, fHeader), BorderLayout.NORTH);
JComboBox<String> jcbFilter = new JComboBox<String>(pOptions);
jcbFilter.setName(pName);
jcbFilter.addActionListener(this);
jpFilter.add(jcbFilter, BorderLayout.CENTER);
filters.put(pName, jpFilter);
jpSidebar.add(jpFilter);
}
/**
* Entfernt die Auswahlbox mit dem angegebnen Namen aus der Seitenleiste.
* @param pName
*/
public void removeFilter(String pName) {
if (filters.containsKey(pName)) {
JPanel jpFilter = filters.get(pName);
jpSidebar.remove(jpFilter);
filters.remove(pName);
}
}
/**
* Entfernt alle Auswahlboxen aus der Seitenleiste.
*/
public void removeAllFilters() {
jpSidebar.removeAll();
}
/**
* Fügt eine neue Stunde in den Plan ein. Exisitiert in der angegebenen Zelle
* schon eine Stunde, wird diese entfernt und durch die neue ersetzt.
*
* Eine Stunde in der GUI hat verschiedene Bereiche, in denen Informationen
* (Text) untergebracht werden können, sowie eine Farbe.
*
* Nachdem eine oder mehrere Stunden hinzugefügt wurden, sollte das Fenster mit
* {@link JFrame#revalidate()} und {@link JFrame#repaint()} neu gezeichnet werden.
*
* @param pCol Spaltenindex (0=Montag, ..., 4=Freitag)
* @param pRow Zeilenindex (0=1. Stunde, ..., 9=10. Stunde)
* @param pTitle Überschrift
* @param pSubtitle Untertitel
* @param pText Beschreibung
* @param pFooter Fußzeile
* @param pColor Farbe der Stunde im Plan
*/
public void addLesson(int pCol, int pRow, String pTitle, String pSubtitle, String pText, String pFooter, Color pColor) {
removeLesson(pCol, pRow);
Color bg = new Color(pColor.getRed(), pColor.getGreen(), pColor.getBlue(), 128);
JPanel jpLesson = new JPanel();
jpLesson.setBackground(bg);
jpLesson.setForeground(pColor);
jpLesson.setBorder(new LineBorder(pColor, 2, true));
jpLesson.setLayout(new BoxLayout(jpLesson, BoxLayout.Y_AXIS));
JLabel jlTitle = new JLabel(pTitle);
jlTitle.setFont(fTitle);
jpLesson.add(jlTitle);
JLabel jlSubtitle = new JLabel(pSubtitle);
jlSubtitle.setFont(fSubtitle);
jpLesson.add(jlSubtitle);
JLabel jlText = new JLabel(pText);
jlText.setFont(fText);
jpLesson.add(jlText);
JLabel jlFooter = new JLabel(pFooter);
jlFooter.setFont(fFooter);
jpLesson.add(jlFooter);
GridBagConstraints c = new GridBagConstraints();
c.gridx = pCol + 1;
c.gridy = pRow + 1;
c.weightx = 0.5;
c.weighty = 0.5;
c.insets = new Insets(2, 4, 2, 4);
c.fill = GridBagConstraints.BOTH;
lessons.put(new Point(pCol, pRow), jpLesson);
jpPlan.add(jpLesson, c);
}
/**
* Entfernt die Stunde aus der angebenen Zelle.
* @param pCol Spaltenindex (0=Montag, ..., 4=Freitag)
* @param pRow Zeilenindex (0=1. Stunde, ..., 9=10. Stunde)
*/
public void removeLesson(int pCol, int pRow) {
Point key = new Point(pCol, pRow);
JPanel jpLesson = lessons.get(key);
if (jpLesson != null) {
jpPlan.remove(jpLesson);
lessons.remove(key);
}
}
/**
* Entfernt alle derzeit angezeigten Stunden aus dem GUI.
*/
public void removeAllLessons() {
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 5; j++) {
removeLesson(i, j);
}
}
}
/**
* Setzt den Listener, der über Änderungen der Auswahlboxen informiert wird.
* @param pSelectionListener
*/
public void addSelectionListener(SelectionListener pSelectionListener) {
this.selectionListener = pSelectionListener;
}
/**
* Leitet die Änderungen der Auswahlboxen an den {@link SelectionListener}
* weiter.
* @param pActionEvent
*/
public void actionPerformed(ActionEvent pActionEvent) {
if (this.selectionListener != null) {
JComboBox<String> source = ((JComboBox<String>) pActionEvent.getSource());
String newValue = (String) source.getSelectedItem();
String sourceName = source.getName();
this.selectionListener.selectionChanged(sourceName, newValue);
}
}
/**
* Fügt dem GUI die Standard-Komponenten hinzu.
*/
private void addComponents() {
this.setLayout(new BorderLayout());
// Seitenleiste für Filter (Auswahlboxen)
jpSidebar = new JPanel();
jpSidebar.setBackground(Color.LIGHT_GRAY);
jpSidebar.setPreferredSize(new Dimension(100, FRAME_HEIGHT));
this.add(jpSidebar, BorderLayout.LINE_START);
// Hauptbereich für den Stundenplan
jpPlan = new JPanel();
this.add(jpPlan, BorderLayout.CENTER);
jpPlan.setLayout(new GridBagLayout());
jpPlan.add(new JPanel());
// Überschriften für die Spalten (Wochentage)
GridBagConstraints c = new GridBagConstraints();
c.gridx = 1;
c.gridy = 0;
c.fill = GridBagConstraints.NONE;
c.weightx = 0.5;
c.weighty = 0.2;
jpPlan.add(makeLabel("Montag", fHeader), c);
c.gridx++;
jpPlan.add(makeLabel("Dienstag", fHeader), c);
c.gridx++;
jpPlan.add(makeLabel("Mittwoch", fHeader), c);
c.gridx++;
jpPlan.add(makeLabel("Donnerstag", fHeader), c);
c.gridx++;
jpPlan.add(makeLabel("Freitag", fHeader), c);
// Überschriften für die Zeilen (Stunden)
c = new GridBagConstraints();
c.gridx = 0;
c.fill = GridBagConstraints.NONE;
c.weightx = 0.2;
c.weighty = 0.5;
for (int i = 0; i < 10; i++) {
c.gridy = i + 1;
jpPlan.add(makeLabel((i + 1) + ". Stunde", fHeader), c);
}
}
/**
* Erzeugt ein Text-Label mit der Standard-Schrift und dem angegebenen Text.
* @param pLabel
* @return
*/
private JLabel makeLabel(String pLabel) {
return makeLabel(pLabel, fText);
}
/**
* Erzeugt ein Text-Label mit der angegebenen Schrift und dem Text.
* @param pLabel
* @param pFont
* @return
*/
private JLabel makeLabel(String pLabel, Font pFont) {
JLabel jlLabel = new JLabel(pLabel);
jlLabel.setFont(pFont);
return jlLabel;
}
}

Binary file not shown.