home *** CD-ROM | disk | FTP | other *** search
-
-
- Begegnungen mit OS/2, siebte Folge
-
- Wenn der Text nicht in das Fenster paßt ..., Teil 2
-
-
- toolbox & Jürgen Heid
-
- (c) DMV 1990
-
-
-
-
- -- ergänzender Text zum Abdruck in toolbox 10/90
-
- -- zweiter Teil zum Thema Scrolling und Presentation Manager, siehe
- toolbox 9/90; siehe auch Texte und Listings der DATABOX 9/90
-
- -- Listings, auf die sich dieser Text bezieht: Scroll1.c
- Scroll1.def
- Scroll1.h
- Scroll1.rc
- Scroll1.mak
-
- -- Diese Listings liegen auch in compilierter Form auf der
- DATABOX 10/90
- -----------------------------------------------------------------------
-
-
-
-
- 10/90:
- Scrolling eines Textfensters mit dem Presentation Manager war schon in
- der Folge Sechs das Thema. Jetzt erfolgt die Umsetzung in ein C-
- Programm, um endlich von der trockenen Theorie zur Tat zu schreiten.
- Das Programm bewegt mit dem Scrollbar den Fensterinhalt auf und ab. Es
- gibt jeweils aus, welche Aktion am Scrollbar vorgenommen wurde und
- welche Zeilen eines imaginären Textes dann ausgegeben werden. Auf diese
- Weise bekommt man ganz schnell ein "feeling" für das Programmieren von
- Scrollbars: Man sieht sein Ergebnis sofort im Client und kann auch alle
- Sonderfälle über Kommandoargumente oder durch Ändern der Fenstergröße
- simulieren.
-
- Wie bei PM-Programmen üblich besteht das Programm aus zwei Hauptteilen:
- In der "main"-Funktion werden etwaige Kommandoargumente eingelesen, die
- Message-Queue und das Fenster erzeugt und eine Anfangs-Messagebox aus-
- gegeben. Danach läuft hier der Message-Loop des PM-Programms.
-
- Die Fensterprozedur "ClientWindowProc" verarbeitet alle Messages, die
- an den Client gehen und reagiert mit einer entsprechenden Ausgabe.
-
- Durch die Funktion "LiesKommandoArg" wird eingestellt, wie lang und wie
- breit der fiktive Text sein soll. Wenn man einen wirklichen Text
- einliest, bestimmt man Textlänge und Textbreite innerhalb von
- WM_CREATE. Werden keine Kommandoargumente übergeben, wird angenommen,
- man habe einen Text von 100 Zeilen Länge und 200 Spalten Breite. Die
- Textgröße wird in den globalen Variablen "sTextRows" und "sTextCols"
- abgelegt. Diese Werte bleiben während des ganzen Programmablaufs
- unverändert.
-
- In "main" wird auch die Funktion "WndCreate" aufgerufen, die alle für
- die Fenster nötigen Initialisierungen ausführt: die Fensterprozedur
- "ClientWindowProc" wird einer Klasse zugeordnet. Anschließend erzeugt
- WinCreateStdWindow ein Fenster, das gerade zu dieser Klasse gehört.
- Weil im 3. Argument von WinCreateStdWindow die Flaggen FCF_VERTSCROLL
- und FCF_HORZSCROLL gesetzt wurden, sind im Clientfenster gleich zwei
- Scrollbars enthalten.
-
- WinCreateStdWindow gibt den Handle des erzeugten Framefensters zurück:
- ist dieser Wert NULL, ging etwas schief, WndCreate gibt FALSE zurück
- und die "main"-Funktion beendet daraufhin das Programm, ohne den
- Message-Loop zu starten. Die Verarbeitung von WinCreateStdWindow kann
- zum Beispiel schiefgehen, weil man die Flagge für eine Resource setzt,
- die man nicht definiert hat. Dies würde beispielsweise passieren, wenn
- man FCF_ICON setzt, aber keine Ikone in der Resource-Datei definiert.
- -----------------------------------------------------------------------
-
-
-
-
-
- Konzentration auf die wesentlichen Messages
-
- Von den vielen Messages, die an den Client übergeben werden, beschränkt
- sich unser Programm auf die explizite Verarbeitung von sieben:
- WM_CREATE, WM_SIZE, WM_PAINT, WM_COMMAND, WM_CHAR, WM_VSCROLL,
- WM_HSCROLL. Alle anderen werden innerhalb von "ClientWindowProc" als
- default-Fall der PM-Funktion WinDefWindowProc übergeben.
-
- Als allererste Message erhält die Fensterprozedur des Client stets
- WM_CREATE. Sie wird von WinCreateStdWindow generiert. Diese einmalige
- Message nutzt man, um sich Informationen zu beschaffen, die man im
- späteren Programmverlauf braucht. Hier kann man zum Beispiel einen
- Timer starten oder sich Speicherplatz beschaffen. Wenn man FALSE
- zurückgibt, bedeutet dies, daß aus der Sicht des Client alles geklappt
- hat. Taucht ein Problem auf, gibt man TRUE zurück. Dadurch hört der PM
- auf, das Fenster zu erstellen und gibt bei WinCreateStdWindow NULL
- zurück.
-
- Um an die Scrollbars Sliderpositionen zu schicken, benötigen wir deren
- Handles. Diese beschafft man sich in WM_CREATE und übergibt sie den
- globalen Variablen hwndVScroll und hwndHScroll: Wir wissen, daß
- Scrollbars Kinder des Frame sind und daß sie die Fenster-IDs
- FID_VERTSCROLL und FID_HORZSCROLL haben. Somit kann man sich mit
- WinWindowFromID die Scrollbar-Handle beschaffen. Warum dies doch nicht
- so ganz einfach geht, erläutert der Text "Warum ist hwndFrame in
- WM_CREATE noch nicht bekannt" (siehe unten), der wieder einige
- Hintergrundinformationen zum PM enthält.
-
- Außerdem wird innerhalb von WM_CREATE mit Hilfe von GpiQueryFontMetrics
- die große Struktur FONTMETRICS gefüllt, die alle Attribute des
- aktuellen Fonts enthält. Als ersten Parameter braucht
- GpiQueryFontMetrics den Handle des PS. Da man WinBeginPaint nur
- innerhalb von WM_PAINT verwenden kann, muß man sich diesen mit WinGetPS
- holen. Auf die Feinheiten von Fonts gehen wir ein anderes Mal ein.
-
- Hier brauchen wir nur wenige Teile aus der FONTMETRICS-Struktur: diese
- Komponenten weisen wir den globalen Variablen cxChar, cxCaps, cyChar
- und cyDesc zu. Wir benötigen diese Werte, um beim Schreiben zum
- Beispiel genau die y-Koordinaten zu bestimmen.
- -----------------------------------------------------------------------
-
-
-
-
-
- Auswertung einer Scrollbar-Message
-
- Wir behandeln auch dies hier wiederum nur für den vertikalen Scrollbar
- - im Beispielprogramm ist es auch für den horizontalen Scrollbar
- ausgeführt. Wenn der Benutzer den unteren Pfeil klickt, passiert
- folgendes: Der Scrollbar schickt eine WM_VSCROLL-Message ab, die der
- Client erhält. Um zu sehen, wie der Benutzer mit dem Scrollbar agierte,
- muß das Programm die beiden Message-Parameter mp1 und mp2 auswerten.
- Danach kann das Programm über Richtung und Ausmaß des Scrollens
- entscheiden.
-
- Im ersten Message-Parameter mp1 steckt die Fenster-ID des Scrollbars,
- von dem die Message stammt. Dies ist brauchbar, falls man mehr als
- einen vertikalen Scrollbar in einem Fenster hat. Im zweiten Message-
- Parameter mp2 stecken zwei Informationen: Die untere Hälfte enthält die
- aktuelle Sliderposition, die obere Hälfte gibt an, welchen Teil des
- Scrollbars der Benutzer anklickte.
-
- Auch für die Werte in der oberen Hälfte von mp2 hat der PM wieder
- sprechende Konstanten definiert. Sie beginnen alle mit dem Prefix "SB_"
- (für ScrollBar) und sind in der Datei PMWIN.h definiert.
-
- Zwei Konstanten entstehen, wenn der Benutzer den Slider zieht:
- SB_SLIDERTRACK und SB_SLIDERPOSITION. SB_SLIDERTRACK übermittelt
- laufend die aktuelle Sliderposition, SB_SLIDERPOSITION kommt, wenn die
- Sliderbewegung zu Ende ist. Nur bei diesen beiden muß man auch die
- untere Hälfte von mp2 auswerten. Mittels SHORT1FROMMP(mp2) erhält man
- die gegenwärtige Sliderposition; sonst weiß das Programm ja nicht, wie-
- weit der Benutzer den Slider zog. Die letzte Message, SB_ENDSCROLL,
- kommt stets, wenn der Benutzer das Scrollen beendet.
- -----------------------------------------------------------------------
-
-
-
-
- Interpretation der SB-Werte im C-Programm
-
- Mit dem Makro SHORT2FROMMP(mp2) gewinnt man den SB-Wert der WM_VSCROLL-
- Message. Gewöhnlich reagiert man auf den gefundenen SB-Wert dann mit
- einer switch-case-Leiste.
-
- In der globalen Variablen sVPos merkt man sich die Sliderposition. Ihre
- Werte können sich nur zwischen der Unter- und Obergrenze des
- Scrollbereiches bewegen: Die Werte stellen hier Zeilen dar. Wird zum
- Beispiel SB_LINEDOWN in mp2 mitgegeben, muß sVPos um eins erhöht
- werden.
-
- Beim Programmieren von Scrollbars entsteht öfter Verwirrung, weil der
- Benutzer und der Programmierer verschiedene Sichtweisen haben.
- Angenommen der Benutzer sieht zu Beginn die Zeilen 1 bis 10 des Textes
- und will die Zeilen 2 bis 11 sehen. Der Benutzer erreicht dies, indem
- er den unteren Pfeil anklickt. Für den Benutzer bewegt sich das Fenster
- über den Text! Real bewegt sich das Fenster jedoch nicht, bewegt wird
- der Text und zwar nach oben! Um dem Benutzer also immer die Sichtweise
- auf den Text zu simulieren, muß sich dann auch der Slider nach unten
- bewegen. Somit bewegen sich für den Programmierer Text und Slider in
- entgegengesetzte Richtung!
-
- Da im Fenster zuerst die 1. Textzeile angezeigt wurde, hat sVPos den
- Wert 0. Danach wird ganz oben die 2. Textzeile angezeigt: sVPos hat den
- Wert 1 (vorausgesetzt sVStart ist 0).
- -----------------------------------------------------------------------
-
-
-
-
-
-
-
- Seitenweises Blättern
-
- Klickt der Benutzer den Bereich unterhalb des Sliders an, erwartet er,
- daß sich der Text genau um eine Bildschirmseite nach oben bewegt.
-
- Dazu muß man wissen, wieviele Zeilen in das gerade gezeigte Fenster
- passen, egal wie groß das Fenster gerade ist. Deshalb wird bei jeder
- Größenänderung in WM_SIZE die Fensterhöhe in Pixeln der globalen
- Variablen cyClient zugewiesen. In das Fenster passen dann genau
- cyClient/cyChar Zeilen (cyChar war die Höhe einer Zeile in Pixeln für
- den aktuellen Font). Für cyClient=203 und cyChar=20 würden sich also 10
- Zeilen ergeben (Integerdivision). Beim Vorwärtsblättern um eine Seite
- würde sich sVPos also um 10 erhöhen.
- -----------------------------------------------------------------------
-
-
-
- Begrenzung der Sliderposition
-
- Grundsätzlich sollte nur soweit gescrollt werden können, daß die 1.
- Textzeile am oberen Fensterrand und die letzte am unteren Fensterrand
- steht. Andernfalls erhält man nur unnötige Leerzeilen. Zu diesem Zweck
- dienen die globalen Variablen sVStart und sVEnd, die Unter- und
- Obergrenze des Scrollbereiches darstellen. sVStart wird gewöhnlich auf
- 0 gesetzt. Wenn sVPos gleich sVStart ist, steht die 1. Textzeile am
- oberen Fensterrand. Die Variablen sVPos und sVEnd werden ebenfalls
- innerhalb von WM_SIZE gesetzt. Sie hängen jedoch nicht nur von der
- Fensterhöhe ab, sondern auch von der Textlänge.
-
- Nachdem der Client nun weiß, in welcher Absicht der Benutzer mit dem
- Scrollbar agierte, muß überprüft werden, ob die gewünschte Scrollweite
- möglich ist. Es gilt:
- sVStart <= sVPos <= sVEnd.
-
- Diese Bedingung kann man in C einfach programmieren:
-
- if ( sVPos < sVStart )
- sVPos = sVStart;
- else if ( sVPos > sVEnd )
- sVPos = sVEnd;
-
- Danach hat man in WM_VSCROLL einen gültigen Wert für sVPos gefunden.
- Wenn dieser Wert vom bisherigen sVPos-Wert abweicht, muß man zwei Dinge
- tun:
-
- -- dem Scrollbar eine Message schicken, die ihm die neue Sliderposition
- mitteilt und
-
- -- das Slider-Fenster neu zeichnen, wodurch dann der Benutzer annimmt,
- der Text sei gescrollt worden.
-
- Bei WM_HSCROLL ist die Vorgehensweise völlig analog.
- -----------------------------------------------------------------------
-
-
-
- Was tun, wenn sich die Fenstergröße ändert?
-
- Die neuen Fensterausmaße, gemessen in Pixeln, werden in den globalen
- Variablen cyClient und cxClient abgelegt. Da wir die Untergrenze des
- Scrollbereichs und die Textlänge nicht verändern, kann die Änderung der
- Fenstergröße nur die Obergrenze des Scrollbereichs sVEnd beeinflussen.
- Wie Sie schon im letzten Artikel sahen, bedeutet eine Verkleinerung des
- Fensters eine Erhöhung(!) der Obergrenze. Und umgekehrt. Beispielsweise
- hat bei einer Textlänge von 15 Zeilen und einer Fensterhöhe von 10
- Zeilen sVEnd den Wert 5. Verkleinert man das Fenster auf 8 Zeilen, hat
- sVEnd den Wert 7. Es gilt also:
-
- sVEnd = sTextRows - cyClient/cyChar;
-
- Wenn sVStart von 0 verschieden sein kann, muß um sVStart erhöht werden:
-
- sVEnd = sTextRows - cyClient/cyChar + sVStart;
-
- Damit man konsistent bleibt ( sVStart <= sVEnd ), muß man auch noch
- berücksichtigen, daß das Fenster so groß werden kann, daß es den Text
- ganz aufnehmen kann: Bei einer Fensterhöhe von 20 und einer Textlänge
- von 15 würde unsere obige Gleichung für sVEnd einen negativen Wert
- ergeben! Um diesen Sonderfall auch abzudecken, berechnet sich sVEnd
- also letztlich nach folgender Formel:
-
- sVEnd = max(sVStart, sTextRows - cyClient/cyChar + sVStart);
-
- Da bei einer Vergrößerung des Fensters sVEnd kleiner werden könnte als
- der bisherige Wert von sVPos, muß wiederum gewährleistet sein, daß
- gilt:
-
- sVPos = min ( sVPos, sVEnd );
-
- Diese Bedingung greift zum Beispiel im folgenden Fall: Das Fenster faßt
- 10 Zeilen, der Text ist 15 Zeilen lang und die Sliderposition ist 3
- (d.h. man sieht die 4. Textzeile in der obersten Fensterzeile). Wird
- das Fenster auf 20 Zeilen vergrößert, wird sVEnd 0. Dann muß aber auch
- sVPos 0 werden, damit es innerhalb des Scrollbereiches bleibt.
-
- Da nach jedem WM_SIZE vom Presentation Manager automatisch ein WM_PAINT
- erzeugt wird, erscheint dann ein Fenster, in dem der Text in der
- obersten Zeile beginnt. Die letzten 5 Zeilen bleiben frei.
- -----------------------------------------------------------------------
-
-
-
-
- Messages an den Scrollbar
-
- Schon bei der Besprechung von WM_VSCROLL sagten wir, daß man dem
- Scrollbar eine Message schickt, um ihm die endgültige Sliderposition
- mitzuteilen. Wie jedes Control kann auch der Scrollbar Messages
- empfangen. Für diesen Zweck wurde sein Handle in WM_CREATE bestimmt.
-
- Messages an den Scrollbar beginnen mit dem Prefix "SBM".
-
- Auf den ersten Blick mag es verwundern, daß man dem Scrollbar stets die
- Sliderposition zurückschickt. Man muß dies tun, und zwar nicht nur,
- wenn die Bedingung (sVStart <= sVPos <= sVEnd) verletzt wird! Wir
- erhalten vom Scrollbar nur die Information, daß ein bestimmter Bereich
- des Scrollbars angeklickt wurde. Von allein versetzt der Scrollbar
- deshalb seinen Slider aber noch nicht! Selbst wenn der Slider gezogen
- wird, erhält man nur die Information, bis zu welcher Position der
- Slider gezogen wurde. Auch wenn es optisch anders aussieht: ohne
- SBM_SETPOS-Message bleibt nach dem Ziehen der Slider an seiner alten
- Position!
- -----------------------------------------------------------------------
-
-
-
-
-
- Woran erkennt der Slider, wieweit er sich bewegen muß?
-
- Wenn der Scrollbar die Message über seine neue Position erhält, woher
- weiß er dann, ob er den Slider um 1/3 oder um 1/10 der Scrollbarlänge
- bewegen muß? Dazu wird dem Scrollbar sein Scrollbereich ebenfalls
- bekannt gemacht (standardmäßig, d.h. ohne explizites Setzen, beträgt
- der Scrollbereich 0...100).
-
- Wie Sie gerade sahen, wurde die Scrollbereichsgrenze innerhalb von
- WM_SIZE berechnet. Also schickt man auch hier sinnvollerweise die
- aktuellen Scrollbereichsgrenzen an den Scrollbar. (Wenn in einem
- Programm die Fenstergröße konstant bleibt, könnte man dies auch
- innerhalb von WM_CREATE tun.)
-
- Die Message an den Scrollbar heißt SBM_SETSCROLLBAR: damit kann man
- Scrollbereich und Sliderposition gleichzeitig setzen.
-
- Generell gilt, daß die Untergrenze des Scrollbereiches nicht negativ
- sein darf. Auch in der LiesKommandoArg-Funktion ist berücksichtigt, daß
- das 3. oder 4. Argument nicht negativ sein darf.
-
- Gehen wir einmal kurz von folgendem aus: der Text hat 100 Zeilen, die
- Scrollbereichsuntergrenze ist 0 (dies ist auch die Standardeinstellung,
- falls in dem "scroll1"-Programm keine Argumente angegeben werden) und
- die aktuelle Fensterhöhe ist 20. Dann beträgt die
- Scrollbereichsobergrenze 100 - 20 + 0 = 80. Bei einer Scrollbe-
- reichsuntergrenze von 1 hat die Obergrenze den Wert 100 - 20 + 1 = 81.
-
- Wenn man, wie es naiverweise oft geschieht, vergißt, die Fensterhöhe
- abzuziehen, wird die Scrollbereichsobergrenze zu groß und man kann
- soweit nach unten scrollen, daß man hinter dem Text noch eine ganze
- leere Bildschirmseite erhält.
-
- Wenn man nun innerhalb von WM_SIZE dem Scrollbar seinen Scrollbereich
- richtig mitgeteilt hat, weiß der Scrollbar, wieviele Positionen der
- Slider annehmen kann: falls der Scrollbereich zum Beispiel (0, 2)
- beträgt, kann der Slider 3 verschiedene Positionen annehmen. Bei einem
- Bereich von (0, 80) kann er 81 verschiedene Positionen annehmen.
- Die Sliderstellung bestimmt sich aus dem Verhältnis der Sliderposition
- zur Anzahl der verschiedenen Positionen. Deshalb ergibt sich auch: Beim
- Verkleinern des Fensters wird der Scrollbereich größer und der Slider
- wandert optisch relativ zur Scrollbarlänge nach oben, obwohl die
- Sliderposition dieselbe blieb.
-
- Als letztes wird in WM_SIZE noch geklärt, ob man den Slider überhaupt
- anzeigt. Wenn der Text ganz in das Fenster paßt, gilt sVEnd = sVStart.
- In diesem Fall wird der Scrollbar "disabled", indem man
- "WinEnableWindow" - mit dem 2. Argument auf FALSE gesetzt - anwendet.
- Wenn man einen disabled Scrollbar anklickt, piepst dieser. Er schickt
- dann auch keine Notification-Message ab.
- -----------------------------------------------------------------------
-
-
-
-
-
- Könnte man noch mehr optimieren?
-
- WM_SIZE-Messages werden vom PM nur geschickt, wenn sich die
- Fenstergröße ändert. Wir könnten auch hier wie bei WM_VSCROLL und
- WM_HSCROLL abfragen, ob die Änderung der Fenstergröße so groß ist, daß
- sich Sliderposition und Scrollbereich wirklich ändern - und nur dann
- eine Message an den Scrollbar schicken. Da dies aber der Normalfall
- ist, läßt man diese Abfrage weg.
- -----------------------------------------------------------------------
-
-
-
-
- Was wird angezeigt?
-
- Bisher haben wir uns so sehr um den Weg der Messages gekümmert, daß wir
- die richtige Ausgabe einfach voraussetzten.
-
- Wir wollen von unserem fiktiven Text nur die Zeilennummern ausgeben.
- Wenn man scrollt, bewegen sich die Zeilennummern.
-
- Da wir mit den Kommandoargumenten 3 und 4 die
- Scrollbereichsuntergrenzen sVStart und sHStart verändern können, können
- wir deren Einfluß auch direkt sehen. Die erste ausgegebene Nummer ist
- sVStart, die letzte ist sVStart + Textlänge - 1. Aber selbst wenn bei
- sVStart=10 in der allerersten Zeile 10 steht, ist dies doch die erste
- Textzeile. Die ausgegebenen Zahlen stimmen also nur dann mit den
- eigentlichen Textzeilennummern (ab 1 beginnend zu zählen) überein, wenn
- man für sVStart 1 eingibt.
-
- Real wird man sVStart nicht verändern, sondern auf 0 lassen. Dann hat
- die 1. Textzeile die Nummer 0. Dies ist auch unsere
- Standardeinstellung, wenn man die Argumente 3 und 4 wegläßt.
-
- Jedesmal, wenn eine Scroll-Message die Slider-Position veränderte, wird
- auch eine globale Flagge (bVertScroll oder bHorzScroll) gesetzt und das
- Fenster durch WinInvalidateRect für "ungültig" erklärt, was zu einem
- WM_PAINT führt. Sitzt nun eine dieser Flaggen bei Ausführung von
- WM_PAINT, wird "Display Scroll" aufgerufen (wird WM_PAINT ausgeführt,
- weil zum Beispiel ein anderes Fenster unser Fenster verdeckte, so
- sitzen diese Flaggen nicht). "Display Scroll" gibt uns im Client die
- internen Informationen beim Scrollen aus: die Art der Scroll-Message
- (= SB-WERT), die Slider-Position und den Scrollbereich (hier vertikale
- oder horizontale "Ausdehnung" genannt).
-
- Als Besonderheit werden die Clientmaße berücksichtigt: passen diese
- Informationen nicht in den Client, weil er zu klein ist, wird dieselbe
- Message in einer Messagebox ausgegeben: den SB-Wert verwenden wir
- hierbei als Titel der Box! Anschließend setzt "Display Scroll" die
- globale Flagge wieder auf 0 zurück.
- -----------------------------------------------------------------------
-
-
-
-
- Wie werden die Textzeilen ausgegeben?
-
- Die exakte Ausgabe der fiktiven Zeilennummern ist nicht ganz trivial.
- GpiCharStringAt erwartet als Parameter einen String: also muß man die
- Zeilennummer mit der itoa-Funktion in einen String umwandeln. Außerdem
- braucht man die Koordinaten der Grundlinie des ersten Zeichens von
- jeder Ausgabezeile. Diese übergeben wir der POINTL-Variablen ptl:
-
- ptl.x = ( 8 - sHPos ) * cxCaps;
- ptl.y = cyClient - (i + 1 - sVPos) * cyChar + cyDesc;
-
- -- Die x-Koordinate ist abhängig von sHPos. Steht der waagrechte
- Slider ganz links, ist sHPos 0. Dann wird mit der Breite von 8
- Zeichen eingerückt (cxCaps ist die durchschnittliche Breite der
- Großbuchstaben des gewählten Fonts). Je weiter der Slider nach
- rechts verschoben wird, desto mehr wandern die Zeilennummern nach
- links. Wenn sHPos > 8 ist, sieht man von den Zahlen nichts mehr
- (dies ist zu beachten bei der Wahl des 4. Kommandoarguments!).
-
- -- Bei der y-Koordinate muß man beachten, daß beim PM alle Angaben
- bezüglich der linken unteren Ecke des Clients erfolgen. Die linke
- obere Ecke hat also die Koordinaten ( 0, cyClient ). Will man Text
- in die oberste Zeile setzen, muß man wissen, daß als y-Koordinate
- stets die Höhe der Zeichen-Grundlinie anzugeben ist. Die Grundlinie
- der obersten Zeile liegt bei: cyClient - cyChar + cyDesc. Die
- Grundlinie der zweitobersten Zeile liegt bei: cyClient - 2*cyChar +
- cyDesc.
- cyChar ist die Höhe von 1 Zeile unseres Fonts, cyDesc ist der
- Abstand zwischen Grundlinie und dem was unterhalb davon steht (zum
- Beispiel beim "g"). In der obigen Gleichung ist "i" die Zeilennummer.
-
- Mit der for-Schleife erreicht man, daß nur die Zeilen, die in das
- Fenster passen, ausgegeben werden: dies sind bei einer Fensterhöhe von
- 10 Zeilen genau 10 Aufrufe von GpiCharStringAt, wobei die
- Schleifenvariable von sVPos bis 10 - 1 + sVPos läuft.
-
- Man könnte "i" statt von sVPos auch stets bei 0 beginnen lassen: auf
- dem Schirm erhielte man dasselbe Ergebnis, weil man für i < sVPos eine
- y-Koordinate errechnen würde, die oberhalb des Clients liegt. Alle
- Ausgaben auf den Presentation Space, die nicht innerhalb vom Client
- liegen, werden einfach geklippt. Der Nachteil bei dieser Vereinfachung:
- man verschwendet CPU-Zeit. Beispiel: die Sliderposition sVPos steht auf
- 3: Dann werden für die Zeilen 0, 1 und 2 auch keine Aufrufe von
- GpiCharStringAt erfolgen.
-
- sVPaintEnd hat den Index der letzten Zeile, die im Fenster ausgegeben
- wird: für sVPos = 0 ergibt sich:
-
-
- sVPaintBeg = 0;
- sVPaintEnd = Fensterhöhe - 1;
-
- Falls der gesamte Text jedoch in das Fenster paßt, dürfen nur maximal
- sTextRows ( = Textlänge ) Zeilen ausgegeben werden: der Index läuft
- dann von 0 bis sTextRows - 1.
-
- Außer dieser allgemeinen Ausgabe der Zeilennummern des fiktiven Textes
- verursachen die Informationen zur Scroll-Message ein generelles
- Problem: Die Ausgabe dieser Informationen in "DisplayScroll" soll nur
- beim 1. WM_PAINT nach Erhalt einer Scroll-Message (WM_VSCROLL oder
- WM_HSCROLL) erscheinen. Bei WM_PAINT-Messages, die andere Ursachen
- haben, soll diese Information nicht mehr angezeigt werden - auch nicht
- Teile davon.
-
- Wenn ein anderes Fenster nur Teile der Scroll-Informationen verdeckte,
- wird anschließend nur der verdeckte Teil neu gezeichnet ("Update
- Region") - in der Hintergrundfarbe rot. Daß dann Reste der
- DisplayScroll-Ausgabe stehenbleiben, sieht komisch aus. Der Grund dafür
- ist sinnvoll: der PM zeichnet stets nur in der Update-Region. Wenn
- Fensterteile ungültig werden, muß in WM_PAINT alle Information
- vorhanden sein, diese neu zu zeichnen. Der PM geht davon aus, was nicht
- "ungültig" ist, sieht aus wie vorher. Da dies bei uns nicht der Fall
- ist, muß hier stets zu Beginn von WM_PAINT durch WinInvalidateRect die
- Update-Region auf den gesamten Client ausgeweitet werden ( 2. Parameter
- ist 0! ).
-
- Genaueres zum Zeichnen beim PM (Einschränkungen in der Wirkung von Win-
- FillRect, ...) steht im Text "Besonderheiten beim WM_PAINT -
- Ausgabe im PM" (siehe unten).
- -----------------------------------------------------------------------
-
-
-
-
- Wie kann man mit der Tastatur scrollen?
-
- Wir wollen, daß auch die Cursortasten zu einem Scrollen führen: d.h.
- der Scrollbar versetzt seinen Slider und der Client scrollt den Text.
-
- Man kann dies ganz einfach erreichen! Tastatureingaben gehen stets an
- das Fenster, das den Input-Focus hat. Dies ist unser Client-Fenster.
- Jede Tastatur-Eingabe erzeugt eine WM_CHAR-Message.
-
- Wenn in dem 2. Messageparameter die CHARMSG-Komponente "vkey" eine
- Cursortaste darstellt, kann man die gesamte Message samt ihren
- Messageparametern an den Scrollbar schicken. Die interne
- Fensterprozedur des Scrollbar versteht solche WM_CHAR-Messages. (Jedoch
- darf man nicht alle(!) WM_CHAR-Messages unausgewählt an beide(!)
- Scrollbars schicken, weil es sonst zu Mißverständnissen kommt.)
-
- Auf eine solche Message hin reagiert der Scrollbar so, als wäre der
- entsprechende Scrollbarteil angeklickt worden. Er schickt dem Client
- eine Scroll-Message zurück. Danach schickt der Client eine SBM_SETPOS-
- Message. In diesem Fall muß der Client also zweimal und nicht nur
- einmal Nachrichten an den Scrollbar schicken.
-
- Ein Unterschied zwischen Tastatureingabe und Klicken auf den Scrollbar
- ergibt sich nur, wenn der Scrollbar disabled ist. Beim Klicken schickt
- er dann keine Scroll-Message. Bei Tastatureingabe schickt er eine
- Scroll-Message: da diese dann einen unzulässigen sVPos-Wert enthält,
- wird dieser innerhalb der Scroll-Message wieder zurechtgerückt:
- beispielsweise könnten sVStart = sVEnd = 0 sein. Die Taste Pagedown
- erzeugt eine WM_VSCROLL-Message: sVPos darf trotzdem nicht auf +22
- gesetzt werden! Hier wird in einer zukünftigen PM-Version sicher auch
- noch optimiert werden!
- -----------------------------------------------------------------------
-
-
-
-
-
- Informationen zum laufenden Programm
-
- Wenn man einen Menüeintrag selektiert, wird eine WM_COMMAND-Message
- erzeugt. Unser Actionbar hat nur 1 Eintrag: "Exit und Info". In dessen
- Untermenü stehen 3 selektierbare Einträge: "Exit Scroll1" beendet
- ebenso wie F3 das Programm! "Resume" klappt das Untermenü wieder hoch
- und "Programm-Information..." gibt in einer Message-Box ein paar
- Informationen zum Programm aus.
-
- In der globalen Variablen "szBoxTitel" steht der Titel der Box, der
- innerhalb der Funktion "AusgabeBox" verwendet wird. Ebenfalls in dieser
- Funktion verwendet wird für den Text innerhalb der Box die globale
- Variable "szBoxText". Diese wird mit sprintf gefüllt. Der Formatstring
- steht in der Variablen "apchFormatTable[0]". Gefüllt wird der
- Formatstring stets mit den aktuellen Werten für Textlänge, Textbreite,
- vertikale und horizontale Scrollbereichsuntergrenze (genau diese Werte
- sind als Kommandoargumente veränderbar).
-
- Wir haben den Weg über den globalen Formatstring "apchFormatTable" ge-
- wählt, damit alle längeren Ausgabetexte an 1 Stelle gesammelt sind.
- Dadurch wird das eigentliche Programm übersichtlicher.
- -----------------------------------------------------------------------
-
-
-
-
- Wann wird die Scroll-Information in einer Message-Box ausgegeben?
-
- Nun noch eine kurze Anmerkung zur Funktion "DisplayScroll": DisplaySc-
- roll wird aufgerufen mit den Koordinaten des Client-Rechtecks als
- aktuellen Parametern. Dabei gilt:
-
- pRectl->yTop = cyClient;
- pRectl->xRight = cxClient;
- pRectl->xLeft = pRectl->yBottom = 0;
-
- Man übergibt diese RECTL-Struktur statt zweier Ausdehnungen cxClient
- und cyClient, weil innerhalb von DisplayScroll ebenfalls mit dieser
- RECTL-Struktur gearbeitet wird.
-
- DisplayScroll prüft, ob der Ausgabetext mit den Scroll-Informationen
- noch in das Clientfenster paßt. Die Prüfung ist ganz einfach:
- pRectl->yTop muß größer als 4 Zeilen sein und die Breiten von Titel und
- Text dürfen nicht größer als pRectl->xRight sein. Die Ausdehnung in
- Pixeln eines gegebenen Textes erhält man mit GpiQueryTextBox.
-
- Falls der Text in den Client paßt, wird unterschieden, ob der
- Scrollevent vom vertikalen oder vom horizontalen Scrollbar kam. Kam er
- vom vertikalen Scrollbar erfolgt die Ausgabe mit WinDrawText
- rechtsbündig und vertikal zentriert. (Da für Titel und Text jeweils
- getrennt WinDrawText aufgerufen wird, sieht es etwas komplizierter aus
- als es ist). Wenn der Scrollevent vom horizontalen Scrollbar kam,
- erfolgt die Ausgabe unten und horizontal zentriert (auch wieder mit 2
- getrennten WinDrawText-Aufrufen).
-
- Geht der Text nicht in den Client, wird die Flagge "bMsgBox" auf TRUE
- gesetzt und, abhängig von welchem Scrollbar der Event kam, szBoxText
- gefüllt. Dann wird die Funktion AusgabeBox aufgerufen (szBoxTitel wurde
- schon innerhalb von WM_SCROLL bzw. WM_HSCROLL gefüllt).
-
-
-
-
-
- -----------------------------------------------------------------------
- -----------------------------------------------------------------------
-
-
-
-
-
- "Warum ist hwndFrame in WM_CREATE noch nicht bekannt?"
-
- Innerhalb von WM_CREATE möchte man sich mit WinWindowFromID den Handle
- des vertikalen Scrollbars beschaffen. WinWindowFromID hat 2 Argumente:
- den Handle des Vaters und die ID des gesuchten Fensters. Der gesuchte
- vertikale Scrollbar hat die Fenster-ID FID_VERTSCROLL. Der Vater des
- Scrollbars ist der Frame mit dem Handle hwndFrame.
-
- Also könnte man versuchen, sich hwndVScroll auf folgende Weise zu be-
- schaffen:
- hwndVScroll = WinWindowFromID ( hwndFrame, FID_VERTSCROLL );
-
- Dieser Aufruf geht leider schief!!!
-
- Hier ist wieder ein Punkt, wo in anderen PM-Publikationen stillschwei-
- gend die Lösung angeboten wird -- ohne auch nur ein Wort darüber zu
- verlieren, warum der direkte Weg nicht geht. Jeder soll akzeptieren,
- weil alle denselben Weg gehen! Aber wie soll jemand, der den PM lernen
- will, verstehen, wenn keine Gründe genannt werden?
-
- Zuerst einmal die Lösung: da wir innerhalb der Fensterprozedur des
- Client sind, enthält "hwnd" dessen Handle. Wenn man den ,,fHandle eines
- Fensters hat, kann man den Handle eines dazu in Beziehung stehenden
- Fensters mit WinQueryWindow erhalten: das 1. Argument ist der gegebene
- Handle, das 2. Argument enthält die Beziehung in Form einer Konstanten
- (die QW-Konstanten sind in PMWIN.h definiert). Der 3. Parameter ist für
- uns i.M. ohne Bedeutung. Den Handle des Vaters von "hwnd" liefert also
-
- WinQueryWindow( hwnd, QW_PARENT, ,,l FALSE );
-
- Somit kann man mit dem Tandem WinWindowFromID und WinQueryWindow den
- Handle hwndVScroll bestimmen:
-
- hwndVScroll = WinWindowFromID( WinQueryWindow( hwnd, QW_PARENT,
- FALSE ), FID_VERTSCROLL );
-
- Warum aber geht der Aufruf von WinWindowFromID mit der globalen Varia-
- blen hwndFrame als 1. Argument schief, obwohl WinQueryWindow doch genau
- den Wert von hwndFrame liefert?
-
- Der Grund liegt darin, daß zu dem Zeitpunkt, wo WM_CREATE abgearbeitet
- wird, die globale Variable hwndFrame noch nicht gefüllt ist. Auf den 1.
- Blick erscheint dies unmöglich: In der main-Routine (innerhalb von
- WndCreate) wird die globale Variable hwndFrame als Rückgabewert von
- WinCreateStdWindow gefüllt und erst danach wird im Message-Loop von
- WinGetMsg die 1. Message aus der Anwendungs-Queue geholt!
- Aber genau hier stößt man wieder auf nicht-dokumentierte PM-Interna:
- Welche Messages werden von welchen PM-Befehlen direkt oder indirekt
- angestoßen? WinGetMsg kann nur solche Messages aus der Anwendungs-Queue
- holen, die hineingestellt wurden ("posting"). Die ersten 16 (die genaue
- Zahl variert von PM-Version zu PM-Version) Messages, die der Client
- erhält, gelangen aber direkt (durch "Senden") in die Client-Fensterpro-
- zedur (zum Beispiel WM_CREATE, WM_ACTIVATE, WM_SIZE).
-
- Die Fensterprozedur "ClientWindowProc" wird nämlich mehrfach direkt wie
- eine Funktion innerhalb der Verarbeitung von WinCreateStdWindow aufge-
- rufen! Da dies passiert, bevor WinCreateStdWindow hwndFrame zurückgibt,
- ist hwndFrame innerhalb von WM_CREATE auch nicht gefüllt (hat als glo-
- bale Variable also noch den Wert NULL).
-
- -----------------------------------------------------------------------
-
-
-
-
-
-
-
-
- "Besonderheiten beim WM_PAINT - Ausgabe im PM"
-
- Der Presentation Manager hat einen internen Speicherbereich, genannt
- Presentation Space (PS), in den die Ausgabe erfolgt. Da der Standard-PS
- mit dem Bildschirm verbunden ist, erscheint sein Inhalt zum Beispiel in
- einem Fenster auf dem Schirm. Den Handle des Standard-PS erhält man
- nach WM_PAINT durch
-
- hps = WinBeginPaint( hwnd, NULL, &rectl );
-
- Zwischen diesem Aufruf und
-
- WinEndPaint( hps );
-
- müssen die Ausgabefunktionen (Gpi-Funktionen) liegen.
-
- Die Fensterprozedur des Client wird jedesmal mit einem WM_PAINT aufge-
- rufen, wenn Teile des Clients neu gezeichnet werden sollen. Fenstertei-
- le müssen neu gezeichnet werden, wenn sie "ungültig" (invalid) sind.
- Die ungültigen Bereiche nennt man "Update Region". Ungültig wird ein
- Fensterteil dann, wenn er vorher von einem anderen Fenster verdeckt war
- und nun in den Vordergrund tritt. Invalidieren kann man Fensterteile
- aber auch dadurch, daß man sie explizit als ungültig erklärt: durch den
- Aufruf von
-
- WinInvalidateRect( hwnd, &rectl, fInclChildren );
-
- Mit dem 2. Argument kann man den Bereich innerhalb des Fensters hwnd
- angeben, der als nicht mehr gültig erklärt werden soll. WinInvalidate-
- Rect erzeugt dann ein WM_PAINT, das sofort ausgeführt wird.
-
- Betrachten Sie kurz folgenden Programmausschnitt:
-
- case WM_PAINT:
- Color = (Color==CLR_BLUE) ? CLR_RED : CLR_BLUE;
- hps = WinBeginPaint( hwnd, NULL, &rectl );
- WinFillRect( hps, &rectl, Color );
- WinEndPaint( hps );
-
- Bei jedem Aufruf von WM_PAINT wird die Update-Region mit einer anderen
- Farbe gefüllt: Ist das Fenster blau, wird die Update-Region danach rot
- und umgekehrt. So kann man genau sehen, welche Teile eines Fensters
- verdeckt waren.
-
- Wenn man jetzt vor das WinFillRect den Aufruf
-
- WinQueryWindowRect( hwnd, &rectl );
-
- stellt, wird rectl mit den Koordinaten des ganzen Clients gefüllt. Man
- erwartet auf Anhieb, daß das folgende WinFillRect mit diesem neuen Wert
- von rectl arbeitet: bei jedem WM_PAINT sollte also das ganze Fenster
- die Farbe wechseln und nicht nur der gerade wieder sichtbar gewordene
- Teil. ABER: nur die Update-Region erhält die neue Farbe, der Rest von
- rectl wird geklippt.
-
- Auch bei GpiErase oder WinDrawText ist es genau dasselbe: bei dem ff.
- Codeteil
- case WM_PAINT:
- Color = (Color==CLR_BLUE) ? CLR_RED : CLR_BLUE;
- hps = WinBeginPaint( hwnd, NULL, &rectl );
- WinQueryWindowRect( hwnd, &rectl );
- WinDrawText( hps, -1,
- szText,
- &rectl,
- CLR_BLACK,
- Color,
- DT_CENTER | DT_VCENTER );
- WinEndPaint( hps );
-
- ändert sich durch "Color" nur die Hintergrundfarbe der Update-Region!
- "rectl" dient also nur dazu, bei der Zentrierung des Textes die Grenzen
- abzustecken.
-
- Wie kann man es nun aber unabhängig von der gegebenen Update-Region
- schaffen, daß bei jedem WM_PAINT stets das ,,fganze Fenster neu ge-
- zeichnet wird? Die Lösung steckt im zusätzlichen Aufruf WinInvalidate-
- Rect noch vor WinBeginPaint.
-
- case WM_PAINT:
- Color = (Color==CLR_BLUE) ? CLR_RED : CLR_BLUE;
- WinInvalidateRect( hwnd, NULL, FALSE );
- hps = WinBeginPaint( hwnd, NULL, &rectl );
- WinFillRect( hps, &rectl, Color );
- WinEndPaint( hps );
-
- Nun wird bei jedem WM_PAINT stets der ganze Client neu gezeichnet. Dies
- ist immer nötig, wenn sich auch Teile ändern sollen, die nicht in der
- Update-Region liegen. Es kommt hierbei nicht zu einer doppelten Ausfüh-
- rung von WM_PAINT, obwohl WinInvalidateRect normalerweise ein WM_PAINT
- generiert. Der PM optimiert: es wird nur die Update-Region verändert.
-
- Das häufig benutzte WinQueryWindowRect kann also bestenfalls dazu die-
- nen, in WinDrawText den Text zu zentrieren oder um Teile der Update-
- Region zu erneuern. Nie aber kann man damit Teile außerhalb der Update-
- Region neu zeichnen: Jeder graphische Output darüberhinaus wird an der
- Update-Region geklippt.
-
-
- -----------------------------------------------------------------------
-
-
- --- ENDE Begleittext zu 10/90 ---