Windows-Programmierung mit BORLAND-C

Der einfachste Umstieg ist:

* Verzicht auf Farbe und Grafik
* Beschrnkung auf
    gotoxy(), wherex(), wherey()
    clrscr(), clreol()
* include <windows 

Was braucht man unbedingt:

#include <windows.h>

#pragma argsused
int PASCAL WinMain
(
  HANDLE hInstance,
  HANDLE hPrevInstance,
  LPSTR lpszCmdLine,
  int cmdShow
)
{
  _InitEasyWin();
  ...
  alles weitere ist beliebig
}

Wie man sieht, ersetzt WinMain() das bekannte main() aus C und C++.
Der Startup-Kode ist auch unterschiedlich. Etwa bentigt man mehr
Parameter als noch unter DOS:

hInstance
Jede Windows-Applikation unterscheidet sich von einer anderen
durch ein HANDLE, eine ganze Zahl.

hPrevInstance
Gibt es mehr als nur eine Instanz, dann enthlt dieser Wert das HANDLE auf
die vorige Instanz. Gibt es keinen Vorlufer, ist dieser Wert 0.

lpszCmdLine
Pointer auf die Kommandozeile.

cmdShow
Zeigt auf das erste darzustellende Fenster

_InitEasyWin()
Initialisiert die verwendbaren stdio- und conio-Funktionen.

EASY1.CPP

Dieses Programm zeigt die Verwendung der stdio-Funktionen. die Zeile
#pragma argsused
verhindert die Fehlermeldung, da die bergebenen Parameter
ungenutzt bleiben.

EASY2.CPP

Diese Programm zeigt die Werte der vier bergabeparameter. Man sieht,
da die Kommandozeile mit Ausschlu des Programmnamen selbst gezhlt wird.

Am besten verwendet man EasyWin zum Debuggen, wenn der eingebaute
Debugger nicht komfortabel genug arbeitet.

Programmieren mit Object Windows

Folgende Voraussetzungen sind fr das Arbeiten mit ObjectWindows
erforderlich:

Options
  Compiler - LARGE Model
  Linker Libraries
    Container(Static)
    Object Windows(Static)
    StandardRunTime (Static)
  Directories
    Pfade auf INCLUDE- und LIB-Subdirectories von RTL,CLASSLIB und OWL

OW0.CPP
Eine Object-Windows-Anwendung wird von der Klasse TApplication
abgeleitet. ber die Gre des Fensters mu man sich in
Windows zunchst nicht kmmern. Einer TApplication bergibt man
den Titel (hier "Hello") die entsprechenden HANDLE und
auch die Kommandozeile.

Die einfachste Anwendung (ein einzelnes Fenster) nimmt uns
einmal die Aufgabe ab, sich um die bergabeparameter kmmern zu mssen, indem
die Klasse TApplication diese als Parameter bernimmt. Das Objekt a der
Klasse TApplication wird mit a.Run() ausgefhrt.


  TApplication a
  (
    "Hello",
    hInstance,
    hPrevInstance,
    lpCmdLine,
    nCmdShow
  );
  a.Run();
  return a.Status;

OW01.CPP

Wie kann man nun etwas an diesem Basisvorgang verndern?
Zunchst knnte
man versuchen, eine bestehende Funktion von TApplication zu berladen;
z.B. CanClose().

Wie kann man den Benutzer fragen, ob er aufhren will?
Man ffnet eine MessageBox und teilt dieser Box mit,
welchen Titel("Verabschiedung"),
welche Botschaft("Wollen Sie wirklich aufhren?")
sie haben soll und welche Schalter(MB_YESNO | MB_ICONQUESTION)
sie enthalten soll.

Die mglichen anderen Werte der Schalter und der Antworten der MessageBox
erfhrt man am besten ber die eingebaute on-line-Hilfe.

Die MessageBox mu aber, wie viele andere Fenster auch, wissen, von welchem
Fenster sie abstammt. hInstance ist ein Mitglied von TModule und enthlt
das aktuelle HANDLE der Applikation.


OW01.CPP

In diese neuen Klasse knnen die Funktionen zum Zeichnen
des Fensters InitMainWindow() und CanClose() zu lschen des
Fensters berladen werden.

Das berladen des Fensters selbst generiert einen anderen
als den Anfangstext durch ffnen eines Fensters TWindow.
Zum Verlassen des Programms einer MessageBox mit einem
JA/NEIN-Knopf. IDYES bedeutet, da der OK-Knopf
selektiert wurde.

OW02.CPP

Jede Anwendung TApplication hat so etwas wie einen Konstruktor,
nmlich eine Funktion, die aufgerufen wird, wenn eine Instanz
dieser Anwendung kreiert wird: InitInstance(); aber auch eine Funktion,
die beim allerersten Aufruf dieser Anwendung aufgerufen wird:
InitApplication().

  void InitInstance()
  {
    TApplication::InitInstance();

    HDC DC;
    char S[100];

    sprintf(S,"Begrungstext");
    DC = GetDC(MainWindow->HWindow);
    TextOut(DC,100,100,S,strlen(S));
    ReleaseDC(MainWindow->HWindow,DC);
  }

Dieses InitInstance druckt einen Begrungstext aus. Ganz so einfach
geht das aber nicht:

   Man schreibt nicht direkt in den Bildschirm, sondern in einen
   Display-Kontext HDC, den man von der Anwendung mit GetDC() erhlt.
   Alle Ausgaben werden auf diesen Display-Kontext bezogen, indem
   dieser als Argument in der Ausgabefunktione TextOut() bergeben wird.
   Das Wichtigste aber ist, den Display-Kontext durch ReleaseDC()
   wieder freizugeben, sonst reagiert Windows hnlich wie bei einem
   Stapelberlauf auf Grund fehlender POP-Befehle.

Das Hauptfenster in TApplication ist einfach ein leeres Fenster.
Will man etwas in das Fenster schreiben, mu man die Funktion
InitMainWindow() berladen und ein eigenes Fenster einsetzen, dem man
den Namen der Anwendung gibt.

Wie kann jetzt ein Fenster aktiv werden?

In der Klasse des neuen Fensters TMyWindow wird die virtuelle
Funktion WMLButtonDown() berladen. diese Funktion erhlt als eine
BORLAND-C-Besonderheit einen Dispatcher-Index in eckigen Klammern nachgestellt
der die Nummer der Botschaft enthlt, die diese Funktion aktivieren soll,
hier WM_LBUTTONDOWN. Der zustliche Wert WM_FIRST ist lediglich ein
Offset fr alle Konstanten, der praktisch in jedem Dispatcher-Index vorkommt.

ACHTUNG: Diese Syntax ist vllig ungenormt, vereinfacht zwar die Sache
ungemein, ist aber berhaupt nicht portabel. Bei Microsoft-C mssen die
Botschaften im Event-Handler mit switch-Anweisungen an die richtige Funktion
verteilt werden, hnlich, wie es auch bei der TURBO-VISION-Bibliothek
der Fall ist.


In diesem Beispiel wird die aktuelle Kursorposition beim Drcken der linken
Maustaste ausgegeben.

    HDC DC;
    char S[100];

    sprintf(S,"(%d,%d)",Msg.LP.Lo,Msg.LP.Hi);
    DC = GetDC(HWindow);
    TextOut(DC,Msg.LP.Lo,Msg.LP.Hi,S,strlen(S));
    ReleaseDC(HWindow,DC);


OW03.CPP

Wie kann man in ein Fenster etwas schreiben? Dazu gibts die Funktion
Paint() der Klasse TWindow, die fr unseren Zweck in der Klasse TMyWindow
berladen wird und einen neuen Begrungstext ausgibt.

Versuchen Sie einmal das Fenster in seiner Gre zu verndern!
Die Koordinatenangaben verschwinden alle wieder, der Begrungstext bleibt!
Warum? Die Funktion Paint() wird bei jeder Vernderung des Bildschirms gerufen,
die Funktion WMLButtonDown() dagegen nur bei jedem Mausklick.

OW04.CPP

Was kann man also tun, da sich der Fensterinhalt beim Verschieben
mitndert? Man mu sich merken, was der Benutzer auf den Bidlschirm gemalt hat!
Wie? Nichts einfacher als das! Mit den Hilfsmitteln der Container-Bibliothek
schafft man das mit ein paar Zeilen, wir wollen es besonders schn machen
und verwenden fr einen Punkt eine eigene struktur mit hbsch berladenen
Operatoren. Die Punkte selbst speichern wir in einer Liste genau
in der Reihenfolge der Eingabe:

  struct Pt
  {
    int x;
    int y;
    Pt(int x1, int y1) {x=x1; y=y1;}
    Pt() {x=0; y=0;}
    int operator!=(Pt p) { return !(p.x==x && p.y==y); }
    int operator==(Pt p) { return (p.x==x && p.y==y); }
  };

Nicht nur, da die Struktur Pt ein Koordinatenpaar x,y enthlt, es gibt auch
einen Defaultkostruktor Pt(), der die Punktkordinaten auf Null setzt und
einen Konstruktor mit Anfangswerten. Die beiden Operatoren == und !=
stellen fest, ob zwei Punkte gleich sind. Sie werden im restlichen Kode
vergeblich nach der Anwendung dieser Konstuktoren suchen. Wer braucht sie?
Wir werden die erfordeliche Liste natrlich nicht selbst schreiben, sondern
eine fertige Liste aus der Container-Bibliothek, am besten ein solche
mit Templates verwenden. Man mu bedenken, dap eine vordefinierte Liste
immer etwas mehr kann als man gerade bentigt. Sie enthlt also auch
Vergleichsmglichkeiten, die wir eigentlich gar nicht brauchen.
Fr einfache Variablentypen, fr die der
Vergleichsoperator implizit definiert ist, also fr ganze Zahlen und
Gleitkommanzahlen ist der Vergleich auf Gleichheit oder Ungleichheit
kein Problem. Ist es aber schon fr Strukturen. Wann sind zwei Strukturen gleich?
Genau diese Frage stellt der Compiler, wenn man versucht, die Punktstruktur
der vordefinierten Template-Klasse aufzudrcken. Nun, jetzt hat er sie,
und in der Zeile

  BI_ListImp<Pt> Points;

wird die Liste definiert. 

Der erste Punkt wird mit

    Points.add(Pt(0,0));

alle weiteren Punkte mit

    Points.add(Pt(Msg.LP.Lo,Msg.LP.Hi));

in dies Liste eingefgt. Der erste Punkt (0,0) dient als
Markierung fr das Ende der Liste.

Mit diesem Behelf merken wir uns alle Benutzereingaben (beachten Sie: 3 Zeilen)
und geben die so gespeicherte Liste in der Funktion Paint() wieder aus. 

Fr das Auslesen der Liste wird ein zerstrungsfreier Iterator
verwendet:

    BI_ListIteratorImp<Pt> i(Points);
    Pt p, p0;
    while ((p=i++) != p0)
    {
      sprintf(S,"(%d,%d)",p.x,p.y);
      TextOut(DC,p.x,p.y,S,strlen(S));
    }

Hier findet man auch die Abfrage auf das Ende der Liste mit dem Operator !=.

OW05.CPP

Windows ist ja aber eine grafische Benutzeroberflche,
die wir bisher nur fr Texte mibraucht haben. Dieses Beispiel
ist aber gleich gut auch fr Grafik verwendbar, indem wir statt der
Koordinatenangabe eine Linie ziehen. Dazu mssen wir aber nicht nur
den Mausklick, sondern auch die Mausbewegung bercksichtigen.
Die Maus soll nur zeichnen, wenn die Maustaste gedrckt ist.

Innerhalb der Fensterklasse TMyWindow mu man sich daher zwei Dinge
merken

 1. Den aktuellen Display-Kontext DragDC, der beim Drcken der linken
    Maustaste gesetzt wird. und
 2. den aktuellen Zustand der linken Maustaste in ButtonDown, da die Maus auch
    ohne Taste bewegt wird.

Die beiden neuen Funktionen WMMouse Move und WMLButtonUp
und die neu gestaltete Funktion WMLButtonDown lauten:

  virtual void WMMouseMove(RTMessage Msg)
   = [WM_FIRST + WM_MOUSEMOVE]
  {
    if (ButtonDown)
    {
      LineTo(DragDC,Msg.LP.Lo,Msg.LP.Hi);
      Points.add(Pt(Msg.LP.Lo,Msg.LP.Hi));
    }
  }

  virtual void WMLButtonDown(RTMessage Msg)
   = [WM_FIRST + WM_LBUTTONDOWN]
  {
    if (!ButtonDown)
    {
      ButtonDown = TRUE;
      DragDC = GetDC(HWindow);
      MoveTo(DragDC,Msg.LP.Lo,Msg.LP.Hi);
      Points.add(Pt(Msg.LP.Lo,Msg.LP.Hi));
    }
  }

  virtual void WMLButtonUp(RTMessage Msg)
   = [WM_FIRST + WM_LBUTTONUP]
  {
    if (ButtonDown)
    {
      ButtonDown = FALSE;
      ReleaseDC(HWindow,DragDC);
    }
  }


Das Zeichnen gelingt ja ganz gut aber beim Erneuern mit Paint() werden noch
die alten Punktkoordinaten gesetzt.

OW06.CPP

Der Unterschied zur Textausgabe ist lediglich der, da sich
die Wiederholung der gezeichneten Linie etwas vereinfacht in:

virtual void Paint(HDC DC, PAINTSTRUCT& Paintinfo)
  {
    BI_ListIteratorImp<Pt> i(Points);
    Pt p, p0;
    while ((p=i++) != p0)
    {
      LineTo(DC,p.x,p.y);
    }
    ReleaseDC(HWindow,DC);
  }


Das wrs, ein Fenster htten wir! Was fehlt sind die vielen in Windows
mglichen Steuerelemente. Dazu gibts eine Besinderheit bei der Programm-
entwicklung: Man benutzt nicht den Compiler allein, sondern zustzlich
einen sogenannten Resource-Compiler fr alle Kleinigkeiten, die
Windows son handlich machen, wie Icons, Fonts, Mens, Knpfe,
Eintragungsfelder, Scrol-Bars uvam.

Diese Resourcen werden in einer eigenen Sprache erstellt; aber keine
Angst, die mu man nicht wirklich knnen, der mitgelieferte Workshop
erlaubt es mehr oder weniger intuitiv, die Arbeitsoberflche zu entwickeln und
die dabei entstehende Resource-Quelldatei in ein Binrformat zu konvertieren,
welches beim Binden mit der eigenen EXE-Datei verbunden wird.

In einem eigenen Arbeitsschritt mit dem Workshop werden folgende Resourcen erstellt:

ICON, FONT, CURSOR, BITMAP, MENU

Das folgende Programm zeigt, wie diese Resourcen im eigenen Programm
mitverwendet werden knnen.

OW1.CPP

Dieses Programm ist in seiner Funktion mit OW06.CPP gleich, nur werden folgende
Dateien beigefgt:

  OW1.DEF
  OW1.RC

Das erfordert auch eine Projektdatei OW1.PRJ, die in der Lage ist, diese beiden
Dateien und den Kose in OW1.CPP zu verbinden. Der Projekt-Manager bergibt die
Datei OW1.RC dem Resource-Kompiler der daraus OW1.REF im Binrformat
fertigt; der Linker TLINK verbindet OW1.OBJ und OW1.RES zu OW1.EXE.
 
Einbindung des Menus

Der Resource-Compiler generiert fr das Menu etwa folgenden Kode:

HAUPTMENU MENU
BEGIN
	POPUP "&File"
	BEGIN
		MENUITEM "&Open", 101
		MENUITEM "&Close", 102
		MENUITEM "&Save", 103
		MENUITEM "Save&As", 104
		MENUITEM "&DOS", 105, MENUBREAK
		MENUITEM "&Exit", 106
	END

END

Die '&'-Zeichen kennzeichnen die Aktivierungstaste, die Zahlen sind die
Kommando-Nummern, die die korrespondierende Funktion der betreffenden Fensters
aktiviert. Das Schlsselwort, unter dem dieses Menu gefunden wird, heit
HAUPTMENU und wird im Konstruktor fr TMyWindow eingesetzt.

    AssignMenu("HAUPTMENU");

Die Aktivittsfunktionen bekommen einen sinngemen Namen, und bekommen einen
Dispatch-Index, der der Zahl im Resource-Menu entspricht. Fr unseren einfachen
Versuch wollen wir nur berprfen, ob die Funktion angewhlt wird und
erffnen eine MessageBox.

  virtual void CMFileOpen(RTMessage Msg)
   = [CM_FIRST+101]
  {
    MessageBox(HWindow,"Nicht implementiert","File Open",MB_OK);
  }

Whrend das Menu mit der AssignMenu()-Funktion eingefgt wird,
gibt es fr CURSOR und ICON einen etwas anderen Weg, nmlich ber die
GetWindowClass() Funktion, die in unserem Fenster TMyWindow berladen
wird

  void GetWindowClass(WNDCLASS& NewWin)
  {
    TWindow::GetWindowClass(NewWin);
    NewWin.hCursor=LoadCursor(0,IDC_CROSS);
    NewWin.hIcon = LoadIcon( 0, IDI_HAND );
//    NewWin.hCursor=LoadCursor(GetApplication()->hInstance,"MEINCURSOR");
//    NewWin.hIcon = LoadIcon( GetApplication()->hInstance, "MEINICON" );

  }

Bei der Vernderung von CURSOR und ICON fr dieses Fenster gibt es zwei
Mglichkeiten: Entweder man verwendet vorhandene Resourcen, die sich durch
vordefinierte Konstanten anwhlen lassen oder man verwendet die selbst
konstruierten aus dem Resource-Workshop.
 

Ein Font wird Windows durch die Funktion AddFontResource() bekanntgemacht.
Da das schiefgehen kann, mu man  abfragen, ob das Laden erfolgreich war.
Man erhlt die Anzahl der geladenen Fonts zurck. War das Laden erfolglos,
dann eben 0.

    if (AddFontResource("ow1.fnt"))
    {
      MessageBox(HWindow,"Font geladen","Font",MB_OK);
    }
    else
    {
      MessageBox(HWindow,"Font nicht geladen","Font",MB_OK);
    }


Das Fontladen gibt bei mir einen Fehler, vermutlich ist die Fontdatei
nicht das, was hier erwartet wird.




