home *** CD-ROM | disk | FTP | other *** search
/ Turbo Toolbox / Turbo_Toolbox.iso / 1990 / 10 / grdlagen / os2_1090.asc next >
Encoding:
Text File  |  1990-07-17  |  35.4 KB  |  799 lines

  1.  
  2.  
  3.                  Begegnungen mit OS/2, siebte Folge
  4.  
  5.            Wenn der Text nicht in das Fenster paßt ..., Teil 2
  6.  
  7.  
  8.                       toolbox & Jürgen Heid
  9.  
  10.                           (c) DMV 1990
  11.  
  12.  
  13.  
  14.  
  15. --   ergänzender Text zum Abdruck in toolbox 10/90
  16.  
  17. --   zweiter Teil zum Thema Scrolling und Presentation Manager, siehe
  18.      toolbox 9/90; siehe auch Texte und Listings der DATABOX 9/90
  19.  
  20. --   Listings, auf die sich dieser Text bezieht: Scroll1.c
  21.                                                  Scroll1.def
  22.                                                  Scroll1.h
  23.                                                  Scroll1.rc
  24.                                                  Scroll1.mak
  25.  
  26. --   Diese Listings liegen auch in compilierter Form auf der
  27.      DATABOX 10/90
  28. -----------------------------------------------------------------------
  29.  
  30.  
  31.  
  32.  
  33. 10/90:
  34. Scrolling eines Textfensters mit dem Presentation Manager war schon in
  35. der Folge Sechs das Thema. Jetzt erfolgt die Umsetzung in ein C-
  36. Programm, um endlich von der trockenen Theorie zur Tat zu schreiten.
  37. Das Programm bewegt mit dem Scrollbar den Fensterinhalt auf und ab. Es
  38. gibt jeweils aus, welche Aktion am Scrollbar vorgenommen wurde und
  39. welche Zeilen eines imaginären Textes dann ausgegeben werden. Auf diese
  40. Weise bekommt man ganz schnell ein "feeling" für das Programmieren von
  41. Scrollbars: Man sieht sein Ergebnis sofort im Client und kann auch alle
  42. Sonderfälle über Kommandoargumente oder durch Ändern der Fenstergröße
  43. simulieren.
  44.  
  45. Wie bei PM-Programmen üblich besteht das Programm aus zwei Hauptteilen:
  46. In der "main"-Funktion werden etwaige Kommandoargumente eingelesen, die
  47. Message-Queue und das Fenster erzeugt und eine Anfangs-Messagebox aus-
  48. gegeben. Danach läuft hier der Message-Loop des PM-Programms.
  49.  
  50. Die Fensterprozedur "ClientWindowProc" verarbeitet alle Messages, die
  51. an den Client gehen und reagiert mit einer entsprechenden Ausgabe.
  52.  
  53. Durch die Funktion "LiesKommandoArg" wird eingestellt, wie lang und wie
  54. breit der fiktive Text sein soll. Wenn man einen wirklichen Text
  55. einliest, bestimmt man Textlänge und Textbreite innerhalb von
  56. WM_CREATE. Werden keine Kommandoargumente übergeben, wird angenommen,
  57. man habe einen Text von 100 Zeilen Länge und 200 Spalten Breite. Die
  58. Textgröße wird in den globalen Variablen "sTextRows" und "sTextCols"
  59. abgelegt. Diese Werte bleiben während des ganzen Programmablaufs
  60. unverändert.
  61.  
  62. In "main" wird auch die Funktion "WndCreate" aufgerufen, die alle für
  63. die Fenster nötigen Initialisierungen ausführt: die Fensterprozedur
  64. "ClientWindowProc" wird einer Klasse zugeordnet. Anschließend erzeugt
  65. WinCreateStdWindow ein Fenster, das gerade zu dieser Klasse gehört.
  66. Weil im 3. Argument von WinCreateStdWindow die Flaggen FCF_VERTSCROLL
  67. und FCF_HORZSCROLL gesetzt wurden, sind im Clientfenster gleich zwei
  68. Scrollbars enthalten.
  69.  
  70. WinCreateStdWindow gibt den Handle des erzeugten Framefensters zurück:
  71. ist dieser Wert NULL, ging etwas schief, WndCreate gibt FALSE zurück
  72. und die "main"-Funktion beendet daraufhin das Programm, ohne den
  73. Message-Loop zu starten. Die Verarbeitung von WinCreateStdWindow kann
  74. zum Beispiel schiefgehen, weil man die Flagge für eine Resource setzt,
  75. die man nicht definiert hat. Dies würde beispielsweise passieren, wenn
  76. man FCF_ICON setzt, aber keine Ikone in der Resource-Datei definiert.
  77. -----------------------------------------------------------------------
  78.  
  79.  
  80.  
  81.  
  82.  
  83.             Konzentration auf die wesentlichen Messages
  84.  
  85. Von den vielen Messages, die an den Client übergeben werden, beschränkt
  86. sich unser Programm auf die explizite Verarbeitung von sieben:
  87. WM_CREATE, WM_SIZE, WM_PAINT, WM_COMMAND, WM_CHAR, WM_VSCROLL,
  88. WM_HSCROLL. Alle anderen werden innerhalb von "ClientWindowProc" als
  89. default-Fall der PM-Funktion WinDefWindowProc übergeben.
  90.  
  91. Als allererste Message erhält die Fensterprozedur des Client stets
  92. WM_CREATE. Sie wird von WinCreateStdWindow generiert. Diese einmalige
  93. Message nutzt man, um sich Informationen zu beschaffen, die man im
  94. späteren Programmverlauf braucht. Hier kann man zum Beispiel einen
  95. Timer starten oder sich Speicherplatz beschaffen. Wenn man FALSE
  96. zurückgibt, bedeutet dies, daß aus der Sicht des Client alles geklappt
  97. hat. Taucht ein Problem auf, gibt man TRUE zurück. Dadurch hört der PM
  98. auf, das Fenster zu erstellen und gibt bei WinCreateStdWindow NULL
  99. zurück.
  100.  
  101. Um an die Scrollbars Sliderpositionen zu schicken, benötigen wir deren
  102. Handles. Diese beschafft man sich in WM_CREATE und übergibt sie den
  103. globalen Variablen hwndVScroll und hwndHScroll: Wir wissen, daß
  104. Scrollbars Kinder des Frame sind und daß sie die Fenster-IDs
  105. FID_VERTSCROLL und FID_HORZSCROLL haben. Somit kann man sich mit
  106. WinWindowFromID die Scrollbar-Handle beschaffen. Warum dies doch nicht
  107. so ganz einfach geht, erläutert der Text "Warum ist hwndFrame in
  108. WM_CREATE noch nicht bekannt" (siehe unten), der wieder einige
  109. Hintergrundinformationen zum PM enthält.
  110.  
  111. Außerdem wird innerhalb von WM_CREATE mit Hilfe von GpiQueryFontMetrics
  112. die große Struktur FONTMETRICS gefüllt, die alle Attribute des
  113. aktuellen Fonts enthält. Als ersten Parameter braucht
  114. GpiQueryFontMetrics den Handle des PS. Da man WinBeginPaint nur
  115. innerhalb von WM_PAINT verwenden kann, muß man sich diesen mit WinGetPS
  116. holen. Auf die Feinheiten von Fonts gehen wir ein anderes Mal ein.
  117.  
  118. Hier brauchen wir nur wenige Teile aus der FONTMETRICS-Struktur: diese
  119. Komponenten weisen wir den globalen Variablen cxChar, cxCaps, cyChar
  120. und cyDesc zu. Wir benötigen diese Werte, um beim Schreiben zum
  121. Beispiel genau die y-Koordinaten zu bestimmen.
  122. -----------------------------------------------------------------------
  123.  
  124.  
  125.  
  126.  
  127.  
  128.                 Auswertung einer Scrollbar-Message
  129.  
  130. Wir behandeln auch dies hier wiederum nur für den vertikalen Scrollbar
  131. - im Beispielprogramm ist es auch für den horizontalen Scrollbar
  132. ausgeführt. Wenn der Benutzer den unteren Pfeil klickt, passiert
  133. folgendes: Der Scrollbar schickt eine WM_VSCROLL-Message ab, die der
  134. Client erhält. Um zu sehen, wie der Benutzer mit dem Scrollbar agierte,
  135. muß das Programm die beiden Message-Parameter mp1 und mp2 auswerten.
  136. Danach kann das Programm über Richtung und Ausmaß des Scrollens
  137. entscheiden.
  138.  
  139. Im ersten Message-Parameter mp1 steckt die Fenster-ID des Scrollbars,
  140. von dem die Message stammt. Dies ist brauchbar, falls man mehr als
  141. einen vertikalen Scrollbar in einem Fenster hat. Im zweiten Message-
  142. Parameter mp2 stecken zwei Informationen: Die untere Hälfte enthält die
  143. aktuelle Sliderposition, die obere Hälfte gibt an, welchen Teil des
  144. Scrollbars der Benutzer anklickte.
  145.  
  146. Auch für die Werte in der oberen Hälfte von mp2 hat der PM wieder
  147. sprechende Konstanten definiert. Sie beginnen alle mit dem Prefix "SB_"
  148. (für ScrollBar) und sind in der Datei PMWIN.h definiert.
  149.  
  150. Zwei Konstanten entstehen, wenn der Benutzer den Slider zieht:
  151. SB_SLIDERTRACK und SB_SLIDERPOSITION. SB_SLIDERTRACK übermittelt
  152. laufend die aktuelle Sliderposition, SB_SLIDERPOSITION kommt, wenn die
  153. Sliderbewegung zu Ende ist. Nur bei diesen beiden muß man auch die
  154. untere Hälfte von mp2 auswerten. Mittels SHORT1FROMMP(mp2) erhält man
  155. die gegenwärtige Sliderposition; sonst weiß das Programm ja nicht, wie-
  156. weit der Benutzer den Slider zog. Die letzte Message, SB_ENDSCROLL,
  157. kommt stets, wenn der Benutzer das Scrollen beendet.
  158. -----------------------------------------------------------------------
  159.  
  160.  
  161.  
  162.  
  163.                 Interpretation der SB-Werte im C-Programm
  164.  
  165. Mit dem Makro SHORT2FROMMP(mp2) gewinnt man den SB-Wert der WM_VSCROLL-
  166. Message. Gewöhnlich reagiert man auf den gefundenen SB-Wert dann mit
  167. einer switch-case-Leiste.
  168.  
  169. In der globalen Variablen sVPos merkt man sich die Sliderposition. Ihre
  170. Werte können sich nur zwischen der Unter- und Obergrenze des
  171. Scrollbereiches bewegen: Die Werte stellen hier Zeilen dar. Wird zum
  172. Beispiel SB_LINEDOWN in mp2 mitgegeben, muß sVPos um eins erhöht
  173. werden.
  174.  
  175. Beim Programmieren von Scrollbars entsteht öfter Verwirrung, weil der
  176. Benutzer und der Programmierer verschiedene Sichtweisen haben.
  177. Angenommen der Benutzer sieht zu Beginn die Zeilen 1 bis 10 des Textes
  178. und will die Zeilen 2 bis 11 sehen. Der Benutzer erreicht dies, indem
  179. er den unteren Pfeil anklickt. Für den Benutzer bewegt sich das Fenster
  180. über den Text! Real bewegt sich das Fenster jedoch nicht, bewegt wird
  181. der Text und zwar nach oben! Um dem Benutzer also immer die Sichtweise
  182. auf den Text zu simulieren, muß sich dann auch der Slider nach unten
  183. bewegen. Somit bewegen sich für den Programmierer Text und Slider in
  184. entgegengesetzte Richtung!
  185.  
  186. Da im Fenster zuerst die 1. Textzeile angezeigt wurde, hat sVPos den
  187. Wert 0. Danach wird ganz oben die 2. Textzeile angezeigt: sVPos hat den
  188. Wert 1 (vorausgesetzt sVStart ist 0).
  189. -----------------------------------------------------------------------
  190.  
  191.  
  192.  
  193.  
  194.  
  195.  
  196.  
  197.                         Seitenweises Blättern
  198.  
  199. Klickt der Benutzer den Bereich unterhalb des Sliders an, erwartet er,
  200. daß sich der Text genau um eine Bildschirmseite nach oben bewegt.
  201.  
  202. Dazu muß man wissen, wieviele Zeilen in das gerade gezeigte Fenster
  203. passen, egal wie groß das Fenster gerade ist. Deshalb wird bei jeder
  204. Größenänderung in WM_SIZE die Fensterhöhe in Pixeln der globalen
  205. Variablen cyClient zugewiesen. In das Fenster passen dann genau
  206. cyClient/cyChar Zeilen (cyChar war die Höhe einer Zeile in Pixeln für
  207. den aktuellen Font). Für cyClient=203 und cyChar=20 würden sich also 10
  208. Zeilen ergeben (Integerdivision). Beim Vorwärtsblättern um eine Seite
  209. würde sich sVPos also um 10 erhöhen.
  210. -----------------------------------------------------------------------
  211.  
  212.  
  213.  
  214.                      Begrenzung der Sliderposition
  215.  
  216. Grundsätzlich sollte nur soweit gescrollt werden können, daß die 1.
  217. Textzeile am oberen Fensterrand und die letzte am unteren Fensterrand
  218. steht. Andernfalls erhält man nur unnötige Leerzeilen. Zu diesem Zweck
  219. dienen die globalen Variablen sVStart und sVEnd, die Unter- und
  220. Obergrenze des Scrollbereiches darstellen. sVStart wird gewöhnlich auf
  221. 0 gesetzt. Wenn sVPos gleich sVStart ist, steht die 1. Textzeile am
  222. oberen Fensterrand. Die Variablen sVPos und sVEnd werden ebenfalls
  223. innerhalb von WM_SIZE gesetzt. Sie hängen jedoch nicht nur von der
  224. Fensterhöhe ab, sondern auch von der Textlänge.
  225.  
  226. Nachdem der Client nun weiß, in welcher Absicht der Benutzer mit dem
  227. Scrollbar agierte, muß überprüft werden, ob die gewünschte Scrollweite
  228. möglich ist. Es gilt:
  229.                       sVStart <= sVPos <= sVEnd.
  230.  
  231. Diese Bedingung kann man in C einfach programmieren:
  232.  
  233. if ( sVPos < sVStart )
  234.         sVPos = sVStart;
  235. else if ( sVPos > sVEnd )
  236.         sVPos = sVEnd;
  237.  
  238. Danach hat man in WM_VSCROLL einen gültigen Wert für sVPos gefunden.
  239. Wenn dieser Wert vom bisherigen sVPos-Wert abweicht, muß man zwei Dinge
  240. tun:
  241.  
  242. -- dem Scrollbar eine Message schicken, die ihm die neue Sliderposition
  243. mitteilt und
  244.  
  245. -- das Slider-Fenster neu zeichnen, wodurch dann der Benutzer annimmt,
  246. der Text sei gescrollt worden.
  247.  
  248. Bei WM_HSCROLL ist die Vorgehensweise völlig analog.
  249. -----------------------------------------------------------------------
  250.  
  251.  
  252.  
  253.              Was tun, wenn sich die Fenstergröße ändert?
  254.  
  255. Die neuen Fensterausmaße, gemessen in Pixeln, werden in den globalen
  256. Variablen cyClient und cxClient abgelegt. Da wir die Untergrenze des
  257. Scrollbereichs und die Textlänge nicht verändern, kann die Änderung der
  258. Fenstergröße nur die Obergrenze des Scrollbereichs sVEnd beeinflussen.
  259. Wie Sie schon im letzten Artikel sahen, bedeutet eine Verkleinerung des
  260. Fensters eine Erhöhung(!) der Obergrenze. Und umgekehrt. Beispielsweise
  261. hat bei einer Textlänge von 15 Zeilen und einer Fensterhöhe von 10
  262. Zeilen sVEnd den Wert 5. Verkleinert man das Fenster auf 8 Zeilen, hat
  263. sVEnd den Wert 7. Es gilt also:
  264.  
  265.                 sVEnd = sTextRows - cyClient/cyChar;
  266.  
  267. Wenn sVStart von 0 verschieden sein kann, muß um sVStart erhöht werden:
  268.  
  269.             sVEnd = sTextRows - cyClient/cyChar + sVStart;
  270.  
  271. Damit man konsistent bleibt ( sVStart <= sVEnd ), muß man auch noch
  272. berücksichtigen, daß das Fenster so groß werden kann, daß es den Text
  273. ganz aufnehmen kann: Bei einer Fensterhöhe von 20 und einer Textlänge
  274. von 15 würde unsere obige Gleichung für sVEnd einen negativen Wert
  275. ergeben! Um diesen Sonderfall auch abzudecken, berechnet sich sVEnd
  276. also letztlich nach folgender Formel:
  277.  
  278.      sVEnd = max(sVStart, sTextRows - cyClient/cyChar + sVStart);
  279.  
  280. Da bei einer Vergrößerung des Fensters sVEnd kleiner werden könnte als
  281. der bisherige Wert von sVPos, muß wiederum gewährleistet sein, daß
  282. gilt:
  283.  
  284.                     sVPos = min ( sVPos, sVEnd );
  285.  
  286. Diese Bedingung greift zum Beispiel im folgenden Fall: Das Fenster faßt
  287. 10 Zeilen, der Text ist 15 Zeilen lang und die Sliderposition ist 3
  288. (d.h. man sieht die 4. Textzeile in der obersten Fensterzeile). Wird
  289. das Fenster auf 20 Zeilen vergrößert, wird sVEnd 0. Dann muß aber auch
  290. sVPos 0 werden, damit es innerhalb des Scrollbereiches bleibt.
  291.  
  292. Da nach jedem WM_SIZE vom Presentation Manager automatisch ein WM_PAINT
  293. erzeugt wird, erscheint dann ein Fenster, in dem der Text in der
  294. obersten Zeile beginnt. Die letzten 5 Zeilen bleiben frei.
  295. -----------------------------------------------------------------------
  296.  
  297.  
  298.  
  299.  
  300.                        Messages an den Scrollbar
  301.  
  302. Schon bei der Besprechung von WM_VSCROLL sagten wir, daß man dem
  303. Scrollbar eine Message schickt, um ihm die endgültige Sliderposition
  304. mitzuteilen. Wie jedes Control kann auch der Scrollbar Messages
  305. empfangen. Für diesen Zweck wurde sein Handle in WM_CREATE bestimmt.
  306.  
  307. Messages an den Scrollbar beginnen mit dem Prefix "SBM".
  308.  
  309. Auf den ersten Blick mag es verwundern, daß man dem Scrollbar stets die
  310. Sliderposition zurückschickt. Man muß dies tun, und zwar nicht nur,
  311. wenn die Bedingung (sVStart <= sVPos <= sVEnd) verletzt wird! Wir
  312. erhalten vom Scrollbar nur die Information, daß ein bestimmter Bereich
  313. des Scrollbars angeklickt wurde. Von allein versetzt der Scrollbar
  314. deshalb seinen Slider aber noch nicht! Selbst wenn der Slider gezogen
  315. wird, erhält man nur die Information, bis zu welcher Position der
  316. Slider gezogen wurde. Auch wenn es optisch anders aussieht: ohne
  317. SBM_SETPOS-Message bleibt nach dem Ziehen der Slider an seiner alten
  318. Position!
  319. -----------------------------------------------------------------------
  320.  
  321.  
  322.  
  323.  
  324.  
  325.        Woran erkennt der Slider, wieweit er sich bewegen muß?
  326.  
  327. Wenn der Scrollbar die Message über seine neue Position erhält, woher
  328. weiß er dann, ob er den Slider um 1/3 oder um 1/10 der Scrollbarlänge
  329. bewegen muß? Dazu wird dem Scrollbar sein Scrollbereich ebenfalls
  330. bekannt gemacht (standardmäßig, d.h. ohne explizites Setzen, beträgt
  331. der Scrollbereich 0...100).
  332.  
  333. Wie Sie gerade sahen, wurde die Scrollbereichsgrenze innerhalb von
  334. WM_SIZE berechnet. Also schickt man auch hier sinnvollerweise die
  335. aktuellen Scrollbereichsgrenzen an den Scrollbar. (Wenn in einem
  336. Programm die Fenstergröße konstant bleibt, könnte man dies auch
  337. innerhalb von WM_CREATE tun.)
  338.  
  339. Die Message an den Scrollbar heißt SBM_SETSCROLLBAR: damit kann man
  340. Scrollbereich und Sliderposition gleichzeitig setzen.
  341.  
  342. Generell gilt, daß die Untergrenze des Scrollbereiches nicht negativ
  343. sein darf. Auch in der LiesKommandoArg-Funktion ist berücksichtigt, daß
  344. das 3. oder 4. Argument nicht negativ sein darf.
  345.  
  346. Gehen wir einmal kurz von folgendem aus: der Text hat 100 Zeilen, die
  347. Scrollbereichsuntergrenze ist 0 (dies ist auch die Standardeinstellung,
  348. falls in dem "scroll1"-Programm keine Argumente angegeben werden) und
  349. die aktuelle Fensterhöhe ist 20. Dann beträgt die
  350. Scrollbereichsobergrenze 100 - 20 + 0 = 80. Bei einer Scrollbe-
  351. reichsuntergrenze von 1 hat die Obergrenze den Wert 100 - 20 + 1 = 81.
  352.  
  353. Wenn man, wie es naiverweise oft geschieht, vergißt, die Fensterhöhe
  354. abzuziehen, wird die Scrollbereichsobergrenze zu groß und man kann
  355. soweit nach unten scrollen, daß man hinter dem Text noch eine ganze
  356. leere Bildschirmseite erhält.
  357.  
  358. Wenn man nun innerhalb von WM_SIZE dem Scrollbar seinen Scrollbereich
  359. richtig mitgeteilt hat, weiß der Scrollbar, wieviele Positionen der
  360. Slider annehmen kann: falls der Scrollbereich zum Beispiel (0, 2)
  361. beträgt, kann der Slider 3 verschiedene Positionen annehmen. Bei einem
  362. Bereich von (0, 80) kann er 81 verschiedene Positionen annehmen.
  363. Die Sliderstellung bestimmt sich aus dem Verhältnis der Sliderposition
  364. zur Anzahl der verschiedenen Positionen. Deshalb ergibt sich auch: Beim
  365. Verkleinern des Fensters wird der Scrollbereich größer und der Slider
  366. wandert optisch relativ zur Scrollbarlänge nach oben, obwohl die
  367. Sliderposition dieselbe blieb.
  368.  
  369. Als letztes wird in WM_SIZE noch geklärt, ob man den Slider überhaupt
  370. anzeigt. Wenn der Text ganz in das Fenster paßt, gilt sVEnd = sVStart.
  371. In diesem Fall wird der Scrollbar "disabled", indem man
  372. "WinEnableWindow" - mit dem 2. Argument auf FALSE gesetzt - anwendet.
  373. Wenn man einen disabled Scrollbar anklickt, piepst dieser. Er schickt
  374. dann auch keine Notification-Message ab.
  375. -----------------------------------------------------------------------
  376.  
  377.  
  378.  
  379.  
  380.  
  381.                    Könnte man noch mehr optimieren?
  382.  
  383. WM_SIZE-Messages werden vom PM nur geschickt, wenn sich die
  384. Fenstergröße ändert. Wir könnten auch hier wie bei WM_VSCROLL und
  385. WM_HSCROLL abfragen, ob die Änderung der Fenstergröße so groß ist, daß
  386. sich Sliderposition und Scrollbereich wirklich ändern - und nur dann
  387. eine Message an den Scrollbar schicken. Da dies aber der Normalfall
  388. ist, läßt man diese Abfrage weg.
  389. -----------------------------------------------------------------------
  390.  
  391.  
  392.  
  393.  
  394.                           Was wird angezeigt?
  395.  
  396. Bisher haben wir uns so sehr um den Weg der Messages gekümmert, daß wir
  397. die richtige Ausgabe einfach voraussetzten.
  398.  
  399. Wir wollen von unserem fiktiven Text nur die Zeilennummern ausgeben.
  400. Wenn man scrollt, bewegen sich die Zeilennummern.
  401.  
  402. Da wir mit den Kommandoargumenten 3 und 4 die
  403. Scrollbereichsuntergrenzen sVStart und sHStart verändern können, können
  404. wir deren Einfluß auch direkt sehen. Die erste ausgegebene Nummer ist
  405. sVStart, die letzte ist sVStart + Textlänge - 1. Aber selbst wenn bei
  406. sVStart=10 in der allerersten Zeile 10 steht, ist dies doch die erste
  407. Textzeile. Die ausgegebenen Zahlen stimmen also nur dann mit den
  408. eigentlichen Textzeilennummern (ab 1 beginnend zu zählen) überein, wenn
  409. man für sVStart 1 eingibt.
  410.  
  411. Real wird man sVStart nicht verändern, sondern auf 0 lassen. Dann hat
  412. die 1. Textzeile die Nummer 0. Dies ist auch unsere
  413. Standardeinstellung, wenn man die Argumente 3 und 4 wegläßt.
  414.  
  415. Jedesmal, wenn eine Scroll-Message die Slider-Position veränderte, wird
  416. auch eine globale Flagge (bVertScroll oder bHorzScroll) gesetzt und das
  417. Fenster durch WinInvalidateRect für "ungültig" erklärt, was zu einem
  418. WM_PAINT führt. Sitzt nun eine dieser Flaggen bei Ausführung von
  419. WM_PAINT, wird "Display Scroll" aufgerufen (wird WM_PAINT ausgeführt,
  420. weil zum Beispiel ein anderes Fenster unser Fenster verdeckte, so
  421. sitzen diese Flaggen nicht). "Display Scroll" gibt uns im Client die
  422. internen Informationen beim Scrollen aus: die Art der Scroll-Message
  423. (= SB-WERT), die Slider-Position und den Scrollbereich (hier vertikale
  424. oder horizontale "Ausdehnung" genannt).
  425.  
  426. Als Besonderheit werden die Clientmaße berücksichtigt: passen diese
  427. Informationen nicht in den Client, weil er zu klein ist, wird dieselbe
  428. Message in einer Messagebox ausgegeben: den SB-Wert verwenden wir
  429. hierbei als Titel der Box! Anschließend setzt "Display Scroll" die
  430. globale Flagge wieder auf 0 zurück.
  431. -----------------------------------------------------------------------
  432.  
  433.  
  434.  
  435.  
  436.              Wie werden die Textzeilen ausgegeben?
  437.  
  438. Die exakte Ausgabe der fiktiven Zeilennummern ist nicht ganz trivial.
  439. GpiCharStringAt erwartet als Parameter einen String: also muß man die
  440. Zeilennummer mit der itoa-Funktion in einen String umwandeln. Außerdem
  441. braucht man die Koordinaten der Grundlinie des ersten Zeichens von
  442. jeder Ausgabezeile. Diese übergeben wir der POINTL-Variablen ptl:
  443.  
  444.  ptl.x = ( 8 - sHPos ) * cxCaps;
  445.  ptl.y = cyClient - (i + 1 - sVPos) * cyChar + cyDesc;
  446.  
  447. --  Die x-Koordinate ist abhängig von sHPos. Steht der waagrechte
  448.    Slider ganz links, ist sHPos 0. Dann wird mit der Breite von 8
  449.    Zeichen eingerückt (cxCaps ist die durchschnittliche Breite der
  450.    Großbuchstaben des gewählten Fonts). Je weiter der Slider nach
  451.    rechts verschoben wird, desto mehr wandern die Zeilennummern nach
  452.    links. Wenn sHPos > 8 ist, sieht man von den Zahlen nichts mehr
  453.    (dies ist zu beachten bei der Wahl des 4. Kommandoarguments!).
  454.  
  455. --  Bei der y-Koordinate muß man beachten, daß beim PM alle Angaben
  456.    bezüglich der linken unteren Ecke des Clients erfolgen. Die linke
  457.    obere Ecke hat also die Koordinaten ( 0, cyClient ). Will man Text
  458.    in die oberste Zeile setzen, muß man wissen, daß als y-Koordinate
  459.    stets die Höhe der Zeichen-Grundlinie anzugeben ist. Die Grundlinie
  460.    der obersten Zeile liegt bei: cyClient - cyChar + cyDesc. Die
  461.    Grundlinie der zweitobersten Zeile liegt bei: cyClient - 2*cyChar +
  462.    cyDesc.
  463.    cyChar ist die Höhe von 1 Zeile unseres Fonts, cyDesc ist der
  464.    Abstand zwischen Grundlinie und dem was unterhalb davon steht (zum
  465.    Beispiel beim "g"). In der obigen Gleichung ist "i" die Zeilennummer.
  466.  
  467. Mit der for-Schleife erreicht man, daß nur die Zeilen, die in das
  468. Fenster passen, ausgegeben werden: dies sind bei einer Fensterhöhe von
  469. 10 Zeilen genau 10 Aufrufe von GpiCharStringAt, wobei die
  470. Schleifenvariable von sVPos bis 10 - 1 + sVPos läuft.
  471.  
  472. Man könnte "i" statt von sVPos auch stets bei 0 beginnen lassen: auf
  473. dem Schirm erhielte man dasselbe Ergebnis, weil man für i < sVPos eine
  474. y-Koordinate errechnen würde, die oberhalb des Clients liegt. Alle
  475. Ausgaben auf den Presentation Space, die nicht innerhalb vom Client
  476. liegen, werden einfach geklippt. Der Nachteil bei dieser Vereinfachung:
  477. man verschwendet CPU-Zeit. Beispiel: die Sliderposition sVPos steht auf
  478. 3: Dann werden für die Zeilen 0, 1 und 2 auch keine Aufrufe von
  479. GpiCharStringAt erfolgen.
  480.  
  481. sVPaintEnd hat den Index der letzten Zeile, die im Fenster ausgegeben
  482. wird: für sVPos = 0 ergibt sich:
  483.  
  484.  
  485.                     sVPaintBeg = 0;
  486.                     sVPaintEnd = Fensterhöhe - 1;
  487.  
  488. Falls der gesamte Text jedoch in das Fenster paßt, dürfen nur maximal
  489. sTextRows ( = Textlänge ) Zeilen ausgegeben werden: der Index läuft
  490. dann von 0 bis sTextRows - 1.
  491.  
  492. Außer dieser allgemeinen Ausgabe der Zeilennummern des fiktiven Textes
  493. verursachen die Informationen zur Scroll-Message ein generelles
  494. Problem: Die Ausgabe dieser Informationen in "DisplayScroll" soll nur
  495. beim 1. WM_PAINT nach Erhalt einer Scroll-Message (WM_VSCROLL oder
  496. WM_HSCROLL) erscheinen. Bei WM_PAINT-Messages, die andere Ursachen
  497. haben, soll diese Information nicht mehr angezeigt werden - auch nicht
  498. Teile davon.
  499.  
  500. Wenn ein anderes Fenster nur Teile der Scroll-Informationen verdeckte,
  501. wird anschließend nur der verdeckte Teil neu gezeichnet ("Update
  502. Region") - in der Hintergrundfarbe rot. Daß dann Reste der
  503. DisplayScroll-Ausgabe stehenbleiben, sieht komisch aus. Der Grund dafür
  504. ist sinnvoll: der PM zeichnet stets nur in der Update-Region. Wenn
  505. Fensterteile ungültig werden, muß in WM_PAINT alle Information
  506. vorhanden sein, diese neu zu zeichnen. Der PM geht davon aus, was nicht
  507. "ungültig" ist, sieht aus wie vorher. Da dies bei uns nicht der Fall
  508. ist, muß hier stets zu Beginn von WM_PAINT durch WinInvalidateRect die
  509. Update-Region auf den gesamten Client ausgeweitet werden ( 2. Parameter
  510. ist 0! ).
  511.  
  512. Genaueres zum Zeichnen beim PM (Einschränkungen in der Wirkung von Win-
  513. FillRect, ...) steht im Text "Besonderheiten beim WM_PAINT -
  514. Ausgabe im PM" (siehe unten).
  515. -----------------------------------------------------------------------
  516.  
  517.  
  518.  
  519.  
  520.                Wie kann man mit der Tastatur scrollen?
  521.  
  522. Wir wollen, daß auch die Cursortasten zu einem Scrollen führen: d.h.
  523. der Scrollbar versetzt seinen Slider und der Client scrollt den Text.
  524.  
  525. Man kann dies ganz einfach erreichen! Tastatureingaben gehen stets an
  526. das Fenster, das den Input-Focus hat. Dies ist unser Client-Fenster.
  527. Jede Tastatur-Eingabe erzeugt eine WM_CHAR-Message.
  528.  
  529. Wenn in dem 2. Messageparameter die CHARMSG-Komponente "vkey" eine
  530. Cursortaste darstellt, kann man die gesamte Message samt ihren
  531. Messageparametern an den Scrollbar schicken. Die interne
  532. Fensterprozedur des Scrollbar versteht solche WM_CHAR-Messages. (Jedoch
  533. darf man nicht alle(!) WM_CHAR-Messages unausgewählt an beide(!)
  534. Scrollbars schicken, weil es sonst zu Mißverständnissen kommt.)
  535.  
  536. Auf eine solche Message hin reagiert der Scrollbar so, als wäre der
  537. entsprechende Scrollbarteil angeklickt worden. Er schickt dem Client
  538. eine Scroll-Message zurück. Danach schickt der Client eine SBM_SETPOS-
  539. Message. In diesem Fall muß der Client also zweimal und nicht nur
  540. einmal Nachrichten an den Scrollbar schicken.
  541.  
  542. Ein Unterschied zwischen Tastatureingabe und Klicken auf den Scrollbar
  543. ergibt sich nur, wenn der Scrollbar disabled ist. Beim Klicken schickt
  544. er dann keine Scroll-Message. Bei Tastatureingabe schickt er eine
  545. Scroll-Message: da diese dann einen unzulässigen sVPos-Wert enthält,
  546. wird dieser innerhalb der Scroll-Message wieder zurechtgerückt:
  547. beispielsweise könnten sVStart = sVEnd = 0 sein. Die Taste Pagedown
  548. erzeugt eine WM_VSCROLL-Message: sVPos darf trotzdem nicht auf +22
  549. gesetzt werden! Hier wird in einer zukünftigen PM-Version sicher auch
  550. noch optimiert werden!
  551. -----------------------------------------------------------------------
  552.  
  553.  
  554.  
  555.  
  556.  
  557.              Informationen zum laufenden Programm
  558.  
  559. Wenn man einen Menüeintrag selektiert, wird eine WM_COMMAND-Message
  560. erzeugt. Unser Actionbar hat nur 1 Eintrag: "Exit und Info". In dessen
  561. Untermenü stehen 3 selektierbare Einträge: "Exit Scroll1" beendet
  562. ebenso wie F3 das Programm! "Resume" klappt das Untermenü wieder hoch
  563. und "Programm-Information..." gibt in einer Message-Box ein paar
  564. Informationen zum Programm aus.
  565.  
  566. In der globalen Variablen "szBoxTitel" steht der Titel der Box, der
  567. innerhalb der Funktion "AusgabeBox" verwendet wird. Ebenfalls in dieser
  568. Funktion verwendet wird für den Text innerhalb der Box die globale
  569. Variable "szBoxText". Diese wird mit sprintf gefüllt. Der Formatstring
  570. steht in der Variablen "apchFormatTable[0]". Gefüllt wird der
  571. Formatstring stets mit den aktuellen Werten für Textlänge, Textbreite,
  572. vertikale und horizontale Scrollbereichsuntergrenze (genau diese Werte
  573. sind als Kommandoargumente veränderbar).
  574.  
  575. Wir haben den Weg über den globalen Formatstring "apchFormatTable" ge-
  576. wählt, damit alle längeren Ausgabetexte an 1 Stelle gesammelt sind.
  577. Dadurch wird das eigentliche Programm übersichtlicher.
  578. -----------------------------------------------------------------------
  579.  
  580.  
  581.  
  582.  
  583.    Wann wird die Scroll-Information in einer Message-Box ausgegeben?
  584.  
  585. Nun noch eine kurze Anmerkung zur Funktion "DisplayScroll": DisplaySc-
  586. roll wird aufgerufen mit den Koordinaten des Client-Rechtecks als
  587. aktuellen Parametern. Dabei gilt:
  588.  
  589.                  pRectl->yTop = cyClient;
  590.                  pRectl->xRight = cxClient;
  591.                  pRectl->xLeft = pRectl->yBottom = 0;
  592.  
  593. Man übergibt diese RECTL-Struktur statt zweier Ausdehnungen cxClient
  594. und cyClient, weil innerhalb von DisplayScroll ebenfalls mit dieser
  595. RECTL-Struktur gearbeitet wird.
  596.  
  597. DisplayScroll prüft, ob der Ausgabetext mit den Scroll-Informationen
  598. noch in das Clientfenster paßt. Die Prüfung ist ganz einfach:
  599. pRectl->yTop muß größer als 4 Zeilen sein und die Breiten von Titel und
  600. Text dürfen nicht größer als pRectl->xRight sein. Die Ausdehnung in
  601. Pixeln eines gegebenen Textes erhält man mit GpiQueryTextBox.
  602.  
  603. Falls der Text in den Client paßt, wird unterschieden, ob der
  604. Scrollevent vom vertikalen oder vom horizontalen Scrollbar kam. Kam er
  605. vom vertikalen Scrollbar erfolgt die Ausgabe mit WinDrawText
  606. rechtsbündig und vertikal zentriert. (Da für Titel und Text jeweils
  607. getrennt WinDrawText aufgerufen wird, sieht es etwas komplizierter aus
  608. als es ist). Wenn der Scrollevent vom horizontalen Scrollbar kam,
  609. erfolgt die Ausgabe unten und horizontal zentriert (auch wieder mit 2
  610. getrennten WinDrawText-Aufrufen).
  611.  
  612. Geht der Text nicht in den Client, wird die Flagge "bMsgBox" auf TRUE
  613. gesetzt und, abhängig von welchem Scrollbar der Event kam, szBoxText
  614. gefüllt. Dann wird die Funktion AusgabeBox aufgerufen (szBoxTitel wurde
  615. schon innerhalb von WM_SCROLL bzw. WM_HSCROLL gefüllt).
  616.  
  617.  
  618.  
  619.  
  620.  
  621. -----------------------------------------------------------------------
  622. -----------------------------------------------------------------------
  623.  
  624.  
  625.  
  626.  
  627.  
  628.       "Warum ist hwndFrame in WM_CREATE noch nicht bekannt?"
  629.  
  630. Innerhalb von WM_CREATE möchte man sich mit WinWindowFromID den Handle
  631. des vertikalen Scrollbars beschaffen. WinWindowFromID hat 2 Argumente:
  632. den Handle des Vaters und die ID des gesuchten Fensters. Der gesuchte
  633. vertikale Scrollbar hat die Fenster-ID FID_VERTSCROLL. Der Vater des
  634. Scrollbars ist der Frame mit dem Handle hwndFrame.
  635.  
  636. Also könnte man versuchen, sich hwndVScroll auf folgende Weise zu be-
  637. schaffen:
  638. hwndVScroll = WinWindowFromID ( hwndFrame, FID_VERTSCROLL );
  639.  
  640. Dieser Aufruf geht leider schief!!!
  641.  
  642. Hier ist wieder ein Punkt, wo in anderen PM-Publikationen stillschwei-
  643. gend die Lösung angeboten wird -- ohne auch nur ein Wort darüber zu
  644. verlieren, warum der direkte Weg nicht geht. Jeder soll akzeptieren,
  645. weil alle denselben Weg gehen! Aber wie soll jemand, der den PM lernen
  646. will, verstehen, wenn keine Gründe genannt werden?
  647.  
  648. Zuerst einmal die Lösung: da wir innerhalb der Fensterprozedur des
  649. Client sind, enthält "hwnd" dessen Handle. Wenn man den ,,fHandle eines
  650. Fensters hat, kann man den Handle eines dazu in Beziehung stehenden
  651. Fensters mit WinQueryWindow erhalten: das 1. Argument ist der gegebene
  652. Handle, das 2. Argument enthält die Beziehung in Form einer Konstanten
  653. (die QW-Konstanten sind in PMWIN.h definiert). Der 3. Parameter ist für
  654. uns i.M. ohne Bedeutung. Den Handle des Vaters von "hwnd" liefert also
  655.  
  656. WinQueryWindow( hwnd, QW_PARENT, ,,l FALSE );
  657.  
  658. Somit kann man mit dem Tandem WinWindowFromID und WinQueryWindow den
  659. Handle hwndVScroll bestimmen:
  660.  
  661. hwndVScroll = WinWindowFromID( WinQueryWindow( hwnd,  QW_PARENT,
  662. FALSE ),  FID_VERTSCROLL );
  663.  
  664. Warum aber geht der Aufruf von WinWindowFromID mit der globalen Varia-
  665. blen hwndFrame als 1. Argument schief, obwohl WinQueryWindow doch genau
  666. den Wert von hwndFrame liefert?
  667.  
  668. Der Grund liegt darin, daß zu dem Zeitpunkt, wo WM_CREATE abgearbeitet
  669. wird, die globale Variable hwndFrame noch nicht gefüllt ist. Auf den 1.
  670. Blick erscheint dies unmöglich: In der main-Routine (innerhalb von
  671. WndCreate) wird die globale Variable hwndFrame als Rückgabewert von
  672. WinCreateStdWindow gefüllt und erst danach wird im Message-Loop von
  673. WinGetMsg die 1. Message aus der Anwendungs-Queue geholt!
  674. Aber genau hier stößt man wieder auf nicht-dokumentierte PM-Interna:
  675. Welche Messages werden von welchen PM-Befehlen direkt oder indirekt
  676. angestoßen? WinGetMsg kann nur solche Messages aus der Anwendungs-Queue
  677. holen, die hineingestellt wurden ("posting"). Die ersten 16 (die genaue
  678. Zahl variert von PM-Version zu PM-Version) Messages, die der Client
  679. erhält, gelangen aber direkt (durch "Senden") in die Client-Fensterpro-
  680. zedur (zum Beispiel WM_CREATE, WM_ACTIVATE, WM_SIZE).
  681.  
  682. Die Fensterprozedur "ClientWindowProc" wird nämlich mehrfach direkt wie
  683. eine Funktion innerhalb der Verarbeitung von WinCreateStdWindow aufge-
  684. rufen! Da dies passiert, bevor WinCreateStdWindow hwndFrame zurückgibt,
  685. ist hwndFrame innerhalb von WM_CREATE auch nicht gefüllt (hat als glo-
  686. bale Variable also noch den Wert NULL).
  687.  
  688. -----------------------------------------------------------------------
  689.  
  690.  
  691.  
  692.  
  693.  
  694.  
  695.  
  696.  
  697.          "Besonderheiten beim WM_PAINT - Ausgabe im PM"
  698.  
  699. Der Presentation Manager hat einen internen Speicherbereich, genannt
  700. Presentation Space (PS), in den die Ausgabe erfolgt. Da der Standard-PS
  701. mit dem Bildschirm verbunden ist, erscheint sein Inhalt zum Beispiel in
  702. einem Fenster auf dem Schirm. Den Handle des Standard-PS erhält man
  703. nach WM_PAINT durch
  704.  
  705. hps = WinBeginPaint( hwnd, NULL, &rectl );
  706.  
  707. Zwischen diesem Aufruf und
  708.  
  709. WinEndPaint( hps );
  710.  
  711. müssen die Ausgabefunktionen (Gpi-Funktionen) liegen.
  712.  
  713. Die Fensterprozedur des Client wird jedesmal mit einem WM_PAINT aufge-
  714. rufen, wenn Teile des Clients neu gezeichnet werden sollen. Fenstertei-
  715. le müssen neu gezeichnet werden, wenn sie "ungültig" (invalid) sind.
  716. Die ungültigen Bereiche nennt man "Update Region". Ungültig wird ein
  717. Fensterteil dann, wenn er vorher von einem anderen Fenster verdeckt war
  718. und nun in den Vordergrund tritt. Invalidieren kann man Fensterteile
  719. aber auch dadurch, daß man sie explizit als ungültig erklärt: durch den
  720. Aufruf von
  721.  
  722. WinInvalidateRect( hwnd, &rectl, fInclChildren );
  723.  
  724. Mit dem 2. Argument kann man den Bereich innerhalb des Fensters hwnd
  725. angeben, der als nicht mehr gültig erklärt werden soll. WinInvalidate-
  726. Rect erzeugt dann ein WM_PAINT, das sofort ausgeführt wird.
  727.  
  728. Betrachten Sie kurz folgenden Programmausschnitt:
  729.  
  730. case WM_PAINT:
  731.    Color = (Color==CLR_BLUE) ? CLR_RED : CLR_BLUE;
  732.    hps = WinBeginPaint( hwnd, NULL, &rectl );
  733.    WinFillRect( hps, &rectl, Color );
  734.    WinEndPaint( hps );
  735.  
  736. Bei jedem Aufruf von WM_PAINT wird die Update-Region mit einer anderen
  737. Farbe gefüllt: Ist das Fenster blau, wird die Update-Region danach rot
  738. und umgekehrt. So kann man genau sehen, welche Teile eines Fensters
  739. verdeckt waren.
  740.  
  741. Wenn man jetzt vor das WinFillRect den Aufruf
  742.  
  743.     WinQueryWindowRect( hwnd, &rectl );
  744.  
  745. stellt, wird rectl mit den Koordinaten des ganzen Clients gefüllt. Man
  746. erwartet auf Anhieb, daß das folgende WinFillRect mit diesem neuen Wert
  747. von rectl arbeitet: bei jedem WM_PAINT sollte also das ganze Fenster
  748. die Farbe wechseln und nicht nur der gerade wieder sichtbar gewordene
  749. Teil. ABER: nur die Update-Region erhält die neue Farbe, der Rest von
  750. rectl wird geklippt.
  751.  
  752. Auch bei GpiErase oder WinDrawText ist es genau dasselbe: bei dem ff.
  753. Codeteil
  754.  case WM_PAINT:
  755.    Color = (Color==CLR_BLUE) ? CLR_RED : CLR_BLUE;
  756.    hps = WinBeginPaint( hwnd, NULL, &rectl );
  757.      WinQueryWindowRect( hwnd, &rectl );
  758.      WinDrawText( hps, -1,
  759.                   szText,
  760.                   &rectl,
  761.                   CLR_BLACK,
  762.                   Color,
  763.                   DT_CENTER | DT_VCENTER );
  764.    WinEndPaint( hps );
  765.  
  766. ändert sich durch "Color" nur die Hintergrundfarbe der Update-Region!
  767. "rectl" dient also nur dazu, bei der Zentrierung des Textes die Grenzen
  768. abzustecken.
  769.  
  770. Wie kann man es nun aber unabhängig von der gegebenen Update-Region
  771. schaffen, daß bei jedem WM_PAINT stets das ,,fganze Fenster neu ge-
  772. zeichnet wird? Die Lösung steckt im zusätzlichen Aufruf WinInvalidate-
  773. Rect noch vor WinBeginPaint.
  774.  
  775.  case WM_PAINT:
  776.  Color  = (Color==CLR_BLUE) ? CLR_RED : CLR_BLUE;
  777.  WinInvalidateRect( hwnd, NULL, FALSE );
  778.  hps = WinBeginPaint( hwnd, NULL, &rectl );
  779.    WinFillRect( hps, &rectl, Color );
  780.  WinEndPaint( hps );
  781.  
  782. Nun wird bei jedem WM_PAINT stets der ganze Client neu gezeichnet. Dies
  783. ist immer nötig, wenn sich auch Teile ändern sollen, die nicht in der
  784. Update-Region liegen. Es kommt hierbei nicht zu einer doppelten Ausfüh-
  785. rung von WM_PAINT, obwohl WinInvalidateRect normalerweise ein WM_PAINT
  786. generiert. Der PM optimiert: es wird nur die Update-Region verändert.
  787.  
  788. Das häufig benutzte WinQueryWindowRect kann also bestenfalls dazu die-
  789. nen, in WinDrawText den Text zu zentrieren oder um Teile der Update-
  790. Region zu erneuern. Nie aber kann man damit Teile außerhalb der Update-
  791. Region neu zeichnen: Jeder graphische Output darüberhinaus wird an der
  792. Update-Region geklippt.
  793.  
  794.  
  795. -----------------------------------------------------------------------
  796.  
  797.  
  798.          ---          ENDE Begleittext zu 10/90            ---
  799.