Die Community zu .NET und Classic VB.
Menü

Einstieg in die Grafikprogrammierung

 von 

Übersicht 

Wie Sie unschwer erkennen, hat dieser Kurs das Thema "Grafik". Es handelt sich um ein weites, aber wichtiges Feld in der Programmierung. Nun, Sie brauchen nicht zu befürchten, daß ich Sie mit viel Mathematik nerven werde. Dieser Kurs soll Ihnen schließlich keine Nachhilfe in Vektorrechnung geben, sondern Ihnen helfen, Grafiken in Ihren VB-Programmen einzusetzen.

Mit freundlichen Grüßen
Dominik Scherlebeck (PCDVisual)

1.1 Eine neue Leinwand  

Zu den guten alten MS-DOS-Zeiten wurde natürlich auch schon mit Grafik gearbeitet. "Damals" wurde der ganze Monitor zum Zeichenblock und die Programmierer konnten ihrer Kreativität freien Lauf lassen. Unter Windows teilen sich oft mehrere Anwendungen den Bildschirm und so wurde es erheblich schwieriger, Grafiken und Spiele zu erstellen.

VisualBASIC hat auch hier eine einfache Lösung: Das Bildfeld-Steuerelement:
Setzen Sie dieses Steuerelement einfach auf eine Form und bringen Sie es in die gewünschte Größe. Auf der nun entstandenen weißen Fläche wird später die Grafik ausgegeben


Abbildung 1: PictureBox in der IDE

Das Bildfeld-Steuerelement kann nicht nur von Ihnen direkt während der Laufzeit, sondern auch schon in der Entwicklungszeit verändert werden. Sie können zum Beispiel mit Hilfe der "Picture"-Eigenschaft ein beliebiges Bild (unterstützte Dateiformate: BMP, ICO, WMF, RLE und DIB) in dem Objekt anzeigen lassen. Hierzu genügt es, im Eigenschaftfenster den Eintrag "Picture" auszuwählen und dann auf die "..."-Taste zu klicken. Nun können Sie eine Datei auswählen.
In dem Zusammenhang will ich ebenfalls die "AutoSize"-Eigenschaft erwähnen. Wenn Sie AutoSize auf True setzen, paßt sich das Bildfeld der Größe des ausgewählten Bildes an. Steht AutoSize auf False, so kann das Bildfeld beliebig groß sein.
Übrigens: Wenn Sie ein Bild in Ihr Objekt aufnehmen, wird dieses Bild in einer Zusatzdatei mit der Endung .FRX zusammen mit allen anderen Bildern gespeichert. Sie benötigen also Bilder, die Sie mit Hilfe der Picture-Eigenschaft in Ihre Form aufnehmen später nicht mehr.

1.2 Vorbereitungen  

Ich will Ihnen natürlich nicht die Befehle nur in der Theorie erklären. Daher bitte ich Sie, bevor es richtig los geht, folgende Form zu entwerfen:

Das Formular:
Name=TEST

Das Bildfeld (in der Mitte):
Name=BILD
ScaleMode=3 (Pixel)

Der Knopf:
Name=LOS
Caption="Los"


Abbildung 2: Vorbereitungen

Wir werden später die Grafikbefehle in die Ereignisprozedur "LOS_CLICK" setzen...

1.3 Von Twips und Pixeln  

Die Grundlage des Zeichnens unter DOS waren immer die Pixel. Ein Pixel ist einfach ein Punkt auf dem Bildschirm. So konnte man angeben, der Bildschirm (bzw. der aktuelle Bildschirmmodus) hat eine Zeichenbreite von 640 Pixeln. Natürlich besteht auch das Bild unter Windows aus Pixeln, jedoch hat Windows auch die Fähigkeit, in anderen Einheiten zu rechnen. Zum Beispiel in Zentimetern, Zoll und in Twips. Twips?? Moment mal! Was sollen denn Twips sein? Ja, das habe ich mich auch gefragt, als ich mit VisualBASIC angefangen habe. Ein Twip ist 20 mal kleiner, als ein gedruckter Punkt. 1440 Twips entsprechen einem Inch und 567 Twips entsprechen einem Zentimeter.
Twips sind unter VisualBASIC die Standardeinheit. Der Vorteil liegt darin, daß Abstände auf jedem Bildschirm richtig dargestellt werden. Auf einem 14"er sind 1000 Twips genauso groß wie auf einem 17" Monitor.
Wir wollen lieber wieder auf die gute alte Pixel-Einheit zugreifen. Dies erreichen wir,, indem wir die ScaleMode-Eigenschaft ändern. Steht ScaleMode auf 3, so werden Strecken in dem betreffenden Objekt in Pixeln gemessen.

1.4 Dimensionen  

Keine Angst, ich will Ihnen jetzt nichts über 3D-Grafiken sagen! Es geht nur um die Größe einer Zeichenfläche. Damit wir vernünftig auf einem Bildfeld zeichnen können ist es wichtig, die Breite und die Höhe dieses Objekts zu kennen. Dazu hat das Bildfeld die Eigenschaften ScaleWidth und ScaleHeight. ScaleWidth gibt die Breite und ScaleHeight die Höhe in der aktuellen Einheit an.
Wenn wir eine vorgegebene Zeicheneinheit von VisualBASIC benutzen, werden die ScaleWidth- und ScaleHeight-Eigenschaften automatisch gesetzt.
Es gibt zusätzlich die Möglichkeit, eine eigene Einheit zu definieren. Dazu müssen Sie ScaleMode auf 0 (Benutzerdefiniert) setzen. Nun können Sie die ScaleWidth- und ScaleHeight-Eigenschaften selbst ändern. Wollen Sie zum Beispiel, daß das Feld 10 Einheiten breit ist, dann setzen Sie ScaleWidth auf 10. Für den Fall, daß Sie mit einer eigenen Einheit arbeiten, können Sie noch zwei weitere Eigenschaften ändern: ScaleTop und ScaleLeft. ScaleTop gibt an, welche Koordinate am oberen Rand vorliegt und ScaleLeft, welche am linken Rand vorliegt. Das klingt vielleicht etwas kompliziert, daher hier ein Beispiel:


Abbildung 3: Skalierungen

ScaleTop = 10
ScaleHeight = 100
ScaleLeft = 0
ScaleWidth = 100

In dem Bild sehen Sie, welche Auswirkungen diese Einstellungen auf ein Bildfeld haben.

1.5 Vom ersten Pixel zum Kreis  

So, nun können wir mit dem eigentlichen Zeichnen anfangen. Erstellen Sie dazu bitte die Ereignisprozedur "LOS_CLICK" und geben Sie folgende Zeilen ein:

Private Sub LOS_CLICK()
  Bild.PSet (10, 10), 0
End Sub
Starten Sie nun das Programm und klicken Sie auf "Los". Auf dem Bildfeld erscheint nun ein schwarzer Punkt. Damit haben Sie schon den ersten Grafikbefehl:
<Objekt>.PSet (X,Y)[,Farbe]
PSet ist eigentlich kein richtiger Befehl, vielmehr handelt es sich um eine Methode des Bildfeldes. PSet erwartet als Parameter mindestens ein Koordinatenpaar, das in Klammern steht.
Bild.PSet (10,50) gibt also einen Punkten an der X-Koordinate 10 und Y-Koordinate 50 aus.
Um die Farbe des Punktes zu beeinflussen gibt es zwei Möglichkeiten:

1. Sie können einfach die Vordergrundfarbe des Bildfeldes ändern.
2. Sie können eine Farbe als Parameter angeben .

Die erste Möglichkeit ist klar und wird daher nicht weiter beschrieben. Wenn Sie jedoch eine Farbe als Parameter angeben, z.B. Bild.PSet (10,10),15, so werden Sie bald merken, daß die meisten Pixel Schwarz sind. Woher kommt das? Nun, unter DOS wurde fast immer mit einer einheitlichen Palette gearbeitet. Man brauchte nur die Nummer der Farbe anzugeben. Zum Beispiel 1 für Blau, 2 für Grün usw. Unter Windows gibt es jedoch eine Große Zahl an verschiedenen Grafikmodi und Grafikkarten. Die Auswahl reicht von Schwarz/Weiß bis zu 32 Bit TrueColor. Darum gibt es unter Windows ein anderes "Farbformat". Die Nummer setzt sich aus dem Rot-, Blau- und Grünanteil der Farbe zusammen.
In diesem Kurs will ich Ihnen jedoch eine Umrechnungsformel ersparen. Wir werden nur mit den Funktionen von VisualBASIC arbeiten, um zu vernünftigen Farben zu kommen.
QBColor(Farbe) QBColor liefert den Farbwert, der einer EGA-Farbe entspricht. QBColor(1) liefert zum Beispiel den Wert für ein dunkles Blau, QBColor(15) den Wert für ein helles Weiß. Diese Funktion wird oft benutzt, um Zeichenroutinen von QBasic, QuickBasic und VisualBASIC für DOS nach Windows zu übersetzen.
FarbnummerFarbe
0 Schwarz
1 Dunkles Blau
2 Dunkles Grün
3 Dunkles Cyan
4 Dunkles Rot
5 Dunkles Lila
6 Dunkles Gelb
7 Dunkles Weiß / Grau
8 Dunkles Grau
9 Helles Blau
10 Helles Grün
11 Helles Cyan
12 Helles Rot
13 Helles Lila
14 Helles Gelb
15 Helles Weiß

RGB(Rot, Grün, Blau)
RGB mischt eine Farbe aus den angegebenen Farbanteilen zusammen. Die Anteile werden jeweils mit 0-255 angegeben. 0 bedeutet, die Farbe wird nicht beim Mischen benutzt. 255 bedeutet, die Farbe wird beim Mischen benutzt.
Sie wissen nicht, wie man Farben aus Rot, Grün und Blau mischt? Na dann, hier eine kurze Einführung:
Rot + Grün = Gelb
Rot + Blau = Lila
Grün + Blau = Türkis (Cyan)
Rot + Grün + Blau = Grau/Weiß

Je nachdem, wie Groß der Anteil jeder Farbe ist erhalten Sie eine andere, neue Farbe. Wenn Sie sich damit nicht auskennen sollten Sie einfach einmal experimentieren. Nachdem Sie nun die beiden Funktionen zum Erstellen von Farbwerten kennengelernt haben, können Sie auch unseren Punkt z.B. Rot färben:
Private Sub LOS_CLICK()
  Bild.PSet (10, 10), QBColor(4)
End Sub
Ein einzelner Punkt ist natürlich noch nicht besonders viel, aber diese Pixel sind die Grundlage für die komplexesten Grafiken. Allerdings werde ich hier nicht erklären, wie Sie zum Beispiel eine Ausgleichskurve oder sonstiges "selbst" zeichnen. Dazu wird es evtl. einen speziellen Kurs geben. Wir wollen uns nun die anderen Grafikbefehle ansehen, die VisualBASIC mitliefert:
<Objekt>.Line (x1, y1)-(x2,y2) [,Farbe[,B[F]]]
Wir setzen eine Linie auf das Bildfeld, indem wir die Line-Methode benutzen. Line erwartet normalerweise zwei Koordinatenpaare, die durch einen Bindestrich getrennt sind. Dahinter können Sie noch eine Farbe angeben. Als letzten Parameter können Sie B oder BF anhängen. B bedeutet, daß statt einer Linie von x1,y1 nach x2,y2 ein Rechteck zwischen den Koordinaten gezeichnet wird. Und BF bedeutet, daß eine gefüllte Box gezeichnet wird.
Private Sub LOS_CLICK()
  Bild.Line (10, 10)-(100, 80), QBColor(9), BF
  Bild.Line (10, 10)-(100, 80), QBColor(0), B
  Bild.Line (10, 10)-(100, 80), QBColor(14)
End Sub
In dem oberen Beispiel wird erst eine gefüllte hellblaue Box zwischen den Koordinaten 10,10 und 100,80 gezeichnet. Anschließend wird zwischen den selben Koordinaten ein schwarzes Rechteck gezeichnet und zum Schluß wird eine gelbe Linie gezeichnet.
Und nun kommen wir zu einem sehr einfachen und gleichzeitig doch sehr komplexen Objekt: Dem Kreis
<Objekt>.Circle(x, y),Radius[,Farbe[,Start[,Ende [,Verhältnis]]]]
Allein schon die Anzahl der möglichen Parameter zeigt, daß der Kreis sehr vielfältig zum Einsatz kommen kann. Fangen wir aber erst einmal einfach an. Die Circle-Methode verlangt mindestens ein Koordinatenpaar und den Radius des Kreises. Die Anweisung Bild.Circle (50,50),40 würde zum Beispiel einen Kreis mit dem Radius 40 um den Punkt 50/50 zeichnen. Als weiteren Parameter kann man die Farbe angeben. Hierbei gelten die gleichen Regeln wie bei der Linie und dem Punkt.
Anschließend können Sie einen Startwinkel und einen Endwinkel angeben. Der Startwinkel gibt an, ab welchem Winkel der Kreis gezeichnet wird und Endwinkel gibt an, bis zu welchem Winkel der Kreis gezeichnet wird. Damit kann man zum Beispiel ein Kreisstück zeichnen lassen. Der Winkel wird dabei in Bogenmaß angegeben, variiert also zwischen 0 und 2pi. Auch hierzu eine Grafik:
Die Anweisung
Bild.Circle (50,50), 40, QBColor(1), 0, 3.1415/2
gibt in etwa den nebenstehenden Kreis aus. Der Startwinkel liegt bei 0 Grad und der Endwinkel bei 90 Grad, was ½ pi entspricht. Denken Sie daran, die Winkel müssen in Bogenmaß angegeben werden. Sie können von Grad eben in Bogenmaß mit der Formel Bogenmaß = Grad * 3.1415 / 180 umrechnen.
Der letzte Parameter, den Sie an die Kreisanweisung anhängen können gibt das Verhältnis zwischen Höhe und Breite des Kreises an:
Verhältnis = Höhe / Breite
Soll der Kreis zum Beispiel doppelt so hoch wie breit sein, so tragen Sie hier 2/1 also 2 eintragen. Soll der Kreis hingegen doppelt so breit wie hoch sein, einfach 0.5 oder 1/2 eintragen. Der Kreis ist nun eine Elipse.
<Objekt>.Cls
Diese Methode bedarf wohl keiner großen Vorstellung mehr, oder etwa doch? CLS leert das Bildfeld. Ein Bild, das mit Hilfe der Picture-Eigenschaft angezeigt wurde bleibt aber erhalten! Eine andere Möglichkeit, das ganze Bild zu "leeren" ist, eine weiße gefüllte Box zu zeichnen:
<Objekt>.Line (0,0)-(<Objekt>.ScaleWidth, <Objekt>.Scaleheight), _
QBColor(15),BF

1.6 Wichtige Eigenschaften  

Jedes Bildfeld verfügt über einige wichtige Eigenschaften, die das Aussehen von Linien, Kreisen, Boxen etc. beeinflussen: DrawMode, DrawStyle, FillColor und FillStyle. Hier nun die Eigenschaften in der Übersicht:

DrawMode
DrawMode gibt an, wie gezeichnet werden soll. Normalerweise steht DrawMode auf 13, was bedeutet, daß die Pixel gesetzt werden. Es gibt aber noch einige andere interessante Einstellungen. Eine wichtige Einstellung ist 6.Steht DrawMode auf 6, so wird nicht fest gezeichnet, sondern es wird invertiert. Zeichnen Sie also eine gefüllte Box über einen bestimmten Bereich, so wird dieser Bereich nicht einfach ausgefüllt sondern invertiert. Die anderen Einstellungen sind nicht besonders wichtig, können aber in der Hilfe von VisualBASIC nachgelesen werden.

DrawStyle
DrawStyle gibt an, wie gezeichnet werden soll. Dabei bezieht sich DrawStyle nur auf Linien, Umrandungen etc. und nicht auf die Füllung von Kreisen oder Boxen. Hier die Einstellungsmöglichkeiten für DrawStyle:

WertStil
0Gefüllt (Durchgehend)
1Gestrichelt
2Gepunktet
3Strich-Punkt
4Strich-Punkt-Punkt
5Transparent (kein Rahmen etc.)
6Innen gefüllt

FillColor
FillColor gibt die Farbe an, mit der ein geschlossenes Objekt (Rahmen, Kreis) gefüllt wird. Diese Einstellung gilt nicht für eine gefüllte Box (Line .... BF), da diese ja schon eine eigene Füllfarbe hat. Hier gelten natürlich auch wieder die "Farbregeln".

FillStyle
FillStyle ist das Pendant zu DrawStyle, da FillStyle den Zeichenmodus für Flächen angibt. Für FillStyle gibt es folgende Einstellungsmöglichkeiten:

WertFüllstil
0Gefüllt
1Transparent (keine Füllung)
2Horizontale Linien
3Vertikale Linien
4Aufwärtsdiagonal
5Abwärtsdiagonal
6Kreuz
7Diagonalkreuz

Auch FillStyle gilt nicht für eine gefüllte Box, die mit Line ... BF gezeichnet wurde! Was Sie sich im einzelnen unter diesen Einstellungen vorzustellen haben sollten Sie am besten selbst einmal austesten.

1.7 AutoRedraw  

Vielleicht ist Ihnen schon aufgefallen, daß Zeichnungen auf einem Bildfeld verschwinden, sobald ein anderes Fenster darüber gelegt und wieder weggezogen wird. Das liegt daran, daß die Zeichnungen nur auf dem Bildschirm erscheinen, aber nicht im Speicher vorliegen. Dies kann man ändern, wenn man AutoRedraw auf True stellt. Dadurch wird bewirkt, daß Zeichenoperationen (wie zum Beispiel das Setzen eines einfachen Pixels) nicht nur auf den Bildschirm angewandt sondern auch im Speicher "mitgeschrieben" werden.

Der Nachteil ist, daß das Zeichnen etwas länger dauert, aber die Vorteile liegen auf der Hand. Außerdem ist es nicht möglich, das Bild zu speichern, wenn AutoRedraw auf False steht.

AutoRedraw hat aber noch eine andere Funktion. Wenn Sie die Eigenschaft auf False stellen, so wird das Paint-Ereignis ausgelöst, wenn das Bild neugezeichnet werden muß (zum Beispiel, weil der Benutzer das Fenster eben minimiert und nun wieder vergrößert hat). In die Ereignisprozedur Bild_Paint könnte man nun alle wichtigen Schritte schreiben, um das Bild wiederherzustellen. Dies wäre ein manueller Weg um den Bildinhalt beizubehalten.

1.8 Gesammelte Werke  

Im Gegensatz zu den DOS-Versionen bietet VisualBASIC auch die Möglichkeit, ein Bild zu speichern (natürlich wieder in dem, gängigen BMP-Format) und zu laden. Dazu gibt es zwei Befehle:

SavePicture <Objekt>.Image/<Objekt>.Picture, Datei
SavePicture speichert ein beliebiges Bild in eine Datei. Aber warum kann man nicht nur die Picture-Eigenschaft eines Objekts sondern auch die Image-Eigenschaft speichern?
Die Picture-Eigenschaft gibt ein festes Hintergrundbild an, auf das Sie nicht zeichnen können. Image gibt das sichtbare Bild an, also das, was wirklich in diesem Bild zu sehen ist. Sie können auch das aktuelle Bild zum Hintergrundbild machen, indem Sie die folgende Anweisung nutzen:
<Objekt>.Picture = <Objekt>.Image
Wie oben schon gesagt ist es wichtig, daß AutoRedraw auf True steht, damit Bilder richtig gespeichert werden.Die Datei gibt eine BMP-Datei an, in der das Bild gespeichert werden soll.
<Objekt>.Picture=LoadPicture(Datei)
Die LoadPicture-Funktion lädt ein Bild (unterstützte Formate: BMP, WMF, RLE, DIB, ICO) in die Picture-Eigenschaft, also als festes Hintergrundbild.
Wichtig: Sie können kein Bild in die Image-Eigenschaft laden!

2.1 Zeichen setzen  

Wie Sie vielleicht noch wissen benutzt man unter QBasic (also unter DOS) den Befehl PRINT zum Ausgeben von Texten. Auch dieser Befehle hat den Sprung nach Windows geschafft und steht uns als Methode von Bildfeldern zur Verfügung:

<Objekt>.Print <Ausdruck>
Sie können natürlich auch unter Windows komplexe Ausdrücke hinter Print angeben, wie etwa "Hallo " & Name & "!" usw. Probieren wir es doch gleich aus. Öffnen Sie die Ereignisprozedur LOS_CLICK im Codefenster und ändern Sie den Text wie folgt:
Private Sub LOS_CLICK
  Bild.Print "Testtext!"
End Sub
Starten Sie das Programm und klicken Sie auf den Knopf "Los". Auf dem Bildfeld erscheint nun - wie merkwürdig - der Text "Texttext!". Klicken Sie mehrmals auf den Knopf und Sie werden sehen, daß weitere Zeilen ausgegeben werden.
(Auch hier gilt: Wenn die AutoRedraw-Eigenschaft nicht auf "True" steht, so verschwindet der Text, sobald das Fenster verdeckt wurde!)


Abbildung 4: Textausgabe in einer PictureBox

Auf den ersten Blick scheinen die Unterschiede zwischen der DOS- und Windows-Variante von Print nicht besonders groß zu sein. Aber das stimmt nicht. Unter Windows kann der ausgegebene Text beliebig formatiert und positioniert werden. Um die Schriftart einzustellen bedient man sich dabei den bekannten Eigenschaften "FontName", "FontSize", "FontColor" usw. Die Position läßt sich über zwei weitere Eigenschaften des Bildfeldes setzen oder auch abfragen:
<Objekt>.CurrentX <Wert>
<Objekt>.CurrentY <Wert>
Mit CurrentX kann man die X- und mit CurrentY die Y-Position des Texts ändern. Die Angaben erfolgen dabei, wie üblich, in der gewählten Einheit. Auch dies kann man einfach ausprobieren (Dazu bitte die Scale-Eigenschaft auf Pixel setzen!):
Private Sub LOS_CLICK
  Bild.CurrentX=0
  Bild.CurrentY=0
  Bild.Print "Position: 0/0"
  Bild.CurrentX=20
  Bild.CurrentY=20
  Bild.Print "Jetzt an Position 20/20!"
End Sub
Nachdem Sie diesmal auf Los geklickt haben erscheint die folgende Ausgabe: Die Ausgabe spricht für sich. Auch die Formatierung der Schrift mit den FontXXXX-Eigenschaften ist kein größeres Problem und lassen sich auf die gleiche Weise testen:
Private Sub LOS_CLICK
  BILD.FontName="Arial"
  BILD.FontSize=50
  BILD.Print "50"
  BILD.FontSize=20
  BILD.Print "20"
End Sub


Abbildung 5: Positionieren von Text

Es gibt noch zwei weitere Methoden oder Funktionen, die für den Umgang mit grafischen Texten sehr hilfreich sind: TextWidth und TextHeight. Die Funktionen geben jeweils die Breite bzw. Höhe eines bestimmten Textes zurück. Dadurch kann man beispielsweise einen Text zentriert ausgeben.
<Wert> = <Objekt>.TextWidth(<Text>)
<Wert> = <Objekt>.TextHeight(<Text>)
Diese Funktionen können wir zur Demonstration einfach in das obige Programm einbauen:
Private Sub LOS_CLICK
  BILD.FontName="Arial"
  BILD.FontSize=50
  BILD.CurrentX = (BILD.ScaleWidth - BILD.TextWidth("50")) / 2
  BILD.Print "50"
  BILD.FontSize=20
  BILD.CurrentX = (BILD.ScaleWidth - BILD.TextWidth("20")) / 2
  BILD.Print "20"
End Sub
Bevor der Text ausgegeben wird, errechnen wir mit TextWidth die Breite des Textes. Diese ziehen wir von der Breite des Textfelds ab und teilen das Ergebnis durch zwei. Damit bekommen wir die X-Koordinate, an der der Text ausgegeben werden muß, damit er zentriert im Bildfeld erscheint. Genauso kann man auch den Text auf der Y-Achse zentrieren:
Private Sub LOS_CLICK
  BILD.FontName="Arial"
  BILD.FontSize=80
  BILD.CurrentX = (BILD.ScaleWidth - BILD.TextWidth("50")) / 2
  BILD.CurrentY = (BILD.ScaleHeight - BILD.TextHeight("50")) / 2
  BILD.Print "50"
End Sub
Noch eins zum Schluß: Grafische Texte werden transparent ausgegeben. Das heißt, haben Sie ein Bild im Hintergrund, so wird der Text darauf geschrieben und es erscheint nicht etwa ein weißer Kasten oder ähnliches. Dies ist besonders praktisch, wenn man eine Zeichnung beschriften möchte.

2.2 API für Alle!  

Wie Sie sehen hat VisualBasic zwar einiges auf Lager, wenn's um Grafik geht, fehlen entscheidende Befehle. Zum Beispiel ist es nicht möglich, einen Bildausschnitt zu kopieren oder zu verschieben. Darüber hinaus können keine Polygone gezeichnet werden. Diese Mißstände lassen sich aber zum Glück beheben.
Die Windows API (Application Programming Interface) kann man sich als große Bibliothek vorstellen, in der eine Menge Befehle für jedes Programm bereitstehen. Diese Funktionen umfassen nicht nur die schon angekündigten Grafikroutinen sondern auch Befehle um Fenster zu erzeugen, zu verändern usw. Uns interessiert hier jedoch nur der Grafikteil.
Um eine Funktion aus der API zu benutzen muß diese erst einmal deklariert werden. Die Deklarationen setzt man für gewöhnlich in eigene Module. Legen Sie also ein neues Code-Modul an. In den Deklarationsteil des Moduls schreibt man nun die Anweisungen.

Declare Sub <Name> Lib <Datei> (<Parameter>)
Declare Sub <Name> Lib <Datei> (<Parameter>) As <Typ>
Um eine API-Routine deklarieren zu können müssen wir mehrere Sachen über sie wissen: Handelt es sich um eine SUB oder um eine Funktion? Ist es eine Funktion, welchen Rückgabedatentyp hat sie dann? Welche Parameter benötigt sie und von welchen Datentypen sind diese?
Damit wir nicht zu tief in diese Materie einsteigen müssen stelle ich Ihnen hier einige Funktionen vor, die Sie direkt in Ihr Programm übernehmen können.

Option Explicit

Public Declare Function Polyline Lib "Gdi32" (ByVal _
       hDC As Long, Punkte As POINTAPI, ByVal _
       Anzahl As Long) As Long

Public Declare Function Polygon Lib "Gdi32" (ByVal _
       hDC As Long, Punkte As POINTAPI, ByVal _
       Anzahl As Long) As Long

Public Declare Function BitBlt Lib "Gdi32" (ByVal _
       NachHdc As Long, ByVal X As Long, ByVal _
       Y As Long, ByVal w As Long, ByVal h As Long, ByVal _
       vonHdc As Long, ByVal vonX As Long, ByVal _
       vonY As Long, ByVal Modus As Long) As Long

Public Declare Function StretchBlt Lib "Gdi32" (ByVal _
       NachHdc As Long, ByVal X As Long, ByVal _
       Y As Long, ByVal w As Long, ByVal h As Long, ByVal _
       vonHdc As Long, ByVal vonX As Long, ByVal _
       vonY As Long, ByVal vonW As Long, ByVal vonH As _
       Long, ByVal Modus As Long) As Long

Public Type POINTAPI
  X As Long
  Y As Long
End Type

Public Const BIT_COPY = &HCC0020
Public Const BIT_AND = &H8800C6
Public Const BIT_INVERT = &H660046
Public Const BIT_BLACK = &H42&

Listing 1: Deklarationen

(Die langen Absätze Declare... müssen natürlich jeweils in eine Reihe geschrieben werden! Ansonsten erhalten Sie eine Fehlermeldung. Die Deklarationen finden Sie aber auch in den Beispieldateien, die dem Kurs beiliegen - so können Sie sich die Tipparbeit ersparen)
Nachdem Sie nun ein Codemodul mit den Deklarationen erstellt haben können wir uns wieder der Prozedur LOS_CLICK widmen. Als erstes besprechen wir jetzt die Funktion zum Zeichnen eines Polygons:

2.3 Polygone  

<Ergebnis> = Polygon(<Handle>, <PunkteFeld>, <Punkteanzahl>)
Der Parameter Handle gibt den sogenannten DeviceContext an. Hier gibt man immer die hDC-Eigenschaft des Objekts an. Also in unserem Fall BILD.hDC. Was es damit im einzelnen auf sich hat soll hier nicht besprochen werden. Sie müssen nur daran denken, daß Sie bei API-Funktionen das Bildfeld immer mit der hDC-Eigenschaft übergeben. Der nächste Parameter ist der erste Eintrag eines Datenfelds, das die Punkte angibt. Da ein Polygon ja aus mehreren Punkten bestehen kann benutzt man ein Datenfeld. Dazu kommen wir aber gleich noch. Der letzte Parameter gibt die Anzahl der Punkte an, die gezeichnet werden sollen. Die Funktion liefert auch einen Wert zurück. Dieser gibt an, ob das Polygon erfolgreich gezeichnet wurde. Hier nun ein Beispiel, das wir anschließend besprechen:

Private Sub Los_Click
  ReDim P(4) As POINTAPI

    P(0).X = 0
    P(0).Y = 0

    P(1).X = 90
    P(1).Y = 10

    P(2).X = 80
    P(2).Y = 90

    P(3).X = 10
    P(3).Y = 80

    P(4).X = 0
    P(4).Y = 0

    Call Polygon(Bild.hDC, P(0), 5)
    Bild.Refresh
End Sub

Listing 2: Polygone zeichnen

Testen Sie erst einmal dieses Programm und Sie werden sehen, daß ein Polygon mit vier Eckpunkten auf dem Bildfeld erscheint.
In der ersten Zeile des Programms wird ein Datenfeld (erinnern Sie sich noch an Datenfelder? Wenn nein, lesen Sie dies bitte in den vorherigen Kursteilen nach) mit dem Namen P erstellt. Das Datenfeld hat die Einträge 0 bis 4 (also 5 Einträge) und ist vom Typ Punkt. Dieser Typ ist in dem Modul deklariert.
In den darauf folgenden Zeilen wird das Datenfeld mit Werten gefüllt. Zu jedem Eintrag gehört dabei ein X- und ein Y-Wert. Jeder Eintrag steht für einen Punkt. Die Polyline-Routine geht anschließend alle Punkte durch und verbindet Sie mit Linien.
Nun wird die eigentliche Routine aufgerufen. Ihr wird der DeviceContext von unserem Bildfeld der erste (!) Eintrag vom Datenfeld P und die Anzahl der Punkte übergeben. Zum Schluß wird noch das Bildfeld mit der "Refresh"-Methode neugezeichnet.
Mit dieser Routine können Sie natürlich auch ein Polygon mit 3 oder 5 Ecken zeichnen. Das bleibt ganz Ihnen überlassen. Nun noch eine Erweiterung: Wir können auch ein gefülltes Polygon zeichnen lassen. Dazu brauchen wir nur noch die FillStyle und FillColor-Eigenschaft des Bildfeldes mit entsprechenden Werten zu füttern:

  Private Sub Los_Click
    ReDim P(4) As POINTAPI
  
      P(0).X = 0
      P(0).Y = 0
  
      P(1).X = 90
      P(1).Y = 10

      P(2).X = 80
      P(2).Y = 90
  
      P(3).X = 10
      P(3).Y = 80
  
      P(4).X = 0
      P(4).Y = 0
  
      Bild.FillStyle=0
      Bild.FillColor=QBColor(1)
  
      Call Polygon(Bild.hDC, P(0), 5)
      Bild.Refresh
  End Sub

Listing 3: erweitertes Zeichnen von Polygonen

Das Programm zeichnet nun ein blau gefülltes Polygon.

2.4 Bilder kopieren  

Die zweite API-Funktion die ich Ihnen vorstellen möchte heißt BitBlt:

<Ergebnis> = BitBlt(<ZielHandle>, <ZielX>, <ZielY>, <Breite>, _
<Höhe>, <QuellHandle>, <VonX>, <VonY>, <Modus>
Mit BitBlt kann man einen beliebigen Ausschnitt eines Bildfeldes in ein anderes Bildfeld kopieren. Dazu benötigt BitBlt als erstes den DeviceContext des Ziels. Anschließend geben Sie die Zielkoordinaten ein. Darauf folgt die Breite und die Höhe des Ausschnitts. Die folgenden drei Parametern geben den DeviceContext der Quelle und die Koordinaten der Quelle an. Der letzte Parameter gibt das Kopierverfahren an. Benutzen Sie hier BIT_COPY zum Kopieren oder BIT_INVERT zum invertierten Kopieren.
Auch diesen Befehl möchte ich Ihnen gleich in der Praxis vorführen. Ergänzen Sie dazu die Prozedur LOS_CLICK um folgende Zeilen:
Call BitBlt(Bild.hDC, 100, 100, 100, 100, _
     Bild.hDC, 0, 0, BIT_COPY)
Bild.Refresh
Wenn Sie das Programm testen, werden Sie sehen, daß das Polygon an eine andere Stelle kopiert wurde. Es ist jetzt zweimal zu sehen. Damit kommen wir zum letzen Kapitel.

2.5 Auf die Streckbank  

BitBlt hat auch einen größeren Bruder. Dieser hat den Namen StretchBlt und kann nicht nur Bilder kopieren, sondern diese auch gleich auf eine neue Größe bringen.

<Ergebnis> = StretchBlt(<ZielHandle>, <ZielX>, <ZielY>, _
<NeueBreite>, <NeueHöhe>, _
<QuellHandle>, <VonX>, <VonY>, _
<Breite>, <Höhe>, <Modus>
Auch zu diesem Befehl eine Umsetzung in die Praxis. Ändern Sie die BitBlt-Zeile in der LOS_CLICK Prozedur durch folgende:
Call StretchBlt(Bild.hDC, 0, 0, 200, 200, _
     Bild.hDC, 0, 0, 100, 100, _
     BIT_COPY)
Nun wird das Polygon vergrößert. Damit haben wir nun die wichtigsten API-Funktionen besprochen. Ich denke, damit haben Sie schon einiges an, mit denen Sie nun ihre eigenen Programme erweitern können.

Ihre Meinung  

Falls Sie Fragen zu diesem Tutorial haben oder Ihre Erfahrung mit anderen Nutzern austauschen möchten, dann teilen Sie uns diese bitte in einem der unten vorhandenen Themen oder über einen neuen Beitrag mit. Hierzu können sie einfach einen Beitrag in einem zum Thema passenden Forum anlegen, welcher automatisch mit dieser Seite verknüpft wird.