© BIRTHPIX/Shutterstock.com
Gegenüberstellung von JCurses, CHARVA und Lanterna

Terminalbibliotheken im Vergleich


Möchte man eine Konsolenanwendung entwickeln, gibt es unter Java gleich mehrere Bibliotheken. Zu den bekanntesten zählen JCurses, CHARVA und Lanterna. Allen ist gemeinsam, dass die textbasierten grafischen Oberflächen mit Java Swing beziehungsweise Java AWT realisiert werden. Doch welche Gemeinsamkeiten oder Unterschiede weisen diese Bibliotheken sonst noch auf?

Es gibt Anwendungen, bei denen pixelbasierte grafische Oberflächen weder realisierbar noch angemessen sind. Beispielsweise trifft das auf einen Raspberry Pi mit einem 3,2 Zoll großen Bildschirm zu. Oder es wird eine Admin-Schnittstelle für einen Remote-Server oder eine Firewall gebraucht, wobei nicht genug Bandbreite für X-Windows oder das VNC-Protokoll zur Verfügung steht. In solchen Fällen ist das Entwickeln einer textbasierten grafischen Oberfläche, auch TUI genannt, sinnvoll. Alle drei bekannten Terminalbibliotheken, die für die Java-Programmiersprache geeignet sind, können das. Doch sind die Unterschiede beziehungsweise deren Anwendungsgebiete nicht auf Anhieb erkennbar. In diesem Artikel werden deshalb alle drei Terminalbibliotheken vorgestellt und die Vor- und Nachteile hervorgehoben.

JCurses

JCurses [1] ist eine Terminalbibliothek, die vom Code her auf dem grafischen Toolkit Java AWT basiert. Die aktuelle Version ist 0.95b. Sobald man sich den Quellcode herunterlädt und die Quelldateien im Quellverzeichnis jcurses/src/ durchstöbert, fällt allerdings auf, dass 2002 letztmaligVeränderungen am Quellcode vorgenommen wurden. Nichtsdestotrotz lässt sich JCurses mit etwas Aufwand sogar auf dem Raspberry Pi zum Laufen bringen. Fertig kompilierte Versionen existieren sowohl für 32 Bit/64 Bit Linux als auch für Windows.

Wer jedoch die Quelldateien kompilieren möchte, der lädt zunächst den Quellcode herunter und editiert im Ordner jcurses die Makefile. Bei der Installation ist es erforderlich, lediglich die Zeile bei GCCFLAGS anzupassen, indem die Include-Ordner der Java-Entwicklungsumgebung angegeben werden. Außerdem sollte diese Zeile noch um das Flag -fPIC ergänzt werden. Mit dieser Makefile könnt ihr JCurses sogar auf dem Raspi benutzen (Listing 1).

Listing 1

# Generated automatically from Makefile.in by configure. CURSES=-lncurses JAVAHOME=/usr JAVAC=$(JAVAHOME)/bin/javac JAR=$(JAVAHOME)/bin/jar JAVAH=$(JAVAHOME)/bin/javah JAVA=$(JAVAHOME)/bin/java JAVADOC=$(JAVAHOME)/bin/javadoc GCC=gcc GCCFLAGS=-Wall -shared -I$(JAVAHOME)/include -I$(JAVAHOME)/include/zzip -I$(JAVAHOME)/include -I$(JAVAHOME)/include/linux -fPIC CLASSPATH=./classes default: jar native docs java: ;$(JAVAC) -classpath $(CLASSPATH) -d ./classes 'find ./src/jcurses -name *.java' docs: ;$(JAVADOC) -classpath $(CLASSPATH) -sourcepath ./src -d ./doc jcurses.event jcurses.system jcurses.util jcurses.widgets native: java include include: java;$(JAVAH) -classpath $(CLASSPATH) -d ./src/native/include jcurses.system.Toolkit clean: ;rm -rf ./classes/jcurses ./lib/libjcurses.so ./lib/jcurses.jar ./src/native/include/*.h native:java include;$(GCC) $(GCCFLAGS) -o lib/libjcurses.so $(CURSES) src/native/Toolkit.c jar: java;cd classes/ && $(JAR) -cvf ../lib/jcurses.jar * test: ;$(JAVA) -classpath ./lib/jcurses.jar -Djcurses.protocol.filename=jcurses.log jcurses.tests.Test

Die eigentliche Installation sieht wie folgt aus:

$ ./configure $ make all

Die fertig kompilierten Libraries jcurses.jar und libcurses.so landen anschließend im Unterordner jcurses/lib.

Ein Tutorial für JCurses sucht man vergebens. Dabei kommt man um die generierte JavaDoc-Dokumentation nicht herum, die sich unter jcurses/doc/index.html befindet. Zusätzlich existieren Tests, die in jcurses/src/jcurses/tests abgelegt sind. Den Testdateien könnt ihr entnehmen, wie JCurses benutzt wird. Der Einsatz von JCurses ist am Anfang ohne Hilfe erst einmal gewöhnungsbedürftig. In unserem Beispiel wird ein Anmeldefenster ausgegeben, wobei der Code mit Hilfe des MVC-Patterns erstellt worden ist. In Listing 2 wird die Anwendung gestartet, indem die View aufgerufen wird.

Listing 2

public class App { public static void main( String[] args ) { new MainView(); } }

Um ein Fenster auf der Konsole auszugeben, braucht ihr mindestens die Schnittstelle WidgetsConstants. Wichtig ist zudem, die show-Methode der Window-Klasse aufzurufen, damit das Fenster gezeichnet wird (Listing 3).

Listing 3

import jcurses.system.*; import jcurses.widgets.*; import jcurses.util.*; import jcurses.event.*; import java.awt.Rectangle; import java.io.*; public class MainView implements WidgetsConstants { private Window window; private BorderPanel bp; private GridLayoutManager manager1; private GridLayoutManager manager; private Label _l1 = null; private Label _l2 = null; private Button _b1 = null; private TextField _textField = null; private PasswordField _pass = null; private MainVC mvc; public MainView() { this.window = new Window(40,40,true,"Login"); this.mvc = new MainVC(this); Toolkit.beep(); setLayout(); setFields(); this.window.addListener(this.mvc); this.window.show(); } public void setLayout() { this.bp = new BorderPanel(); this.manager1 = new GridLayoutManager(1,1); this.window.getRootPanel().setLayoutManager(this.manager1); this.manager1.addWidget(this.bp,0,0,1,1,ALIGNMENT_CENTER,ALIGNMENT_CENTER); this.manager = new GridLayoutManager(2,5); this.bp.setLayoutManager(this.manager); } public void setFields() { this._l1 = new Label("User Name"); this._l2 = new Label("Password"); this._b1 = new Button("OK"); this._b1.addListener(this.mvc); this._textField = new TextField(); this._pass = new PasswordField(); this.manager.addWidget(this._l1,0,0,1,2,ALIGNMENT_CENTER, ALIGNMENT_CENTER); this.manager.addWidget(this._textField,1,0,1,2,ALIGNMENT_CENTER,ALIGNMENT_CENTER); this.manager.addWidget(this._l2,0,2,1,2,ALIGNMENT_CENTER, ALIGNMENT_CENTER); this.manager.addWidget(this._pass,1,2,1,2,ALIGNMENT_CENTER,ALIGNMENT_CENTER); this.manager.addWidget(this._b1,0,4,1,1,ALIGNMENT_CENTER,ALIGNMENT_CENTER); } public Button getButton() { return this._b1; } public TextField getTxtName() { return this._textField; } public PasswordField getPw() { return this._pass; } public Window getWindow() { return this.window; } }

Wenn sich das Programm erfolgreich kompilieren lässt, sieht das Fenster wie in Abbildung 1 aus. In unserem Test funktionierte die Rückstelltaste nicht, sodass sich Zeichen nicht löschen ließen. Stattdessen wurden die Escape-Zeichen der Rückstelltaste im Eingabefeld eingeblendet.

minosi_terminal_1.tif_fmt1.jpgAbb. 1: Anmeldefenster mit der JCurses-Bibliothek erstellt

JCurses bietet zudem die Klasse Message an, mit der sich Benachrichtigungen ausgeben lassen (Abb. 2).

minosi_terminal_2.tif_fmt1.jpgAbb. 2: Das Benachrichtigungsfenster informiert den User über den erfolgreichen Log-in

Listing 4 könnt ihr entnehmen, wie sich mit der setMessage-Methode Benachrichtigungen erzeugen lassen. Außerdem zeigt Listing 4 den Controller der zugehörigen View. Er implementiert in unserem Fall neben dem WindowListener auch den ActionListener. Der WindowListener sorgt dafür, dass das Fenster geschlossen wird, sobald die Escape-Taste gedrückt wird. Da die Listener bei JCurses Java-Schnittstellen sind, kann der Controller gleich mehrere Listener implementieren. Das Handling des OK-Buttons wird wie in Listing 4 durch die actionPerformed-Methode vorgenommen. Sie prüft zunächst, welcher Button gedrückt wurde und ruft beim OK-Button die checkInput-Methode auf, die die Benutzerdaten validiert.

Wichtig ist, dass ihr eine neue Instanz des Controllers in der View erstellt. Dann verknüpft ihr die addListener-Methoden mit dem Controller, um die einzelnen Listener beim jeweiligen GUI-Element zu registrieren.

Listing 4

import jcurses.system.*; import jcurses.widgets.*; import jcurses.util.*; import jcurses.event.*; import java.awt.Rectangle; import java.io.*; public class MainVC implements WindowListener,ActionListener { private MainView view; private final static String PASSWORD = "mypw"; private final static String USERNAME = "tino"; public MainVC (MainView view) { this.view = view; } public void windowChanged(WindowEvent event) { if (event.getType() == WindowEvent.CLOSING) { event.getSourceWindow().close(); } } public void actionPerformed(ActionEvent event) { Widget w = event.getSource(); if (w == this.view.getButton()) { String pw = this.view.getPw().getText(); String u = this.view.getTxtName().getText(); checkInput(pw,u); } else { setMessage("Please press a button in this window","ERROR","Cancel"); } } private boolean isCorrect(String input,String comp) { if (input.equals(comp)) { return true; } else { return false; } } private void checkInput(String pw,String ...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Teams

Für Firmen haben wir individuelle Teamlizenzen. Wir erstellen Ihnen gerne ein passendes Angebot.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang