Version 2.0!
Features
Tutorials
Files
Glossary
Projects
Contact
Links
Message Board
Extras
LuckyCam
Old News
Sign Guestbook
View Guestbook
VB Horoscope
VB Photo Album
.
ATTENTION READERS! Lucky's VB Gaming Site is no longer active. For updated game programming information and tutorials, please visit The Game Programming Wiki!

Effizientes Zeichnen und Textausgabe

Ich weiß, was Anfänger jetzt wahrscheinlich denken: "Bei DirectX sind doch schon einfache Methoden, um Linien zu zeichnen oder Texte auszugeben, dabei, wir brachen kein Tutorial dafür!", aber die DirectX Methoden sind ineffizient! Ich hab' keine Ahnung was MS sich dabei gedacht hat, aber jedesmal, wenn Ihr die DirectDrawSurface7.DrawLine oder DirectDrawSurface7.DrawText Methode aufruft, sichert (lock) ihr die Surface und gebt sie wieder frei (unlock) ! Das ist zwar ganz schön, wenn ihr nur ein oder zwei Dinge zeichnen wollt... aber wenn ihr größere Menge zeichnen müsst, werdet ihr den Nachteil erkennen. Denn ihr müsst die Surface jedesmal sichern und wieder freigeben - eine teure Operation. Denn es besteht kein Zweifel, dass die verlorenen Mikrosekunden sich summieren werden.

Die Alternative? GDI! Ihr denkt jetzt vielleicht, dass API-Funktionen immer langsamer als DirectX-Funktionen sind, aber das stimmt nicht. Durch die Benutzung der Zeichen-Funktionen, die in Windows' GDI enthalten sind, könnt Ihr verhindern, dass die Surfaces zu oft gesichert und wieder freigegeben werden. Sichert die Surface nur einmal (um an ihren DC zukommen), gebt sie nur einmal wieder frei (um ihren DC freizugeben) und erledigt alle anfallenden Operationen dazwischen.

Jetzt lernen wir die API-Aufrufe kenn, um Text oder Linien zu zeichnen.

Private Declare Function LineTo Lib "gdi32" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long) As Long

Fangen wir mit etwas einfachem an :) Die LineTo-Funktion zeichnet eine Linie von den aktuellen X-,Y-Koordinaten zu den Koordinaten, die beim Aufruf übergeben werden. Aber wo sind diese "aktuellen" Koordinaten? Nun, hierbei handelt es sich um die Koordinaten, der letzten GDI-Operation, die stattgefunden hat. Das ist zwar in einigen Fällen ganz nützlich (zum Beispiel, wenn aneinander liegende Flächen gezeichnet werden sollen), aber in den meisten Fällen ist es das nicht. Also müssen wir die Möglichkeit haben die aktuellen Koordinaten zu verändern:

Private Declare Function MoveToEx Lib "gdi32.dll" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long, lpPoint As POINT_TYPE) As Long
Private Type POINT_TYPE
    x As Long
    y As Long
End Type

Übergebt die Koordinaten, die Ihr als aktuelle Koordinaten haben wollt, einfach der MoveToEx-Funktion. Ihr müsst außerdem ein Variable vom Typ POINT_TYPE übergeben, lasst diese einfach leer.
Ok, Linien sind also ein Klacks. Aber was ist mit Kreisen?

Private Declare Function Ellipse Lib "gdi32" (ByVal hdc As Long, ByVal x1 As Long, ByVal y1 As Long, ByVal x2 As Long, ByVal y2 As Long) As Long

Eine Ellipse? Natürlich... denn ein Kreis ist nur eine spezielle Ellipse. Ihr könnt die Ellipse-Funktion für jede Ellipse (oder jeden Kreis) eurer Wahl verwenden. Die Koordinaten, die Ihr übergebt, formen ein Rechteck, welches die Ausdehnungen der Ellipse formt. Wenn Ihr einen Kreis zeichnen wollt, geht einfach sicher, das Höhe und Breite des Rechtecks gleich groß sind.

Private Declare Function Arc Lib "gdi32" (ByVal hdc As Long, ByVal x1 As Long, ByVal y1 As Long, ByVal x2 As Long, ByVal y2 As Long, ByVal X3 As Long, ByVal Y3 As Long, ByVal X4 As Long, ByVal Y4 As Long) As Long

Manchmal ist eine ganze Ellipse zuviel des Guten... dann benutzt den Kreisbogen (Arc)! Die ersten vier Argumente formen ein Rechteck, das die Form der Ellipse bestimmt. Diese Ellipse wird dann, mit Hilfe der letzten vier Parameter zurecht geschnitten. Hier wird euer Vorstellungsvermögen gefragt... stellt euch einfach eine Halbgerade (einseitig unendliche Linie) vor, die dem Mittelpunkt der Ellipse entspringt und die Koordinaten x3 und y3 schneidet. Nun stellt euch eine ähnliche Halbgerade vor, die aber x4 und y4 schneidet. Diese beiden Halbgeraden teilen die Ellipse in zwei Kreisbögen. Wenn man sich von der ersten Halbgerade aus gegen den Uhrzeigersinn zur zweiten bewegt erhält man den Teil der von der Ellipse übrig bleibt. TADAA! Ein Kriesbogen.

Private Declare Function TextOut Lib "gdi32" Alias "TextOutA" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long, ByVal lpString As String, ByVal nCount As Long) As Long

Die gute alte TextOut-Funktion. Übergeben werden müssen nur die Koordinaten, bei denen der Text gezeichnet werden soll, der String der ausgegeben werden soll und die Länge des Strings (in Zeichen). Das ist alles.

Private Declare Function SetTextColor Lib "gdi32" (ByVal hdc As Long, ByVal crColor As Long) As Long

Ohne Zweifel werden weiße Texte nach einer Weile ein bißchen langweilig... deshalb hat Gott uns auch die SetTextColor-Funktion gegeben. Übergebt Ihr einfach Wert einer Farbe (als Long-Integer), die Ihr sehen wollt, und Ihr sollt Sie sehen! Auch die VB-Konstaten, wie vbGreen und die anderen funktionieren einwandfrei.

Private Declare Function SetBkMode Lib "gdi32" (ByVal hdc As Long, ByVal nBkMode As Long) As Long
Const TEXT_TRANSPARENT = 1
Const TEXT_OPAQUE = 2

Ich weiß nicht wie es euch geht... aber mag die häßlichen, undurchsichtigen Hintergrund auf dem mein Text steht nicht. Dies könnt Ihr mit der SetBkMode-Funktion ändern. Übergebt die Konstante TEXT_TRANSPARENT an diese Funktion und die komischen Hintergründe werden euch nicht mehr stören.

Private Declare Function SetBkColor Lib "gdi32" (ByVal hdc As Long, ByVal crColor As Long) As Long

Oder vielleicht mögt Ihr diesen komischen, undurchsichtigen Hintergrung! Wer bin ich, dass ich darüber entscheide? Ihr könnt die Hintergrundfarbe mit der SetBkColor-Funktion ändern. Es funktioniert genauso wie bei der SetTextColor-Funktion außer, dass es eben die Hintergrundfarbe verändert.
Jetzt wird es ein bißchen komplizierter. Wenn Ihr vorhabt, Änderungen am "Stift" (um zum Beispiel dicke oder farbige Linien zu Zeichnen) oder der Schriftart vorzunehmen, müsst ihr einige zusätzliche Schritte beachten. Ihr könnt nicht einfach einen "Stift" (Pen) oder eine Schrift einem DC zuweisen, Ihr müsst den "Stift" oder die Schrift erst erstellen und könnt es dann in ein DC selektieren.

Private Declare Function SelectObject Lib "gdi32" (ByVal hdc As Long, ByVal hObject As Long) As Long
Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long

Ihr MÜSST unbedingt daran denken die alten Zeicheneinstellungen ("Stift"(Pen)) und die alte Schrift zu speichern und sie nach eurer Zeichenoperation wieder dem DC zuzuweisen. Außerdem muss der von euch erstellte "Pen" oder die Schrift, nach dem Ihr fertig seid, gelöscht werden. Falls Ihr dies nicht beachtet, werdet Ihr schnell feststellen, dass euer Programm ein schweres "Speicherleck" erzeugt!

Private Declare Function CreatePen Lib "gdi32" (ByVal nPenStyle As Long, ByVal nWidth As Long, ByVal crColor As Long) As Long
Const PS_SOLID = 0
Const PS_DASH = 1
Const PS_DOT = 2
Const PS_DASHDOT = 3
Const PS_DASHDOTDOT = 4

Es gibt eine Menge verschiedener Zeicheneinstellungen, die Ihr verwenden könnt... und offensichtlich könnt Ihr auch die Stärke (Breite) und Farbe eures Stifts aussuchen.

Private Declare Function CreateFont Lib "gdi32" Alias "CreateFontA" (ByVal H As Long, ByVal W As Long, ByVal E As Long, ByVal O As Long, ByVal W As Long, ByVal i As Long, ByVal u As Long, ByVal s As Long, ByVal C As Long, ByVal OP As Long, ByVal CP As Long, ByVal Q As Long, ByVal PAF As Long, ByVal F As String) As Long

Verrückt! Es gibt hier eine ganze Menge Argumente... einige werdet Ihr wohl nie ändern müssen, also werde ich nicht weiter auf sie eingehen. Trotzdem gibt es ein paar wichtige. Die ersten 2 Argumente definieren die Schrifthöhe und -breite. Das vierte steht für Fettdruck, das fünfte für Kursivschrift und das sechste für Unterstreichen. Im letzten Argument könnt Ihr den Schriftnamen als String übergeben. "Arial" oder "Courier" sind Beispiele für gültige Werte. Die restlichen Argumente können über Konstanten verändert werden (siehe Quellcode).

So, nun wißt Ihr wie diese API-Aufrufe theoretisch funktionieren. Seht euch diesen Quellcode an, um zu sehen, wie sie im richtigen Leben funktionieren!