Become a member!

TUI-Anwendungen mit Delphi und DMVCFramework entwickeln

🌐
Dieser Artikel ist auch in anderen Sprachen verfügbar:
🇮🇹 Italiano  •  🇬🇧 English  •  🇪🇸 Español

TUI: kein Schritt zurück

Wer die jüngste Entwicklung von DMVCFramework verfolgt hat, weiß bereits, dass Konsolenanwendungen eine zweite Blütezeit erleben. Im vorherigen Beitrag über Konsolenfarben habe ich die Low-Level-ANSI-API vorgestellt — die Records Fore, Back und Style — sowie den Gin-artigen HTTP-Log-Renderer. Das war die Grundlage.

Dieser Beitrag befasst sich mit dem, was darauf aufgebaut wurde: die vollständige TUI-Bibliothek in MVCFramework.Console.pas.

TUI steht für Text User Interface. Es ist die Welt von htop, lazygit, k9s und jedem CLI-Tool, das Pfeiltasten, farbige Boxen und Fortschrittsbalken verwendet. Diese Anwendungen sind keine „einfachen Konsolenprogramme" — es sind reichhaltige, interaktive Oberflächen, die zufällig in einem Terminal laufen.

Mit DMVCFramework 3.5.0 Silicon verfügen Delphi-Entwickler nun über ein vollständiges, plattformübergreifendes TUI-Toolkit:

  • Farbige Textausgabe mit Ausrichtung
  • Interaktive Menüs, die mit Pfeiltasten navigiert werden
  • Animierte Spinner mit 10 Stilen
  • Bestimmte und unbestimmte Fortschrittsbalken
  • Tabellen mit statischer Anzeige und interaktiver Zeilenauswahl
  • Umrandete Boxen mit optionalen Titeln
  • Cursor-Steuerung: ausblenden, anzeigen, speichern, wiederherstellen, bewegen
  • Tastatureingabe-Verarbeitung einschließlich Sondertasten
  • Ein globales Theme-System

Alles in einer einzigen Unit — MVCFramework.Console — mit null Abhängigkeiten vom Rest des Frameworks. Sie können sie in jedes Delphi-Konsolenprojekt kopieren und sofort verwenden.

Einrichtung

Fügen Sie MVCFramework.Console zu Ihrer uses-Klausel hinzu. Für vollständige Unicode-Unterstützung (Spinner-Frames, Box-Zeichenzeichen, Emojis) rufen Sie EnableUTF8Console am Programmanfang auf:

program MyTUIApp;
{$APPTYPE CONSOLE}
uses
  System.SysUtils,
  MVCFramework.Console;

begin
  EnableUTF8Console;  // Erforderlich für Box-Zeichen, Spinner mit Emojis usw.
  // ... Ihr Code hier
end.

Unter Windows ruft EnableUTF8Console SetConsoleOutputCP(CP_UTF8) auf. Unter Linux ist es ein No-Op, da UTF-8 der Standard ist. Wenn Sie auch planen, die ANSI-Farbprimitiven (Fore, Back, Style) direkt mit WriteLn zu verwenden, rufen Sie auch EnableANSIColorConsole auf. Alle High-Level-Funktionen dieser Bibliothek rufen es jedoch intern auf, sodass Sie sich in den meisten Fällen keine Gedanken darüber machen müssen.

ANSI-Farben: der Colorama-Ansatz (Fore / Back / Style)

MVCFramework.Console enthält zwei komplementäre Farbsysteme. Das erste — im vorherigen Beitrag behandelt — ist eine Low-Level-ANSI-API, die nach dem Python-colorama-Modell gestaltet ist. Es lohnt sich, es hier nochmals aufzugreifen, da es ein wesentliches Werkzeug für jede TUI-Anwendung ist und sich natürlich mit allem anderen in dieser Unit verbindet.

Die API besteht aus drei Records mit String-Konstanten:

Record Zweck Beispiel
Fore Vordergrund-(Text-)Farbe Fore.Red, Fore.Green, Fore.Cyan
Back Hintergrundfarbe Back.DarkBlue, Back.DarkRed
Style Textstil und Zurücksetzen Style.Bright, Style.Dim, Style.ResetAll

Jede Konstante ist ein roher ANSI-Escape-Sequenz-String. Farben werden mit dem +-Operator kombiniert, und Style.ResetAll beendet ein farbiges Segment. Rufen Sie EnableANSIColorConsole einmal beim Start auf (idempotent, No-Op unter Linux):

EnableANSIColorConsole;

// Grundlegende Vordergrundfarben
WriteLn(Fore.Red     + 'Fehler: Verbindung abgelehnt'        + Style.ResetAll);
WriteLn(Fore.Green   + 'OK: Server gestartet auf :8080'      + Style.ResetAll);
WriteLn(Fore.Yellow  + 'Warnung: Cache-Miss-Rate 67%'        + Style.ResetAll);
WriteLn(Fore.Cyan    + 'Info: Verwende config.env'           + Style.ResetAll);
WriteLn(Fore.DarkGray + '# Debug-Ausgabe'                   + Style.ResetAll);
Vordergrundfarben mit Fore.*-Escape-Sequenzen

Vordergrund, Hintergrund und Stil kombinieren

Die eigentliche Stärke liegt in der Kombination. Da Konstanten einfache Strings sind, ist jede Kombination eine einzelne Verkettung:

// Vordergrund + Hintergrund
WriteLn(Fore.White + Back.DarkBlue  + '  INFO  ' + Style.ResetAll + ' Server gestartet');
WriteLn(Fore.White + Back.DarkGreen + '  PASS  ' + Style.ResetAll + ' TestUserAuth');
WriteLn(Fore.White + Back.DarkRed   + '  FAIL  ' + Style.ResetAll + ' TestPaymentTimeout');
WriteLn(Fore.White + Back.DarkYellow + '  WARN  ' + Style.ResetAll + ' Hohe Speicherauslastung');

// Stil-Modifikatoren
WriteLn(Style.Bright + Fore.White + 'Fette weiße Überschrift' + Style.ResetAll);
WriteLn(Style.Dim  + Fore.Gray  + 'Gedämpfter sekundärer Text' + Style.ResetAll);

// Gemischtes Inline
WriteLn(
  'Status: ' + Fore.Green + 'ONLINE'  + Style.ResetAll +
  '  |  Req/s: ' + Fore.Cyan + '142' + Style.ResetAll +
  '  |  Fehler: ' + Fore.Red + '0'   + Style.ResetAll
);
Status-Badges und gemischte Inline-Farben mit Fore/Back/Style

Wiederverwendbare Stil-Konstanten (wie CSS für die Konsole)

Anstatt Fore.White + Back.DarkGreen überall zu wiederholen, definieren Sie benannte Konstanten einmalig. Der Compiler löst sie zur Compile-Zeit auf — kein Runtime-Overhead:

const
  BADGE_OK   = Fore.White + Back.DarkGreen;
  BADGE_FAIL = Fore.White + Back.DarkRed;
  BADGE_WARN = Fore.White + Back.DarkYellow;
  BADGE_INFO = Fore.White + Back.DarkBlue;
  MUTED      = Fore.DarkGray;
  RESET      = Style.ResetAll;

// Sauberer, lesbarer Code:
WriteLn(BADGE_OK   + ' PASS ' + RESET + '  TestUserAuth');
WriteLn(BADGE_FAIL + ' FAIL ' + RESET + '  TestPaymentTimeout');
WriteLn(BADGE_WARN + ' SLOW ' + RESET + '  Abfrage dauerte 3.2s');
WriteLn(BADGE_INFO + ' NOTE ' + RESET + '  Verwende Fallback-Konfiguration');
WriteLn(MUTED      + '--- Ende des Berichts ---' + RESET);
Wiederverwendbare Badge-Konstanten auf Testausgaben und Log-Zeilen angewendet

Wann Fore/Back/Style vs. TConsoleColor verwenden

Die beiden Systeme sind komplementär, nicht konkurrierend:

Verwende Fore / Back / Style wenn… Verwende TConsoleColor / WriteLine wenn…
Inline-Färbung innerhalb eines WriteLn benötigt wird Vollzeilige farbige Ausgabe gewünscht ist
Badges oder Log-Präfixe erstellt werden Box, Table, Menu (Theme-bewusst) verwendet wird
Compile-Zeit-Stil-Konstanten gewünscht sind Das globale ConsoleTheme angewendet werden soll
Style.Bright oder Style.Dim benötigt wird Hintergrundfarbe aus dem Theme benötigt wird

Beide Systeme koexistieren. Eine typische TUI-Anwendung verwendet Fore/Back/Style für benutzerdefinierten Inline-Text und verlässt sich auf TConsoleColor-basierte Funktionen für strukturelle Widgets (Boxen, Tabellen, Menüs).

Textausgabe

Die Farbpalette

Die Bibliothek definiert TConsoleColor, eine Enumeration mit 17 Werten, die die klassischen 16 Terminal-Farben plus UseDefault (erbt vom aktiven Theme) abdeckt:

Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta,
DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta,
Yellow, White, UseDefault

Diese entsprechen unter Windows der klassischen Windows Console API und unter Linux den ANSI-Codes. Die Namen sind auf beiden Plattformen identisch: Green ist immer grün, Red ist immer rot.

WriteLine und WriteColoredText

Der einfachste Weg zur farbigen Textausgabe:

// Einfacher Text (verwendet Terminal-Standards)
WriteLine('Serverinitialisierung wird gestartet...');

// Text mit Vordergrundfarbe
WriteLine('Server erfolgreich gestartet', Green);

// Text mit Vordergrund- und Hintergrundfarbe
WriteLine(' KRITISCH ', White, DarkRed);

// Farbiges Inline-Segment (ohne Zeilenumbruch)
WriteColoredText('[INFO] ', Cyan);
WriteLine('Lausche auf Port 8080', White);

WriteColoredText speichert und stellt die aktuellen Farben automatisch wieder her, sodass es sicher verschachtelt oder sequentiell aufgerufen werden kann, ohne sich um Farbübertragung sorgen zu müssen.

Terminal, das alle 16 Farben über WriteLine zeigt

Ausgerichteter Text

WriteAlignedText füllt Text auf eine feste Breite mit konfigurierbarer Ausrichtung auf:

// Titel in einem 80 Zeichen breiten Bereich zentrieren
WriteAlignedText('DMVCFramework Server', 80, taCenter, Yellow);

// Wert rechtsbündig ausrichten
WriteAlignedText('v3.5.0', 80, taRight, DarkGray);

// Linksbündig mit Farbe
WriteAlignedText('Status: ONLINE', 80, taLeft, Green);

Die drei Ausrichtungsmodi — taLeft, taCenter, taRight — decken alle Layout-Anforderungen ab. Der Width-Parameter steuert die Ausgabespaltenbreite, nicht die String-Breite: kürzere Strings werden aufgefüllt, längere werden abgeschnitten.

CenterInScreen ist eine praktische Abkürzung, die einen String im physischen Konsolenfenster zentriert:

CenterInScreen('Drücken Sie eine Taste zum Fortfahren');

Es liest die aktuelle Konsolengröße mit GetConsoleSize und berechnet automatisch die korrekten GotoXY-Koordinaten.

Statusmeldungen

Vier vordefinierte Statusfunktionen erzeugen konsistent gestaltete Ausgaben:

WriteSuccess('Datenbankmigration in 1.2s abgeschlossen');
WriteWarning('Cache-Miss-Rate über 50% — TTL-Optimierung empfohlen');
WriteError('Verbindung auf Port 5432 abgelehnt');
WriteInfo('Verwende Fallback-Konfiguration aus Umgebungsvariablen');

Jede erzeugt ein farbiges Präfix-Badge gefolgt von weißem Meldungstext:

Funktion Badge Badge-Farbe
WriteSuccess [SUCCESS] Grün
WriteWarning [WARNING] Gelb
WriteError [ERROR] Rot
WriteInfo [INFO] Cyan
Alle vier Statusfunktionen: WriteSuccess, WriteWarning, WriteError, WriteInfo

Überschriften und Trennlinien

WriteHeader erzeugt einen zentrierten Titel, der von horizontalen Linien gerahmt wird, unter Verwendung des aktiven Box-Zeichensatzes:

WriteHeader('Datenbank-Migrationstool');
WriteHeader('Konfigurationsübersicht', 60);
WriteHeader('Ergebnisse', 60, Green);  // Benutzerdefinierte Farbe

Die Standardbreite beträgt 80 Spalten. Das horizontale Linienzeichen passt sich an ConsoleTheme.BoxStyle an (siehe Abschnitt Themes weiter unten).

WriteSeparator zeichnet eine einfache horizontale Linie — nützlich zum visuellen Trennen von Ausgabeabschnitten:

WriteSeparator;          // 60 Bindestriche
WriteSeparator(80);      // 80 Bindestriche
WriteSeparator(40, '='); // 40 Gleichheitszeichen
WriteHeader über einem Inhaltsblock, abgeschlossen durch WriteSeparator

Formatierte Listen

WriteFormattedList rendert eine Titelliste mit konfigurierbarem Aufzählungsstil:

var Features: TStringArray;
SetLength(Features, 4);
Features[0] := 'Interaktive Menüs mit Pfeiltasten-Navigation';
Features[1] := 'Nicht-blockierende Spinner auf Hintergrund-Thread';
Features[2] := 'Tabellen mit statischer und interaktiver Anzeige';
Features[3] := 'Globales Theme-System';

WriteFormattedList('Neu in DMVCFramework 3.5.0:', Features, lsBullet);
WriteFormattedList('Schritte zum Upgrade:', Features, lsNumbered);
WriteFormattedList('Entfernte Funktionen:', Features, lsDash);
WriteFormattedList('Nächste Schritte:', Features, lsArrow);

Die vier Listenstile:

Stil Präfix
lsBullet *
lsNumbered 1.
lsDash -
lsArrow >
Alle vier Listenstile: Aufzählung, nummeriert, Bindestrich, Pfeil

Boxen

Die Box-Funktion rendert einen umrandeten Inhaltsbereich, der standardmäßig automatisch auf die Inhaltsbreite skaliert:

// Einfache Box ohne Titel, Standardbreite (60)
Box(['Server: ONLINE', 'Datenbank: VERBUNDEN', 'Speicher: 65%']);

// Box mit Titel
Box('Systemstatus', ['Server: ONLINE', 'Datenbank: VERBUNDEN']);

// Box mit Titel und benutzerdefinierter Breite
Box('WARNUNG', ['Cache-Server antwortet nicht', 'Netzwerkverbindung prüfen'], 50);

Der Box-Inhalt wird automatisch links innerhalb des Rahmens aufgefüllt. Die Rahmenzeichen passen sich an ConsoleTheme.BoxStyle an.

Für detaillierte Zeichensteuerung verwenden Sie direkt das Low-Level-Primitiv DrawBox:

// Eine 40x10-Box ab Spalte 5, Zeile 3 zeichnen
DrawBox(5, 3, 40, 10, bsDouble, 'Debug-Panel');

Box-Stile

Vier Rahmenstile sind über TBoxStyle verfügbar:

DrawBox(0, 0, 30, 5, bsSingle,  'Einfach');      // ┌─┐ │ └─┘
DrawBox(0, 6, 30, 5, bsDouble,  'Doppelt');       // ╔═╗ ║ ╚═╝
DrawBox(0, 12, 30, 5, bsRounded, 'Abgerundet');  // ╭─╮ │ ╰─╯
DrawBox(0, 18, 30, 5, bsThick,   'Dick');         // ┏━┓ ┃ ┗━┛
Die vier Box-Stile: bsSingle, bsDouble, bsRounded, bsThick

Der Standard-Box-Stil für alle High-Level-Funktionen (Box, Menu, Table) wird durch ConsoleTheme.BoxStyle gesteuert. Eine einmalige Änderung wirkt sich auf alle Widgets aus:

ConsoleTheme.BoxStyle := bsDouble;
Box('Jetzt verwendet alles doppelte Rahmen', ['Element 1', 'Element 2']);

Tabellen

Statische Tabelle

Table rendert eine formatierte Tabelle mit automatisch angepassten Spalten:

var
  Headers: TStringArray;
  Data: TStringMatrix;

SetLength(Headers, 4);
Headers[0] := 'ID'; Headers[1] := 'Name';
Headers[2] := 'Framework'; Headers[3] := 'Sprache';

SetLength(Data, 3);

SetLength(Data[0], 4);
Data[0] := ['001', 'Mario Rossi', 'DMVCFramework', 'Delphi'];

SetLength(Data[1], 4);
Data[1] := ['002', 'Luigi Verdi', 'Spring Boot', 'Java'];

SetLength(Data[2], 4);
Data[2] := ['003', 'Anna Bianchi', 'ASP.NET Core', 'C#'];

// Ohne Titel
Table(Headers, Data);

// Mit Titel
Table(Headers, Data, 'Entwicklungsteam');

Spaltenbreiten werden automatisch berechnet: Jede Spalte ist so breit wie ihre breiteste Zelle (Kopfzeile oder Daten), plus 2 Zeichen innerer Auffüllung. Die Kopfzeile wird mit ConsoleTheme.TextHighlightColor zur visuellen Unterscheidung gerendert.

Statische Tabelle mit vier Spalten und Datenzeilen, gerendert von Table()

Interaktive Tabelle

TableMenu verwandelt eine Tabelle in einen interaktiven Selektor, der mit Pfeiltasten navigiert wird:

var SelectedRow: Integer;

SelectedRow := TableMenu('Entwickler auswählen', Headers, Data);

if SelectedRow >= 0 then
  WriteSuccess('Sie haben ausgewählt: ' + Data[SelectedRow][1])
else
  WriteWarning('Auswahl abgebrochen');

Die ausgewählte Zeile wird mit ConsoleTheme.BackgroundHighlightColor und ConsoleTheme.TextHighlightColor hervorgehoben. Navigationstasten:

Taste Aktion
↑ / ↓ Auswahl nach oben/unten bewegen
Enter Auswahl bestätigen, Zeilenindex zurückgeben
Escape Abbrechen, -1 zurückgeben

Der Cursor wird während der Interaktion automatisch ausgeblendet und beim Beenden wiederhergestellt.

Interaktive Tabelle mit Pfeiltasten-Navigation und Zeilenhervorhebung

Sie können eine Zeile vorauswählen:

SelectedRow := TableMenu('Auswählen', Headers, Data, 2); // Beginnt mit Zeile 2 ausgewählt

Interaktive Menüs

Menu zeigt eine tastaturnavigierbare Liste in einer Box an. Es erscheint an der aktuellen Cursorposition und bereinigt sich, wenn der Benutzer eine Auswahl trifft oder Escape drückt.

var
  Items: TStringArray;
  Selected: Integer;

SetLength(Items, 5);
Items[0] := 'Server starten';
Items[1] := 'Server stoppen';
Items[2] := 'Protokolle anzeigen';
Items[3] := 'Einstellungen';
Items[4] := 'Beenden';

// Ohne Titel
Selected := Menu(Items);

// Mit Titel
Selected := Menu('Hauptmenü', Items);

// Mit Titel und vorausgewähltem Element
Selected := Menu('Dateimenü', Items, 2); // Beginnt mit 'Protokolle anzeigen' ausgewählt

Rückgabewert: Der Index des ausgewählten Elements (0-basiert) oder -1, wenn der Benutzer Escape gedrückt hat.

Navigation:

Taste Aktion
Nach oben (mit Wrap)
Nach unten (mit Wrap)
Enter Bestätigen
Escape Abbrechen

Das ausgewählte Element wird mit invertierten Farben gerendert (Hintergrundhervorhebung). Das Menü passt seine Breite automatisch an das längste Element plus Auffüllung an. Wenn das Menü das untere Ende des Terminals überschreiten würde, wird es automatisch nach oben verschoben.

Interaktives Menü mit Pfeiltasten navigiert, Auswahl mit Enter bestätigt
// Praktisches Beispiel: Haupt-Dispatch-Schleife
while True do
begin
  Selected := Menu('Serversteuerung', ['Starten', 'Stoppen', 'Neu starten', 'Beenden']);
  case Selected of
    0: StartServer;
    1: StopServer;
    2: begin StopServer; StartServer; end;
    3, -1: Break;
  end;
end;

Fortschrittsbalken

Bestimmter Fortschritt

Progress mit MaxValue > 0 erzeugt einen bestimmten Fortschrittsbalken:

var
  P: IProgress;
  I: Integer;

P := Progress('Dateien herunterladen', 100);
for I := 1 to 100 do
begin
  P.Update(I);
  Sleep(20); // Arbeit simulieren
end;
P := nil; // Ruft Complete automatisch über den Destruktor auf

Der Balken wird als [==== ] 45% dargestellt. Der gefüllte Anteil wird als (Aktuell * Breite) div MaxValue berechnet. Die Standardbalkenbreite beträgt 50 Zeichen.

Das IProgress-Interface stellt bereit:

Methode Beschreibung
Update(Value) Aktuellen Wert setzen (absolut)
Increment(Amount) Um Amount erhöhen (Standard 1)
SetMessage(Msg) Titelbeschriftung aktualisieren
Complete Als erledigt markieren, “Fertig!” in grün ausgeben

P := nil zu setzen aktiviert den Destruktor, der Complete aufruft, wenn es noch nicht geschehen ist. Das Einwickeln des Fortschritts in einen try/finally-Block ist daher optional — die Bereinigung ist garantiert.

// Inkrementierungsstil
P := Progress('Datensätze verarbeiten', 1000);
for I := 1 to 1000 do
begin
  ProcessRecord(I);
  P.Increment;
end;
Bestimmter Fortschrittsbalken, der sich von 0% auf 100% füllt

Unbestimmter Fortschritt

Progress mit MaxValue = 0 erzeugt einen unbestimmten Spinner innerhalb eines Klammernpaars:

P := Progress('Konfiguration laden');
// Arbeit erledigen...
while StillLoading do
begin
  P.Update(0); // Spinner vorwärtsbewegen
  Sleep(50);
end;
P.Complete;

Der Spinner innerhalb der [|]-Klammern durchläuft die Zeichen |/-\. Rufen Sie Update regelmäßig auf, um die Animation vorwärtszubewegen. Dies ist ein blockierendes Muster — der Spinner bewegt sich nur vorwärts, wenn Sie Update aufrufen. Für einen vollständig nicht-blockierenden Spinner verwenden Sie stattdessen Spinner (siehe unten).

Spinner

Die Spinner-Funktion erstellt einen hintergrundthread-basierten, nicht-blockierenden animierten Spinner. Ihr Hauptthread arbeitet weiter, während der Spinner unabhängig animiert:

var S: ISpinner;

S := Spinner('Daten laden', ssLine, DarkGray);
// Hier Ihre Arbeit erledigen — Spinner animiert von selbst
FetchRemoteData;
S.Hide; // Oder: S := nil (ruft Destruktor auf → Hide)

Das Freigeben des Interface (auf nil setzen) ruft auch Hide auf, was den Thread stoppt und den Spinner vom Terminal löscht.

Die 10 Spinner-Stile

TSpinnerStyle bietet 10 Animationsstile:

Stil Zeichen Intervall Beschreibung
ssLine -\│/ 100ms Klassischer Terminal-Spinner
ssDots Braille-Muster 80ms Flüssige Punkt-Animation
ssBounce Braille-Bounce 80ms Springender Punkt
ssGrow Block-Elemente 120ms Wachsender Balken
ssArrow Pfeilzeichen 100ms Rotierender Pfeil
ssCircle Kreisviertel 100ms Rotierender Kreis
ssClock 🕐🕑🕒… 200ms 12 Uhrzeiger
ssEarth 🌍🌎🌏 200ms Globus-Rotation
ssMoon 🌑🌒🌓… 200ms 8 Mondphasen
ssWeather 🌤🌧⛈… 200ms Wetter-Icons

Jeder Stil verwendet automatisch das passende Animationsintervall. Sleep-Werte müssen nicht angepasst werden.

// Linien-Spinner (Standard)
S := Spinner(ssLine);

// Dots mit Meldung
S := Spinner('Mit Datenbank verbinden', ssDots, Cyan);

// Erd-Rotation, ohne Meldung
S := Spinner(ssEarth);

// Mondphasen mit benutzerdefinierter Farbe
S := Spinner('Synchronisieren', ssMoon, Blue);
Alle 10 Spinner-Stile gleichzeitig animiert

Spinner in einer Arbeitsschleife

Ein häufiges Muster: Spinner während einer asynchronen Operation anzeigen, dann durch eine Statusmeldung ersetzen.

var S: ISpinner;
begin
  S := Spinner('Mit Server verbinden', ssDots, Cyan);
  try
    ConnectToServer; // Blockiert, aber Spinner läuft auf separatem Thread
    S.Hide;
    WriteSuccess('Erfolgreich verbunden');
  except
    S.Hide;
    WriteError('Verbindung fehlgeschlagen: ' + E.Message);
  end;
end;

Confirm und Choose

Confirm

Confirm fragt den Benutzer nach einer Ja/Nein-Antwort mit optionalem Standard:

// Standard: Ja
if Confirm('Möchten Sie fortfahren?') then
  StartOperation
else
  Writeln('Abgebrochen.');

// Standard: Nein (sicherer für destruktive Aktionen)
if Confirm('Alle Datensätze löschen?', False) then
  DeleteAllRecords;

Der Prompt zeigt [J/N] (J): oder [J/N] (N): je nach Standard. Enter ohne Eingabe akzeptiert den Standard.

Choose

Choose präsentiert eine nummerierte Liste und liest eine numerische Eingabe. Nützlich, wenn die Optionsanzahl klein ist und eine einfachere UX als ein vollständiges Pfeiltasten-Menü gewünscht wird:

var
  Options: TStringArray;
  Choice: Integer;

SetLength(Options, 3);
Options[0] := 'Schnellmodus';
Options[1] := 'Normalmodus';
Options[2] := 'Sicherheitsmodus';

Choice := Choose('Verarbeitungsmodus wählen:', Options);
if Choice >= 0 then
  WriteSuccess('Sie haben gewählt: ' + Options[Choice]);

Ausgabe:

Verarbeitungsmodus wählen:
  [1] Schnellmodus
  [2] Normalmodus
  [3] Sicherheitsmodus
Ihre Wahl: _

Gibt den 0-basierten Index der ausgewählten Option zurück, oder -1 bei ungültiger Eingabe.

Cursor-Steuerung

Ausblenden und Anzeigen

Beim Zeichnen interaktiver UI-Elemente (Menüs, Fortschrittsbalken, Spinner) beseitigt das Ausblenden des Cursors das visuelle Rauschen des blinkenden Carets, das über den Bildschirm springt:

HideCursor;
try
  DrawDashboard;
  WaitForKey;
finally
  ShowCursor; // Immer in einem finally-Block wiederherstellen
end;
Ein sauber gerendertes Menü mit ausgeblendetem Cursor

Die High-Level-Funktionen (Menu, TableMenu, Spinner) verwalten die Cursor-Sichtbarkeit intern. HideCursor muss nur manuell aufgerufen werden, wenn Low-Level-Zeichenprimitiven direkt verwendet werden.

GotoXY

GotoXY bewegt den Cursor zu einer absoluten Spalten-/Zeilenposition (0-basiert):

// Zur Spalte 10, Zeile 5 bewegen
GotoXY(10, 5);
Write('Wert: ');
GotoXY(18, 5);
Write(CurrentValue:6:2);

Dies ist die Grundlage aller ortsständigen Bildschirmaktualisierungen. Kombiniert mit GetCursorPosition kann eine Position gespeichert und später zurückgekehrt werden:

var Pos: TMVCConsolePoint;

Pos := GetCursorPosition;
Write('Verarbeite...');
DoSomeWork;
GotoXY(Pos.X, Pos.Y);
Write('Fertig!         '); // Vorherigen Text überschreiben

Cursor-Position speichern und wiederherstellen

SaveCursorPosition und RestoreCursorPosition speichern/stellen Koordinaten in einer globalen Variable wieder her:

SaveCursorPosition;
// ... etwas anderswo zeichnen
RestoreCursorPosition;
Write('Zurück hier!');

Hinweis: Diese verwenden einen einzigen globalen Speicherslot und sind daher nicht re-entrant. Für komplexe Layouts mit mehreren gespeicherten Positionen verwenden Sie direkt GetCursorPosition / GotoXY.

Zeichenprimitiven

Für Layouts, die mehr als die High-Level-Widgets erfordern, können die rohen Zeichenfunktionen verwendet werden:

DrawBox

Zeichnet eine Box an absoluten Koordinaten ohne die Auffüllung und Farbverwaltung der High-Level-Box:

DrawBox(5, 2, 40, 10, bsSingle, 'Panel-Titel');
DrawBox(50, 2, 30, 10, bsDouble);

Parameter: X, Y (obere linke Ecke), Width, Height, Style (optional), Title (optional, im oberen Rahmen zentriert).

DrawHorizontalLine und DrawVerticalLine

// Einen 60-Zeichen-Trenner in Zeile 12 zeichnen
DrawHorizontalLine(0, 12, 60, bsSingle);

// Einen vertikalen Teiler in Spalte 40 ab Zeile 2, 20 Zeichen hoch zeichnen
DrawVerticalLine(40, 2, 20, bsSingle);

Verwenden Sie bsUseDefault, um den Stil von ConsoleTheme.BoxStyle zu erben.

ClearRegion

Löscht einen rechteckigen Bereich durch Überschreiben mit Leerzeichen:

// Einen 40x10-Bereich ab (5, 3) löschen
ClearRegion(5, 3, 40, 10);

Nützlich zum Aktualisieren von Bildschirmteilen ohne ClrScr aufzurufen (was alles löscht und den Cursor auf 0, 0 setzt).

Multi-Panel-Layout mit DrawBox, DrawHorizontalLine und DrawVerticalLine erstellt

Themes

Alle High-Level-Widgets lesen ihre Farben und den Box-Stil aus dem globalen ConsoleTheme-Record:

type
  TConsoleColorStyle = record
    TextColor: TConsoleColor;           // Textkörper in Boxen und Tabellen
    BackgroundColor: TConsoleColor;     // Hintergrund (unter Linux verwendet)
    DrawColor: TConsoleColor;           // Box-Rahmen und Trennlinien
    SymbolsColor: TConsoleColor;        // Listen-Aufzählungszeichen, Präfixe
    BackgroundHighlightColor: TConsoleColor; // Hintergrund des ausgewählten Elements
    TextHighlightColor: TConsoleColor;  // Text des ausgewählten Elements, Kopfzeilentext
    BoxStyle: TBoxStyle;                // Standard-Rahmenstil für alle Widgets
  end;

Das Standard-Theme:

ConsoleTheme.TextColor               := Cyan;
ConsoleTheme.BackgroundColor         := Black;
ConsoleTheme.DrawColor               := White;
ConsoleTheme.SymbolsColor            := Gray;
ConsoleTheme.BackgroundHighlightColor := Cyan;
ConsoleTheme.TextHighlightColor      := Blue;
ConsoleTheme.BoxStyle                := bsRounded;

Eine Theme-Änderung wirkt sich auf alle nachfolgenden Aufrufe von Box, Table, Menu, WriteHeader, WriteFormattedList und ähnlichen Funktionen aus:

// Dunkles Terminal-Stil
ConsoleTheme.TextColor               := White;
ConsoleTheme.DrawColor               := DarkGray;
ConsoleTheme.BackgroundHighlightColor := DarkBlue;
ConsoleTheme.TextHighlightColor      := White;
ConsoleTheme.BoxStyle                := bsDouble;

// Ab hier verwenden alle Widgets das neue Theme
Box('Systeminfo', ['CPU: 12%', 'RAM: 4.2GB']);
Dieselben Widgets mit dem Standard-Theme und einem benutzerdefinierten dunklen Theme gerendert

Themes sind eine globale Variable. Wenn ein temporärer Theme-Wechsel benötigt wird, speichern und wiederherstellen:

var SavedTheme: TConsoleColorStyle;

SavedTheme := ConsoleTheme;
ConsoleTheme.BoxStyle := bsThick;
ConsoleTheme.DrawColor := Red;
Box('FEHLER', ['Kritischer Fehler erkannt']);
ConsoleTheme := SavedTheme; // Wiederherstellen

Tastatureingabe

GetKey und GetCh

GetKey blockiert, bis der Benutzer eine Taste drückt, und gibt einen Integer-Code zurück:

  • Druckbare Zeichen: Ord('A') = 65, Ord(' ') = 32 usw.
  • Sondertasten: Werte über 255, als benannte Konstanten definiert
var Key: Integer;

Key := GetKey;

if IsSpecialKey(Key) then
begin
  case Key of
    KEY_UP:    MoveUp;
    KEY_DOWN:  MoveDown;
    KEY_LEFT:  MoveLeft;
    KEY_RIGHT: MoveRight;
    KEY_ENTER: Confirm;
    KEY_ESCAPE: Cancel;
  end;
end
else
begin
  // Normales Zeichen
  ProcessChar(Chr(Key));
end;

GetCh ist eine Abkürzung, die Char statt Integer zurückgibt. Bei Sondertasten gibt es #0 (Null-Zeichen) zurück — verwenden Sie GetKey, wenn Pfeiltasten unterschieden werden müssen.

Sondertasten-Konstanten

const
  KEY_UP    = 256 + 38;  // VK_UP
  KEY_DOWN  = 256 + 40;  // VK_DOWN
  KEY_LEFT  = 256 + 37;  // VK_LEFT
  KEY_RIGHT = 256 + 39;  // VK_RIGHT
  KEY_ESCAPE = 27;
  KEY_ENTER  = 13;

IsSpecialKey(KeyCode) gibt True zurück, wenn KeyCode > 255, d.h. wenn der Wert eine Pfeiltaste oder einen anderen virtuellen Schlüssel statt eines druckbaren Zeichens darstellt.

Nicht-blockierende Tastenprüfung

KeyPressed gibt True zurück, wenn eine Taste im Eingabepuffer wartet, ohne sie zu verbrauchen:

// Animationsschleife, die bei beliebigem Tastendruck endet
while not KeyPressed do
begin
  UpdateAnimation;
  Sleep(50);
end;
Key := GetKey; // Ausstehende Taste verbrauchen

WaitForReturn

WaitForReturn blockiert, bis der Benutzer Enter drückt — eine sauberere Alternative zu ReadLn für “Drücken Sie Enter zum Fortfahren”-Aufforderungen:

WriteInfo('Überprüfung abgeschlossen. Drücken Sie ENTER zum Fortfahren.');
WaitForReturn;

Terminal-Fähigkeiten

Konsolengröße

GetConsoleSize gibt die sichtbaren Terminal-Fensterabmessungen zurück:

var Size: TMVCConsoleSize;

Size := GetConsoleSize;
WriteLn(Format('Terminal: %d x %d', [Size.Columns, Size.Rows]));

GetConsoleBufferSize gibt die vollständige Puffergröße zurück (unter Windows kann der Scroll-Puffer größer als das Fenster sein). Für TUI-Layouts verwenden Sie immer GetConsoleSize — Sie wollen den sichtbaren Bereich.

Terminal-Erkennung

if IsTerminalCapable then
  StartInteractiveMode
else
  // Umgeleitet in Datei — interaktive Funktionen überspringen
  WritePlainOutput;

WriteLn('Ausgeführt auf: ' + GetTerminalName);
// Windows → 'Windows Console'
// Linux   → Wert der $TERM-Variable (z.B. 'xterm-256color')

Farben und ANSI

// ANSI-Farben aktivieren (erforderlich vor direkter Verwendung von Fore/Back/Style)
EnableANSIColorConsole;

// Prüfen ob ANSI aktiv ist
if IsANSIColorConsoleEnabled then
  WriteLn(Fore.Green + 'Farben aktiv!' + Style.ResetAll);

Hilfsfunktionen

ClrScr

Löscht die gesamte Konsole und bewegt den Cursor auf 0, 0. Zu Beginn eines Bildschirms für einen sauberen Start verwenden:

ClrScr;
WriteHeader('Server-Steuerungspanel');

Beep

Gibt einen Systemton aus. Nützlich als akustisches Fehlersignal:

if CriticalError then
begin
  Beep;
  WriteError('Kritischer Fehler!');
end;

Unter Windows ruft es WinAPI.Windows.Beep(800, 200) auf. Unter Linux schreibt es #7 nach stdout.

FlashScreen

Kehrt kurz die Bildschirmfarben als visuellen Alarm um:

FlashScreen; // 100ms-Flash
WriteError('Ungültige Eingabe');

Unter Linux verwendet dies die ANSI-Reverse-Video-Escape-Sequenz. Unter Windows werden die Farben manuell mit FillConsoleOutputAttribute umgekehrt.

Farb-Hilfsfunktionen

// Den Namen einer Farbkonstante als String abrufen
WriteLn(ColorName(Green)); // 'Green'
WriteLn(ColorName(DarkGray)); // 'DarkGray'

// Farben manuell speichern/wiederherstellen (niedrigeres Niveau als TConsoleColorStyle)
SaveColors;
TextColor(Red);
TextBackground(DarkBlue);
Write('Hervorgehoben');
RestoreSavedColors;

SaveColors und RestoreSavedColors arbeiten auf einem einzigen globalen Slot (gleicher Vorbehalt wie SaveCursorPosition). Sie werden intern von WriteColoredText verwendet und werden selten direkt benötigt.

Alles zusammenführen: ein Server-Dashboard

Hier ist ein realistisches Beispiel, das Boxen, Statusmeldungen, einen Spinner, Fortschritt und ein Menü zu einem einfachen Server-Steuerungspanel kombiniert:

procedure RunServerPanel;
var
  Menu: Integer;
  S: ISpinner;
  P: IProgress;
  I: Integer;
  Data: TStringMatrix;
  Headers: TStringArray;
begin
  EnableUTF8Console;
  ClrScr;

  // Kopfzeile
  WriteHeader('DMVC Server-Steuerungspanel', 80, Cyan);
  WriteLn;

  // Status-Box
  Box('Aktueller Status', [
    'Webserver:   ONLINE',
    'Datenbank:   VERBUNDEN',
    'Cache:       WARNUNG - 67% Miss-Rate',
    'Backup:      INAKTIV'
  ], 50);
  WriteLn;

  // Protokoll-Tabelle
  SetLength(Headers, 3);
  Headers[0] := 'Zeit'; Headers[1] := 'Level'; Headers[2] := 'Meldung';

  SetLength(Data, 3);
  Data[0] := ['14:32:01', 'INFO',  'Anfrage GET /api/users → 200 (12ms)'];
  Data[1] := ['14:32:03', 'WARN',  'Cache-Miss: /api/products/42'];
  Data[2] := ['14:32:07', 'ERROR', 'DB-Abfrage-Timeout nach 5000ms'];

  Table(Headers, Data, 'Aktuelle Protokolleinträge');
  WriteLn;

  // Hauptmenü
  Menu := Menu('Server-Aktionen', [
    'Webserver neu starten',
    'Cache leeren',
    'Datenbankmigration ausführen',
    'Vollständige Protokolle anzeigen',
    'Beenden'
  ]);

  case Menu of
    0: begin
         // Neustart mit Spinner
         S := Spinner('Webserver wird neu gestartet', ssDots, Cyan);
         Sleep(2000);
         S.Hide;
         WriteSuccess('Webserver erfolgreich neu gestartet');
       end;
    1: begin
         // Cache leeren mit Fortschritt
         P := Progress('Cache-Einträge werden geleert', 100);
         for I := 1 to 100 do
         begin
           P.Update(I);
           Sleep(15);
         end;
         P := nil;
         WriteLn;
         WriteSuccess('Cache geleert: 4.832 Einträge entfernt');
       end;
    2: begin
         if Confirm('Datenbankmigration ausführen? Dies kann mehrere Minuten dauern.', False) then
         begin
           S := Spinner('Migration wird ausgeführt', ssGrow, Yellow);
           Sleep(3000);
           S.Hide;
           WriteSuccess('Migration abgeschlossen: 12 Skripte angewendet');
         end
         else
           WriteInfo('Migration abgebrochen');
       end;
    -1, 4:
       WriteInfo('Auf Wiedersehen!');
  end;
end;
Vollständiges DMVC Server-Steuerungspanel: Kopfzeile, Status-Box, Protokoll-Tabelle, Menü-Navigation

Schnellreferenz

Kategorie Funktion / Typ Beschreibung
Einrichtung EnableUTF8Console UTF-8-Ausgabe aktivieren (Box-Zeichen, Emojis)
Einrichtung EnableANSIColorConsole ANSI-Sequenzen unter Windows 10+ aktivieren
Low-Level-Farbe Fore.Red, Back.Blue, Style.Bright Inline ANSI-Escape-Konstanten
Ausgabe WriteLine(text, fg, bg) Vollzeilige farbige Ausgabe
Ausgabe WriteColoredText(text, color) Farbiges Inline-Segment, kein Zeilenumbruch
Ausgabe WriteAlignedText(text, width, align, color) Aufgefüllter/ausgerichteter Text
Status WriteSuccess / WriteWarning / WriteError / WriteInfo Statusmeldungen mit Präfix
Layout WriteHeader(title, width, color) Zentrierter Titel mit horizontalen Linien
Layout WriteSeparator(width, char) Horizontale Trennlinie
Layout WriteFormattedList(title, items, style) Aufzählung / nummeriert / Bindestrich / Pfeil
Widgets Box(title, lines, width) Umrandeter Inhaltsbereich
Widgets DrawBox(x, y, w, h, style, title) Rohe Box an absoluten Koordinaten
Widgets Table(headers, data, title) Statische formatierte Tabelle
Widgets TableMenu(title, headers, data) Tastaturnavigierbare Tabelle, gibt Zeilenindex zurück
Widgets Menu(title, items, default) Tastaturnavigierbare Menü, gibt Element-Index zurück
Animation Spinner(msg, style, color): ISpinner Hintergrundthread-Spinner (10 Stile)
Animation Progress(msg, maxValue): IProgress Bestimmter / unbestimmter Fortschrittsbalken
Eingabe GetKey: Integer Blockierendes Tastenlesen inkl. Sondertasten
Eingabe KeyPressed: Boolean Nicht-blockierende Tastenverfügbarkeitsprüfung
Eingabe WaitForReturn Blockiert bis Enter gedrückt wird
Eingabe Confirm(msg, default): Boolean J/N-Aufforderung
Eingabe Choose(title, options): Integer Nummerierte Auswahlaufgabe
Cursor HideCursor / ShowCursor Cursor-Sichtbarkeit umschalten
Cursor GotoXY(x, y) Zu absoluter Spalte/Zeile bewegen
Cursor GetCursorPosition: TMVCConsolePoint Aktuelle Cursor-Koordinaten lesen
Bildschirm ClrScr Bildschirm löschen, Cursor auf 0,0
Bildschirm ClearRegion(x, y, w, h) Rechteckigen Bereich löschen
Bildschirm GetConsoleSize: TMVCConsoleSize Sichtbare Terminal-Abmessungen
Theme ConsoleTheme: TConsoleColorStyle Globale Farben und Box-Stil für alle Widgets

Häufig gestellte Fragen

Erfordert MVCFramework.Console eine vollständige DMVCFramework-Installation?

Nein. Die Unit ist eigenständig und hat keine Abhängigkeiten vom Rest des Frameworks. Kopieren Sie MVCFramework.Console.pas in jedes Delphi-Konsolenprojekt und fügen Sie es zur uses-Klausel hinzu. Keine Pakete, keine BPL-Dateien, kein IDE-Setup erforderlich.

Ist MVCFramework.Console plattformübergreifend?

Ja. Die Bibliothek kompiliert und läuft auf Windows 10+ und Linux mit derselben API. ANSI-Farbunterstützung ist unter Linux nativ; unter Windows wird sie automatisch über EnableANSIColorConsole aktiviert. Box-Zeichenzeichen und Emojis erfordern EnableUTF8Console beim Start.

Welche Delphi-Version wird benötigt?

Delphi 10 Seattle (D100) oder höher. Die Unit wird mit DMVCFramework 3.5.0 Silicon und späteren Versionen geliefert.

Wie lese ich Pfeiltasten in einer Delphi-Konsolenanwendung?

Verwenden Sie GetKey: Integer. Pfeiltasten geben Werte über 255 zurück: KEY_UP = 256+38, KEY_DOWN = 256+40, KEY_LEFT = 256+37, KEY_RIGHT = 256+39. Verwenden Sie IsSpecialKey(key), um Sondertasten von druckbaren Zeichen zu unterscheiden.

Wie erstelle ich einen nicht-blockierenden Spinner?

S := Spinner('Verbinde...', ssDots, Cyan);
DoWork;       // Hauptthread läuft weiter; Spinner animiert unabhängig
S.Hide;       // oder: S := nil

Der Spinner läuft auf einem Hintergrundthread. Zehn Stile sind verfügbar: ssLine, ssDots, ssBounce, ssGrow, ssArrow, ssCircle, ssClock, ssEarth, ssMoon, ssWeather.

Wie ändere ich die Farben und den Box-Stil aller Widgets auf einmal?

Ändern Sie den globalen ConsoleTheme-Record bevor Sie eine Widget-Funktion aufrufen. Alle nachfolgenden Aufrufe von Box, Menu, Table, WriteHeader und ähnlichen Funktionen verwenden die aktualisierten Einstellungen. Speichern und stellen Sie den Record wieder her, wenn ein temporärer Stilwechsel benötigt wird.

Wie erstelle ich ein interaktives, per Tastatur navigierbares Menü?

Selected := Menu('Optionen', ['Starten', 'Stoppen', 'Beenden']);
// Pfeiltasten navigieren, Enter bestätigt, Escape gibt -1 zurück

Für eine interaktive Tabelle mit Zeilenauswahl verwenden Sie TableMenu(Titel, Kopfzeilen, Daten).

Zusammenfassung

MVCFramework.Console bietet ein vollständiges Toolkit zum Entwickeln von TUI-Anwendungen in Delphi — von einfachen farbigen Statusmeldungen bis hin zu interaktiven Menüs mit Tastaturnavigation, animierten Spinnern, Fortschrittsbalken und strukturierten Tabellen.

Die wichtigsten Design-Punkte:

  • Keine Framework-Abhängigkeit. Unit in jedes Konsolenprojekt einbinden.
  • Plattformübergreifend. Windows (10+) und Linux mit derselben API.
  • Interface-basierter Lebenszyklus. ISpinner und IProgress bereinigen automatisch beim Freigeben.
  • Theme-System. Ein einziger globaler Record steuert alle Widget-Farben und Rahmenstile.
  • Zwei Ebenen. Low-Level (TextColor, GotoXY, DrawBox) für benutzerdefinierte Layouts; High-Level (Menu, Table, Spinner) für Standard-Widgets.

Das vollständige Beispiel befindet sich in samples/console_sample/ConsoleDemo.dpr im DMVCFramework-Repository auf GitHub.


Dieser Beitrag ist Teil der DMVCFramework 3.5.0 “Silicon”-Serie. Für die Low-Level-ANSI-Farb-API (Fore, Back, Style) und den Gin-artigen HTTP-Log-Renderer, siehe den vorherigen Beitrag der Serie.

Comments

comments powered by Disqus