ABOUT Visual Basic Programmieren Programmierung Download Downloads Tips & Tricks Tipps & Tricks Know-How Praxis VB VBA Visual Basic for Applications VBS VBScript Scripting Windows ActiveX COM OLE API ComputerPC Microsoft Office Microsoft Office 97 Office 2000 Access Word Winword Excel Outlook Addins ASP Active Server Pages COMAddIns ActiveX-Controls OCX UserControl UserDocument Komponenten DLL EXE
Diese Seite wurde zuletzt aktualisiert am 03.06.2003

Diese Seite wurde zuletzt aktualisiert am 03.06.2003
Aktuell im ABOUT Visual Basic-MagazinGrundlagenwissen und TechnologienKnow How, Tipps und Tricks rund um Visual BasicActiveX-Komponenten, Controls, Klassen und mehr...AddIns für die Visual Basic-IDE und die VBA-IDEVBA-Programmierung in MS-Office und anderen AnwendungenScripting-Praxis für den Windows Scripting Host und das Scripting-ControlTools, Komponenten und Dienstleistungen des MarktesRessourcen für Programmierer (Bücher, Job-Börse)Dies&Das...

Themen und Stichwörter im ABOUT Visual Basic-Magazin
Code, Beispiele, Komponenten, Tools im Überblick, Shareware, Freeware
Ihre Service-Seite, Termine, Job-Börse
Melden Sie sich an, um in den vollen Genuss des ABOUT Visual Basic-Magazins zu kommen!
Informationen zum ABOUT Visual Basic-Magazin, Kontakt und Impressum

Zurück...

Linien bunt und rhythmisch

Zurück...


Anzeige

(-hg) mailto:hg_linedda@aboutvb.de

Im einfachsten Fall zeichnen Sie Linien und Rechtecke in Visual Basic mit der Line-Methode eines Forms, einer PictureBox oder anderen ähnlichen Objekten, oder Sie verwenden das Line- bzw. Shape-Steuerelement. Die DrawStyle-Eigenschaft bietet dazu eine Reihe von Linienstilen, wie etwa gestrichelt, strichpunktiert und mehr. Der Wunsch nach wechselnden Farben, anderen "Rhythmen" oder gar feinen punktierte Linien bleibt dabei unerfüllt. Dass dies keine unerfüllbaren Wünsche zu bleiben brauchen, zeigen beispielsweise die fein punktierte Linie des überall zu sehenden Fokus-Rechtecks oder die Hierarchie-Linien im Explorer (bzw. im TreeView-Steuerelement).


Linien in beliebigen Strichelungs-Varianten und Farben, auch in beliebiger Winkellage (Abbildung in doppelter Größe)

Natürlich können Sie sich selbst behelfen und mittels Schleifen und ein paar Rechnereien solche Linien Pixel für Pixel selber malen. Bei rein horizontalen oder vertikalen Linien ist auch die Knobelei der Berechnung der Rhythmen nicht allzu kompliziert. Doch zur Berechnung der Positionen der einzelnen Pixels bei Linien in beliebigem Winkel sind schon etwas fortgeschrittenere Geometriekenntnisse erforderlich. Und selbst dabei bleibt immer noch die Frage offen, wie denn eigentlich die Pixels, deren geometrisch berechnete Koordinaten Bruchteile der Pixel-Rasterweite ergeben, zu positionieren sind, so dass die sich ergebende Linie einigermaßen glatt aussieht.

Keine Bange - und bitte auch keine Enttäuschung: Ich werde Sie hier nicht mit den dazu benötigten Algorithmen konfrontieren. Machen Sie es sich einfacher und lassen Sie auch weiterhin die Windows-Grafikmaschine für sich arbeiten, die ja offensichtlich für eine recht ausgewogene Linien-Optik zu sorgen imstande ist.

Zumindest die Berechnung der Pixel-Positionen nimmt Ihnen die (relativ unbekannte) API-Funktion LineDDA ab, so dass Sie sich nur noch um die Entscheidung zu kümmern brauchen, ob und in welcher Farbe jedes einzelne Pixel gemalt werden. Dazu liefert Ihnen die LineDDA-Funktion über eine so genannten Rückruf-Funktion der Reihe nach die Koordinaten jedes Pixels, das nach der Berechnung der Grafik-Maschine zur Darstellung der gewollten Linie zu malen wäre.

Die standardmäßige Deklaration der LineDDA-Funktion sieht wie folgt aus:

Private Declare Function LineDDA Lib "gdi32" (ByVal X1 As Long, _
 ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As Long, _
 ByVal lpLineDDAProc As Long, ByVal lParam As Long) As Long

Sie übergeben dieser LineDDA-Funktion die Start- und End-Koordinaten der Linie in Pixels, die Adresse der besagten Rückruf-Funktion, und in einem zusätzlichen Parameter beliebige weitere Informationen, die in der der Rückruf-Funktion zur eigentlichen Verrichtung der Arbeit benötigt werden. Belassen wir es zunächst einmal dabei, dass Sie in diesem letzten Parameter "lParam" anscheinend nichts anderes als einen Long-Wert mitgeben können - etwa den Geräte-Kontext, den Sie der hDC-Eigenschaft des jeweiligen Objekts, auf dem gezeichnet werden soll, entnehmen.

Was hat es nun mit dieser Rückruf-Funktion auf sich? Nun, diese ist eine Funktion beliebigen Namens, die Sie in einem Standard-Modul jedoch nach einem ganz bestimmten Schema anlegen können (genauer gesagt: dies auch tun müssen), die nicht aus Ihrem Programm heraus, sondern von Windows selbst aufgerufen werden soll. Genau das nämlich tut Windows, nachdem Sie die LineDDA-Funktion aufgerufen haben: Pixel für Pixel erfolgt jetzt ein Aufruf "aus dem Hintergrund" dieser Rückruf-Funktion, wobei Ihnen dabei die Koordinaten des jeweiligen Pixels und dazu der Wert, den Sie in dem Parameter lParam der LineDDA-Funktion abgelegt haben, übergeben werden.

Nun können Sie immerhin schon in der (beispielsweise LineDDAProc genannten) Rückruf-Funktion mittels der API-Funktion SetPixel jedes Pixel selber malen, auf dem in lParam übergebenen Geräte-Kontext:

Public Declare Function SetPixel Lib "gdi32" (ByVal hdc As Long, _
 ByVal x As Long, ByVal y As Long, ByVal crColor As Long) As Long

Private Sub LineDDAProc(ByVal x As Long, ByVal y As Long, _
 ByVal lParam As Long)

  SetPixel lParam, x, y, vbBlack
End Sub

Irgendwie langweilig, nicht wahr? Denn so entsteht nichts anderes, als eine Linie, wie Sie sie auch mit der Line-Methode hätten selber malen können. Immerhin: In Sachen Geschwindigkeit scheint dieses Pixel-für-Pixel-Verfahren trotz allem locker mithalten zu können.

Spielen wir einmal zunächst mit diesem Verfahren ein wenig herum. Einen der Eingangs genannten Wünsche können Sie sich damit nämlich schon erfüllen: das Malen einer fein punktierten Linie:

Private Sub LineDDAProc(ByVal x As Long, ByVal y As Long, _
 ByVal lParam As Long)

  Static sPaint As Boolean

  sPaint = Not sPaint
  If sPaint Then
    SetPixel lParam, x, y, vbBlack
  End If
End Sub

Was hier nun geschieht, ist ja leicht nachzuvollziehen. Bei jedem Aufruf wird die "Erlaubnis", das betreffende Pixel zu malen, durch die jeweilige Negierung der statischen Variable sPaint ein- bzw. ausgeschaltet. Somit wird abwechselnd ein Pixel gemalt oder eben nicht gemalt - und die punktierte Linie entsteht.

Ach so, ja, ich habe noch nicht erwähnt, wie der Aufruf der LineDDA-Funktion, der diese Punktier-Rückruferei anstoßen soll, auszusehen hat. Die Übergabe der Start- und End-Koordinaten und des Zusatz-Parameters ist sicher kein Problem - doch die Übergabe der Adresse der Rückruf-Funktion schon eher. Vielleicht wissen Sie es längst und kennen den AddressOf-Operator bereits, so dass Sie den nächsten Absatz überspringen können. Wenn nicht, dann lernen Sie diesen Operator, der genau für einen solchen Zweck gedacht ist, jetzt kennen.

Im Grunde ist es ganz einfach: Auch der Code eines Programms liegt irgendwo im Arbeitsspeicher, Befehl für Befehl in Maschinensprache. Nicht nur Variablen haben somit eine bestimmte Adresse im Speicher, sondern auch jede Funktion beginnt an irgendeiner Andresse. Und genau diese Adresse Ihrer Rückruf-Funktion braucht Windows, um sie aufrufen zu können:

Private Sub Form_Paint()
  LineDDA 10, 10, 50, 100, AddressOf LineDDAProc, Me.hDC
End Sub

Nebenbei bemerkt: Achten Sie darauf, dass Sie "AddressOf" mit zwei "d" schreiben - ein kleiner, aber mitunter tückischer Unterschied zwischen der deutschen und der englischen Sprache...

Befassen wir uns nun mit der bisherigen Unzulänglichkeit, der farblichen Monotonie der Pixelei. Schließlich haben wir ja den frei verwendbaren Zusatzparameter lParam zur Verfügung. Wir müssten es schaffen können, über diesen Parameter mehr als nur einen Wert, nur einen Long-Wert, transportieren zu können. Die Frage ist nur, wie das gehen soll.

Die Fortgeschritteneren unter Ihnen, die C- und API-Kenntnisse haben, werden nun vielleicht ein Array im Sinn haben, dessen erstes Element als Referenz (und damit das ganze Array) übergeben und empfangen werden könnte. In den beliebig vielen Elementen des Arrays könnten Sie ja nun beliebige Werte transportieren. Aber sonderlich elegant ist das nicht - Sie haben es mit namenlosen und nur über Index-Nummern identifizierbaren Elementen zu tun - und nur mit dem einen Datentyp des Arrays. Na ja, in einem Variant-Array könnten Sie schließlich alles transportieren.

Noch weiter fortgeschrittene werden vielleicht an die Übergabe der Adresse einer benutzerdefinierten Variable (und damit wären verschiedenste Datentypen mit intuitivem, da benanntem Zugriff möglich) und "raffinierte" Speicherkopiererei zur Rückgewinnung des Inhalts dieser Variablen aus der empfangenen Adresse in der Rückruf-Funktion denken.

Ganz so heftig wie in der zweiten angedachten Möglichkeit braucht es jedoch nicht zu werden. Und ganz so schlicht und "irgendwie unelegant" wie in der ersten Möglichkeit, braucht es ebenfalls nicht zu werden. Sie können durchaus eine benutzerdefinierte Variable verwenden. Sie müssen nämlich lediglich die Deklarationen der LineDDA-Funktion und diejenige Ihrer Rückruf-Funktion zu modifizieren. Nehmen wir in einem ersten Schritt neben dem Geräte-Kontext einen Farb-Wert mit ins Boot:

Type LineInfoType
  hDC As Long
  Color As Long
End Type

Private Declare Function LineDDALT Lib "gdi32" Alias "LineDDA" _
 (ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, _
 ByVal Y2 As Long, ByVal lpLineDDAProc As Long, _
 LineInfo As LineInfoType) As Long

Private Sub LineDDAProc(ByVal x As Long, ByVal y As Long, _
 LineInfo As LineInfoType)

  Static sPaint As Boolean

  sPaint = Not sPaint
  If sPaint Then
    With LineInfo
      SetPixel .hDC, x, y, .Color
    End With
  End If
End Sub

Und dazu der Aufruf des Ganzen:

Private Sub Form_Paint()
  Dim nLineInfo As LineInfoType

  With nLineInfo
    .hDC = Me.hDC
    .Color = vb3DShadow
  End With
  LineDDALT 10, 10, 50, 100, AddressOf LineDDAProc, nLineInfo
End Sub

Nanu, Sie bekommen die Linie nicht zu Gesicht? Okay, ich habe hier absichtlich einen kleinen Bock geschossen und bitte Sie vielmals um Vergebung. Der Hund liegt darin begraben, dass ich die Systemfarben-Konstante "vb3DShadow" angegeben habe, mit der Absicht, eine punktierte Linie in der gleichen, von den individuellen Benutzer-Einstellungen der Systemfarben abhängigen Farbe wie beim Explorer bzw. wie beim TreeView-Steuerelement zu erhalten.

Das Problem ist, dass die API-Funktion SetPixel mit den Visual-Basic-Konstanten für Systemfarben nichts anfangen kann. Sie müssen den Konstantwert erst in einen "echten" Farbwert übersetzen. Dazu dienen die API-Funktion OleTranslateColor und eine Hilfsfunktion, die deren Aufruf vereinfacht (näheres dazu finden Sie unter "Systemfarben-Dolmetscher"):

Private Declare Function OleTranslateColor Lib "oleaut32.dll" _
 (ByVal lOleColor As Long, ByVal lHPalette As Long, _
 ByRef lColorRef As Long) As Long

Private Function OleConvertColor(ByVal Color As Long) As Long
  Dim nColor As Long
  
  OleTranslateColor Color, 0&, nColor
  OleConvertColor = nColor
End Function

Die Fütterung der LineInfo-Variablen vor dem Aufruf müsste daher so aussehen:

  With nLineInfo
    .hDC = Me.hDC
    .Color = OleConvertColor(vb3DShadow)
  End With

Nun aber! (Vielleicht sollten Sie noch Weiß als Hintergrundfarbe Ihres Forms wählen, damit Sie jetzt tatsächlich etwas erkennen können...)

Mit der Möglichkeit der Übergabe einer benutzerdefinierten Variablen haben wir nun ein großes Fass aufgemacht. Sie können jetzt in der Rückruf-Funktion im Prinzip anstellen, was immer Sie wollen. Sie könnten sogar so "unverschämt" sein, die Arbeit wieder in Ihr Form-Modul zurück zu verlagern und sich darauf beschränken, in der benutzerdefinierten Variablen lediglich eine Referenz auf das Form zu verschicken:

Im Standard-Modul deklarieren Sie die benutzerdefinierte Variable:

Public Type LineInfoType
  Form As Form
End Type

Der Aufruf, etwa wieder in Form_Paint:

Private Sub Form_Paint()
  Dim nLineInfo As LineInfoType

  Set nLineInfo.Form = Me
  LineDDALT 10, 10, 50, 100, AddressOf LineDDAProc, nLineInfo
End Sub

Deutlich abgemagert die Rückruf-Funktion (nach wie vor im Standard-Modul):

Private Sub LineDDAProc(ByVal x As Long, ByVal y As Long, _
 LineInfo As LineInfoType)

  LineInfo.Form.LDDA X, Y
End Sub

Die Arbeit wird nun in einer öffentlichen Prozedur des Forms erledigt, die aus der Rückruf-Funktion aufgerufen wird (gewissermaßen ist dies ein weiterer "Rückruf"). Gemalt wird hier beispielsweise in der aktuell eingestellten Vordergrundfarbe des Forms (ForeColor):

Public Sub LDDA(ByVal X As Long, ByVal Y As Long)
  Static sPaint As Boolean

  sPaint = Not sPaint
  If sPaint Then
    With Me
      SetPixel .hDC, X, Y, .ForeColor
    End With
  End If
End Sub

Übrigens, wenn Sie wollen, können Sie auch an Stelle der simplen Pixel-Setzerei hübsche Kringel malen, Buchstaben ausgeben, Linien quer zur Laufrichtung der wegen der bloßen Übergabe von Koordinaten eigentlich ja nur "virtuellen" Linie zeichnen, oder was auch immer...

Sie werden langsam ungeduldig? Nun denn, belassen wir es bei diesem Beispiel der ungeahnten Möglichkeiten und beschränken wir uns darauf, unseren Eingangs-Wunschzettel weiter abzuhaken. Als unerledigt stehen dort nämlich noch die Punkte "Rhythmen" und "Gemischte Farben".

Um verschiedene gezeichnete Längen und ausgelassene Pausen innerhalb der Linie zu erhalten, müssen Sie den gewünschten Rhythmus codieren. Am einfachsten ist es wohl, die Längen und Pausen nacheinander in einem Array zu sammeln und dabei durch das Vorzeichen festzulegen, ob gemalt (positiv) oder pausiert (negativ) werden soll.

Array(10, -3, 3, -3)

Diese Folge ergäbe eine einfache strichpunktierte Linie, indem diese Folge immer wieder so lange abgearbeitet wird, bis das Ende der zu zeichnenden Linie erreicht worden ist. Wegen der an sich unabhängigen Einzelaufrufe der Rückruf-Funktion wird der jeweilige Bearbeitungsstand neben diesem "Strichcode"-Array in weiteren Elementen der benutzerdefinierten Variable mitgeführt.

Private Type LineInfoType
  hDC As Long
  Color As Long
  Count As Long
  Steps As Long
  StepCount As Long
  Step As Variant
End Type

Die ersten beiden Elemente, hDC und Color, bleiben erhalten. Hinzu kommen zunächst eine Zählstelle (Count) und die Anzahl der Gesamtschritte (Steps). Diese Anzahl ist eigentlich nichts anderes als die Anzahl der Elemente des Arrays (UBound +1), die allerdings aus Geschwindigkeitsgründen als fester Wert zusätzlich abgelegt wird. Das nächste Element (StepCount) hält fest, welcher Abschnitt des "Strichcode"-Arrays gerade bearbeitet wird. Und im letzten Element (Step) wird schließlich das "Strichcode"-Array abgelegt.

Die Rückruf-Funktion dazu sieht nun so aus:

Private Sub LDDAProc(ByVal x As Long, ByVal y As Long, _
 LineInfo As LineInfoType)

  With LineInfo
    If Sgn(.Step(.StepCount)) = 1 Then
      SetPixel .hDC, x, y, .Color
    End If
    .Count = .Count + 1
    If .Count Mod Abs(.Step(.StepCount)) = 0 Then
      .Count = 0
      .StepCount = .StepCount + 1
      If .StepCount Mod .Steps = 0 Then
        .StepCount = 0
      End If
    End If
  End With
End Sub

Sie können die Elemente der benutzerdefinierten Variablen manuell vor dem Aufruf der LineDDALT-Funktion belegen. Sie können dies aber auch durch die Vorschalt-Prozedur LineDraw erledigen lassen, der sie alle benötigten Werte in einem Rutsch übergeben. Einige Werte sind dabei optional und werden in der Prozedur gegebenenfalls vereinfacht automatisch eingesetzt. Zusätzlich können Sie über diese Vorschalt-Prozedur auch Rechtecke zeichnen lassen:

Public Sub LineDraw(ByVal hDC As Long, ByVal X1 As Long, _
 ByVal Y1 As Long, Optional ByVal X2 As Variant, _
 Optional ByVal Y2 As Variant, _
 Optional ByVal Color As OLE_COLOR = vbWindowText, _
 Optional ByVal Box As Boolean, Optional Steps As Variant)

  Dim nLineInfo As LineInfoType
  Dim nX2 As Long
  Dim nY2 As Long
  
  With nLineInfo
    .hDC = hDC
    .Color = OleConvertColor(Color)
    If IsMissing(Steps) Then
      .Steps = 2
      .Step = Array(1, -1)
    Else
      .Steps = UBound(Steps) + 1
      .Step = Steps
    End If
    If IsMissing(X2) Then
      nX2 = X1
    Else
      nX2 = X2
    End If
    If IsMissing(Y2) Then
      nY2 = Y1
    Else
      nY2 = Y2
    End If
    If Box Then
      LineDDALT X1, Y1, nX2, Y1, AddressOf LDDAProc, nLineInfo
      LineDDALT nX2, Y1, nX2, nY2, AddressOf LDDAProc, nLineInfo
      LineDDALT nX2, nY2, X1, nY2, AddressOf LDDAProc, nLineInfo
      LineDDALT X1, nY2, X1, Y1, AddressOf LDDAProc, nLineInfo
    Else
      LineDDALT X1, Y1, nX2, nY2, AddressOf LDDAProc, nLineInfo
    End If
  End With
End Sub

Wie Sie sehen, brauchen sie sich auch nicht mehr um die gegebenenfalls notwendige Konvertierung von Systemfarben-Konstanten zu kümmern.

Ein Aufruf-Beispiel - als Ergebnis erhalten Sie die horizontale graue Linie "langer Strich - drei kurze Striche" in der oben stehenden Abbildung:

With Me
  LineDraw .DC, 0, 40, .ScaleWidth - 1, , vbGrayText, , _
   Array(20, -5, 5, -5, 5, -5, 5, -5)
End With

Bloß eine durchgängig grau gemalte strichpunktierte Linie? Wo die wechselnden Farben bleiben, fragen Sie? Ganz einfach - die Linien werden mehrfach abgearbeitet, mit entsprechenden "gegenläufigen" Auslassungen codiert. Das LineDDA-Prinzip ist allemal schnell genug dazu, während eine komplexere Struktur, etwa mit einem mehrdimensionalen Array, mit den dann notwendig werdenden aufwändigeren Fallunterscheidungen auch nicht spürbar schneller werden würde.

With Me
  nDC = .hDC
  nScaleWidth = .ScaleWidth - 1
  nScaleHeight = .ScaleHeight - 1

  LineDraw nDC, 0, 60, nScaleWidth, , vbCyan, , _
   Array(20, -5, -5, -5, -5, -5, -5, -5)
  LineDraw nDC, 0, 60, nScaleWidth, , vbGrayText, , _
   Array(-20, -5, 5, -5, 5, -5, 5, -5)
End With

Die deckungsgleiche Codierung der Abschnitte, die sich lediglich in den Vorzeichen unterscheidet, erleichtert die Schreibarbeit. Selbstverständlich können Sie das auch noch ein wenig optimieren, indem sie unmittelbar aufeinander folgende Abschnitte mit gleichem Vorzeichen zu einem Abschnitt zusammenfassen:

  LineDraw nDC, 0, 60, nScaleWidth, , vbCyan, , Array(20, -35)
  LineDraw nDC, 0, 60, nScaleWidth, , vbGrayText, , Array(-20, -5, 5, -5, 5, -5, 5, -5)

Beispiel-Projekt und Modul modLineDDA (linedda.zip - ca. 3,4 KB)


Artikel
Zum Download-Bereich dieses Artikel
Mail an den Autor dieses Artikels

KnowHow
Zur KnowHow-Übersicht

KnowHow-Themen
Themen - Allgemeines
Themen - Entwicklungsumgebung (VB-IDE)
Themen - Forms
Themen - Steuerelemente (Controls)
Themen - Grafik
Themen - Dateien
Themen - UserControls
Themen - Einsteiger-Tipps
Themen - Wussten Sie...?

Übersicht nach Titeln in alphabetischer Reihenfolge
Übersicht nach Erscheinungsdatum

Schnellsuche




Zum Seitenanfang

Copyright © 1999 - 2017 Harald M. Genauck, ip-pro gmbh  /  Impressum

Zum Seitenanfang

Zurück...

Zurück...

Download Internet Explorer