TURBO-VISION, Fenster im DOS-Text-Modus
Moderne Bedientechnik

In dieser Folge kleiner Beispiele wird die Verwendung der TURBO-VISION
Bibliothek fr DOS-Anwednungen gezeigt. Im Gegensatz zu den Beispielen
im Handbuch sind die hier Gezeigten auf ein Minimum zusammengestrichen,
damit die wesentlichen Zeilen berbleiben.

Eins mu man bedenken: Nicht mehr die eigene Anwendung ist das Zentrale
am Programm, sondern die Art, wie der Benutzer damit umgeht.
Alte Programmkonzepte in TURBO-VISION bertragen zu wollen, bedeutet nicht,
da man an ein oder zwei Stellen etwas einflickt, sondern es bedeutet,
da man den eigenen Kode in das TURBO-VISION Konzept einzufgen hat.

Wenn auch C und C++-Kode gut zueinander passen: Objektorientierte
Programme sind anders.

TV1.CPP Der kleinste Schreibtisch

Diese kleinste mgliche Anwendung zeigt, was zu tun ist,
um einen Schreibtisch 'desktop' zu erffnen.
Der Schreibtisch kennt als einzige Funktion die
zum Schlieen, ALT-X.

Diese Anwendung zeigt auch, da ein 'desktop' aus einer Titelzeile,
eine Fuzeile undeiner Arbeitsflche besteht, die zunchst leer sind,
mit Ausnahme der Bezeichnung ALT-X zum Ausstieg.

In der Klassenterminologie ausgedrckt:
Das Objekt hello besitzt ein Objekt der Klasse TDeskTop,
ein Objekt des Typs TMenuBar und ein Objekt des Typs TStatusLine.

In allen Beispielprogrammen wird einzig die Datei tv.h inkludiert.
Es werden jeweils Konstanten vorangestellt, die die jeweils
bentigte Klasse aktivieren, hier: Uses_TProgram. Dem Klassennamen
wird Uses_ vorangestellt.

Jedes Turbo-Vision-Programm enthlt eine Instanz von
TProgramm (oder von TApplication oder einer daraus abgeleiteten Klasse)
Dieses Programm wird mit hello.run() ausgefhrt und kehrt mit ALT-X
aus hello zurck.

TV11.CPP Ein Fenster am Schreibtisch

Ein Programm TProgram kann neben der Arbeitsflche, Statuszeile und
Menzeile auch Fenster von Benutzern beherbegen. Das Programm tut das
nicht direkt, sondern die Arbeitsflche tut es.

Welche Klasse andere Klassen beherbergen kann, hngt davon ab,
ob sie von der Klasse TGroup abgeleitet sind. TGroup ist eine
Container-Klasse.

Fenster sind hier mehr als nur rechteckige Flchen; Fenster
knnen in der Gre verndert werden und knnen geschlossen werden.

Jedes Fenster bentigt einen Anfangswert fr seine ursprngliche
Gre, hier das Objekt TRect. und eine Titelzeile "HELLO" und
eine Fensternummer (1). Fenster knnen andere Fenster beherbergen.
Die Aufnahme und das Zeichnen geschieht mit insert, einer
Memberfunktion von TGroup.

TV12.CPP Text im Fenster

Ein Objekt kann statisch oder dynamischen Charakter haben.
Statische Objekte knnen global oder lokal sein.
Dementsprechend verschieden ist der Speicherort:

global:    im Datensegment
lokal:     am Stack
dynamisch: am Heap

Die meisten Objekte werden dynamisch generiert. Deshalb werden
auch alle Beispiele ab jetzt nur mehr mit dynamischen Objekten
arbeiten.

Auerdem wurde ein Text in das Fenster geschrieben. Es gibt in jedem
TWindow-Objekt die Funktion writeStr();

Der Text pat nicht in das Fenster und auerdem berschreibt der Text
den Rand! Was ist zu tun?

TV2.CPP Ein Fenster hat einen Innenraum

Wie soll ein Fenster auch wissen, was in seinem Inneren zu stehen hat?

Der Mechanismus dazu ist die Eigenschaft einer TGroup, alle TViews
zeichnen zu knnen, die sie beherbergt; und zwar durch berladen
der Funktion draw() eines TView.

Der Innenraum eine Fensters ist genauso ein TView wie das Fenster selbst.
Der DeskTop besitzt helloWin und helloWin besitzt i, den Innenraum.
Innen ist von TView abgeleitet. Bei der Konstruktion wird darauf geachtet,
da das Fenster vernderlich ist. draw wird berladen.
draw ist jene Funktion, die bei einer Vernderung des Fensters das
Fenster und alle beherbergten Subviews neu zeichnet.

Man kann die aktuelle Rechteckflche eines TViews mit getClipRect
bestimmen. Rechtecke knnen wachsen grow(). Der Innenraum wird
mit insert() in helloWin eingesetzt, wie auch helloWin in desktop
eingesetzt wird.

TV21.CPP Text in einem Fenster

Soll ein gleichbleibender Text angezeigt werden, gengt die
dafr vorgesehene Klasse TStaticText.

TV22.CPP Text ohne Rahmen

Ein gleichbleibender Text mu aber gar nicht von einem vernderbaren
Fenster umgeben sein, daher kann man es auch entfallen lassen.

TV23.CPP Text mit Rahmen, nicht vernderbar

Soll auch ein Rahmen vorhanden sein, kann man ihn auch individuell
zufgen. Achtung: zuerst Rahmen zeichnen, dann erst Inhalt.

TV3.CPP Wie wird der Schreibtisch gemacht

Der ursprngliche Schreibtisch ist noch leer; es gibt keine Menzeile
und keine Statuszeile. Will man einen vernderten Schreibtisch, mu
man entweder von TProgram oder von TApplication ein abgleitetes Objekt
schaffen, das die vernderten Zeilen enthlt.

Die Vererbungskette ist wie folgt:

TView
|
|
TGroup  TProgInit
|       |
|       |
+-------+---TProgram
            |
            +---TApplication

Tprogram hat also zwei 'Eltern': TProgInit und TGroup.
Der Konstruktor von TApplication mu bede Elternteile initialisieren:

TApplication::TApplication() :
  TProgInit(&TApplication::initStatusLine,
            &TApplication::initMenuBar,
            &TApplication::initDeskTop)

Zuerst wollen wir nur das nachahmen, was TApplication ohnehin kann. Dazu
mssen wir drei Pointer initialisieren:

initDeskTop, initStatusLine, initMenuBar

initDeskTop
Der Schreibtisch wird blicherweise mit einem Rechteck 0,0..79,24
initialisiert. Die Schreibflche ist um jeweils eine Zeile fr
Men und Kopfzeile kleiner.

initStatusLine
Eine Statuszeile wird ebenso mit einer Rechteckflche initialisiert,
die von einer Liste von Pointern auf Statuszeilenelemente gefolgt wird.
Um den Listenaufbau zu vereinfachen, wurde der Operator + berladen,
die Bestandteile knnen einfach aneinandergereiht werden.
Das erste Element, TStatusDef enthlt als erstes Variablenpaar
das Minimum und das Maximum des Help-Kontext. Es werden nur StatusItems
angezeigt, die sich innerhalb des Help-Kontext befinden.
Normalerweise 0,0xffff.

Ein TStatusItem enthlt die Parameter
Text, der angezeigt wird, mit dem Zeichen '~' wird hellgetastet.
Tastenbezeichnung, die die Funktion auslst
Kommando, das mit dieser Taste ausgefhrt wird.

TV31.CPP Kleinere Arbeitflche

Jemand knnte auf die Idee kommen, nicht den ganzen Bildschirm
fr sein Projekt zu benutzen, sondern nur einen Teil und etwa
zwei, vielleicht verschieden gestaltete Bildschirme darzustellen.
Der Weg dazu ist, die von der Initialisierungsklasse vorgegebenen
Rechteckflchen nicht zu benutzen und eigene einzusetzen.
Ein kleines Problem entsteht durch die Nichtbenutzung
des bergabeparameters r. #pragma warn -par schaltet diese
Warnung aus und nach einer kurzen Zeit wieder ein.

TV32.CPP Mehr Statuszeilen, Pull-DownMens

Wenn es Fenster gibt, sollte es auch ein Kommando geben, mit dem
man ein Fenster generell schlieen kann. Es ist ALT-F3, wie auch beim
Borland-Compiler. Da dieses Kommando auf jedes Fenster anwendbar ist,
kommt der entsprechende Hinweis in die Statuszeile.

Pull-Down-Mens kommen in die Men-Zeile. Sie haben einen hnlichen Aufbau,
wie die Status-Zeilen.
Als erstes kommt TSubMenu, es beschreibt den Pull-Down-Kasten, danach
kommen so viele Eintrge, als man Menpunkte bentigt.

Beginnen wir zunchst mit dem Pull-Down, das in jedem Programm links
oben steht: Files. Es enthlt im Prinzip immer die Eintrge zum ffnen,
Schlieen, Anlegen einer Datei, zum Drucken,
zum Ausstieg ins Betriebssystem und zum Verlassen des Programms.

Jede einzelne Menzeile kann mit einer Auswahltaste (ALT-x),
manchmal mit einer Funktionstaste (F3) oder mit dem Kursor (+ENTER)
oder mit der Maus angewhlt werden.

Man mu aber dem Programm auch sagen, welche Funktion auszufhren ist,
wenn eine Auswahltaste gedrckt wird. Das geschieht so:

In der Menzeile ist ein Feld fr eine Kommandobezeichnung reserviert.
Alle Kommandos beginnen mit cmxx. Jedes Kommando wird bei Aktivierung
der betreffenden Zeile an den Event-Handler weitergeleitet. Dieser
'Ereignis-Bearbeiter' enthlt im Prinzip eine groe switch-Anweisung,
in der die richtigen Funktionen aufgerufen werden.

Viele Kommandos sind vordefiniert und werden schon im ursprnglichen
Event-Handler von TProgram abgearbeitet. Im allgemeinen hat man weitere
Kommandos, die noch nicht in der Verarbeitungsfolge enthalten sind.
Fr diese schreibt man dann einen erweiterten Event-Handler, der nur
mehr fr die neuen Kommandos zustndig ist.

Der Bereich fr eigene Kommandos ist zwischen 100..255 und ab 1000.

Ein eigener Menpunkt soll fr das ffnen eines Fensters
verantwortlich sein.

In diesem Beispiel wurden zwar Men- und Statuszeile erweitert, man
kann sie bedienen, es geschieht aber noch nichts. Die Kommandos
werden intern abgesetzt aber werden nicht abgefragt.

TV33 Aktive Fenster

Die Menzeile wird aktiv, der Eintrag Window soll ein Fenster erffnen.
Dazu ist es erforderlich die Funktion handleEvent zu berladen und
die neuen Kommandos entsprechend abzuarbeiten. Wichtig ist: berladen
heit, da die neue Funktion an die Stelle der alten tritt. Um nicht
alle Abfragen in der alten Funktion noch einmal programmieren zu mssen,
rufen wir zuerst die alte Funktion durch:

  TApplication::handleEvent(event);

Dann erst kmmern wir uns um den Rest und auch nur dann, wenn es
sich um ein Kommando handelte:

  if (event.what == evCommand)
  {
    switch (event.message.command)
    {
    case cmNewWindow:

Ein event ist eine Struktur, die aus dem Kennfeld what, das neben
den Ereignissen Tastatur, Maus auch Kommandos (mit evCommand)
verarbeiten kann; dem eigentlichen Ereignis (mouse-keyDown-message) als
union; sowie zwei Funktionen: getMouseEvent() und getKeyEvent() besteht.
Das eigentliche Ereignis sind die Strukturen
MouseEventtype, KeyDownEvent, MessageEvent, deren Aufbau man
am einfachsten ber die Hilfefunktion erfhrt.

TV34.CPP Wiederkehrende Aktivitten

Datei erffnen

Im geschtzten Teil unserer Hello-Klasse wurde die Variable filename
eingesetzt, die den Dateinamen enthalten soll. Der Konstruktor setzt
sie auf "*.*".

Den Dateinamen kann man dem File-Dialog entweder ber den Konstruktor
oder ber die Memberfunktion setdata() bergeben.

      TFileDialog *file = new TFileDialog
                          (
                            filename,
                            "Open a File",
                            "~N~ame",
                            fdOpenButton,
                            100);
//      file->setData( filename );
      TProgram::deskTop->execView( file );
      file->getData( filename );
      TObject::destroy( file );

Die Funktion execView() des Schreibtischs ist eine Kombination von
insert() und remove() aber zwischen den beiden wird gewartet und
das Resultat der Eingabe abgewartet. Die Zusatzbezeichnung TProgram::
ist nicht unbedingt erforderlich, da es ohnehin nur den einen
Schreibtisch gibt, sie dient aber - wie andere an dieser Stelle auch -
zur Klarstellung.

Achtung: Die obige Darstellung ist stark vereinfacht. Es fehlt jede
Fehlerprfung. Wies wirklich gemacht wird sieht man im Beispiel
TVEDIT2.CPP in der Funktion execDialog(). Weiters ist noch ein Mangel,
da der Funktionsname ein einziges Mal richtig geholt wird, das
zweite Mal nicht mehr, da die vollstndige Dateibezeichnung
inklusive Pfad mitgeschlappt wird.

DOS-Ausstieg

Praktisch jede Anwendung besitzt die Mglichkeit ins DOS auszusteigen.
Die Funktion suspend() in TApplication erledigt drei einzelne
suspend-Funktionen (entnommen aus der Quelldatei TAPPLICA.CPP):

    TSystemError::suspend();
    TEventQueue::suspend();
    TScreen::suspend();

Daraus kann man ersehen, da auch gleichzeitig andere Klassen
wirksam sind, die der Letztverbraucher normalerweise nicht
zu Gesicht bekommt.

      TApplication::suspend();
      system("cls");
      cout << "Type EXIT to return...";
      system( getenv("COMSPEC") );
      TApplication::resume();
      TGroup::redraw();

resume() ist dementsprechend die Umkehrung von suspend(). Der Zusatz
TApplication:: dient nur zur Klarstellung, ist nicht unbedingt erforderlich
(aber wie man sieht, gibt es viele suspen() und resume()-Funktionen
im System). redraw() hilft, zerstrte Bildschirme wiederherzustellen.

Damit unser Programm nicht unkontrolliert wchst, wollen wir wieder
von vorne anfangen:

Gefragt wre eine Einstellbox mit mehreren Feldern. Eintragungen
mit Anfangswerten haben folgende Namen:

Check boxes   : Schalter     : ushort
Radio-Buttons : Umschalter   : ushort
Input Line    : Eingabezeile : string[20]

Der Menpunkt hiee "Einstellungen" und erzeugt eine Eingabebox fr
einen Funktionsgenerator:

                                    Kurveform
   Frequenz: _________ Hz           x Sinus
   Spannung: _________ V            _ Dreieck
   Offset  : _________ V    _ EIN   _ Rechteck

   __ OK   __ CANCEL

Jede Eingabe dieser Form erfordert eine Dialogbox, die mit
TDialog erzeugt wird.

In dieses Fenster werden mit insert() die einzelnen Bestandteile
eingesetzt, danach wird mit execView() der Dialog-Box der Auftrag
gegeben die Eingabe auszuwerten.

TV4.CPP

Zuerst wollen wir einmal eine Minianwendung herstellen, die
zwar eine Klasse Generator aus TApplication ableitet aber keine
neuen Menzeilen und Statuszeilen erzeugt. (Nicht, weil das so
sinnvoll ist aber das Programm bleibt so sehr kurz und zeigt
das Wesentliche.

TV41.CPP

Auerdem kann man zeigen, wie man eine
besondere Tastenkombination abfragen kann, die nicht in einem der Mens
enthalten ist.)

void Generator::handleEvent(TEvent& event)
{
  TProgram::handleEvent(event);
  if (event.what == evKeyboard)
  {
    switch (event.keyDown.keyCode)
    {
    case kbAltE:
      TDialog *einstell = new TDialog(TRect(20,6,60,19),"Einstellungen");
      if (einstell)
      {
        einstell->insert(new TButton(TRect(15,10,25,12),"~O~K",cmOK,bfDefault));
        einstell->insert(new TButton(TRect(28,10,38,12),"Cancel",cmCancel,bfNormal));
        TGroup::execView(einstell);
      }
      TObject::destroy(einstell);
      break;
    default:
      return;
    }
    clearEvent(event);
  }
}

Alle spielt sich in der berladenen Funktion handleEvent() ab.
Dort wird ein Dialogfenster angelegt: new TDialog(...
In dieses Dialogfenster werden zwei Schalter eingesetzt, wobei
einer der beiden (OK) durch bfDefault die Voreinstellung ist, der andere
(Cancel) mit bfNormal angeklickt werden mu. Da fr Cancel bereits
ESC definiert ist, mu Cancel nicht extra mit 'C' durch ~C~ancel
anwhlbar sein. execView fhrt die Dialog-Box aus, destroy()
lscht sie. Achtung: nicht delete verwenden. Wird im Handbuch
ausdrcklich vermerkt.

Nachdem die Dialog-Box grundstzlich funktioniert, sind die
eigentlichen Einstellungen vorzunehmen:

TV42.CPP

In die Dialog-Box werden die drei mglichen Arten von Feldern
eingesetzt:

Eingabezeilen, Check-Boxen und Umschalter.

Wie die einzelnen Felder aufgebaut werden, sieht man aus dem Programm.
Wichtig ist, wie man die Daten in die Dialog-Box einbringt und wie
man sie nach erfolgter nderung wieder herausholt.

Grundstzlich kann man jedem TView, den man in die Dialog-Box einsetzt,
mit setData() die Daten bergeben und mit getData() wieder holen.
Einfacher ist es, man konstruiert in der Anwenderklasse eine Struktur,
die alle Variablen enthlt. Diese Struktur wird im Konstruktor initialisiert.
Wichtig ist, da die Elemente in der Struktur dieselbe Reihenfolge haben,
wie auch die TViews in das Dialog-Fenster eingesetzt werden. Dann
gengt ein einmaliger Aufruf von setData() zu Beginn des Dialogs
und von getData() am Ende des Dialogs.

TV51.CPP

Den Abschlu unserer Versuche rund um die Turbo-Vision-Bibliothek bildet
die Erffung eines Editors.





