Was John Madden und Windows Forms gemeinsam haben
Veröffentlicht: 17. Feb 2003 | Aktualisiert: 23. Jun 2004
Von Billy Hollis
Auf dieser Seite
Jenseits von Visual Basic 6.0-Formularen
Funktionsweise von ChalkTalk
Ein Crashkurs in GDI+
Speichern von Grafikoperationen
Verwalten der Maus
Zusätzlicher Code und weitere Funktionen
Schlussfolgerung
Bitte beachten Sie, dass die Kommentare der Programmierer in den Beispieldateien englischsprachig sind. Zum leichteren Verständnis wurden sie in diesem Artikel übersetzt.
Einige neue Technologien scheinen zunächst keine offensichtlichen Anwendungen zu besitzen. So wurde der Laser einmal als "eine Lösung ohne ein Problem" bezeichnet. Windows Forms verfügen über einige neue Funktionen, die die gleiche Reaktion hervorrufen können. Natürlich messe ich Windows Forms nicht die gleiche Bedeutung wie dem Laser zu. Nichtsdestotrotz: Als ich zum ersten Mal die Opacity-Eigenschaft für ein Formular sah, die ein Formular durchsichtig macht, fragte ich mich: "Welchen Zweck hat das bloß?". Einige andere Funktionen, wie etwa die TransparencyKey-Eigenschaft, schienen auf den ersten Blick ebenfalls einen sehr eingeschränkten Nutzen zu besitzen.
Als ich einige Zeit später eine Schulung durchführte, wünschte ich mir, ich könnte auf dem Bildschirm zeichnen, um etwas für die Schulungsteilnehmer hervorzuheben. Ich wollte über den bereits angezeigten Informationen freihändig zeichnen, ähnlich wie dies Sportkommentatoren bei Fernsehübertragungen tun. Mir wurde klar, dass ich unter Verwendung von Funktionen wie etwa Opacity und TransparencyKey schnell ein solches Programm schreiben könnte.
Das Ergebnis heißt ChalkTalk und dieser Artikel erklärt seine Funktionsweise. In der Regel nehme ich nicht gerne das fertige Produkt vorweg, wenn ich an Artikeln der Reihe Adventures in Visual Basic .NET schreibe, aber in diesem Fall hilft das beim Verständnis der Funktionsweise des Programms. Ich schlage vor, Sie downloaden das Programm jetzt mit Hilfe des Links am Artikelanfang und probieren es aus.
Die Downloaddatei verfügt neben dem gesamten Quellcode auch über eine ausführbare Datei mit der Bezeichnung ChalkTalk.exe. Beim Starten dieses Programms sehen Sie eine Symbolleiste im oberen linken Bildschirmbereich wie in Abbildung 1.
Abbildung 1. Die Symbolleiste für das "ChalkTalk"-Programm
Nun können Sie mit der Maus auf dem Bildschirm zeichnen. Ihre Markierungen erscheinen auf allen Elementen, die sich beim Starten von ChalkTalk auf dem Bildschirm befanden.
In der Standardeinstellung befindet sich das Programm im Freihandzeichenmodus unter Verwendung eines "Rotstifts". Sie können die Farbe und Stärke des Stifts ändern und außerdem Rechtecke oder Ellipsen zeichnen. Mit einem Radiergummi können alle aktuellen Markierungen gelöscht werden.
Wenn Sie die Symbolleiste minimieren, verschwinden die Markierungen. Nach dem Wiederherstellen der Symbolleiste werden die Markierungen wieder auf dem Bildschirm angezeigt. Da alle Mausbewegungen für das Zeichnen registriert werden, kann auf andere Programme nicht zugegriffen werden, solange die Symbolleiste angezeigt wird.
Die meisten Benutzer, die dieses Programm in Aktion erleben, fragen sich, wie es möglich ist, über anderen Programmen zu zeichnen. Wir beantworten diese Frage weiter unten, wenn wir die einzelnen Komponenten des Programms untersuchen. Zunächst fassen wir aber die Windows Forms-Technologien zusammen, die vom Programm verwendet werden.
Jenseits von Visual Basic 6.0-Formularen
ChalkTalk benötigt eine Reihe neuer Windows Forms-Technologien, die nicht in Microsoft Visual Basic® 6.0 verfügbar waren oder sich nur durch aufwändige Programmierung der Windows-API realisieren ließen. Dazu gehören:
Die Opacity-Eigenschaft von Windows Forms.
Die TransparencyKey-Eigenschaft von Windows Forms.
Die neue None-Rahmenoption für Windows Forms (FormBorderStyle-Eigenschaft).
Die TopMost-Eigenschaft von Windows Forms.
Die Opacity- und TransparencyKey-Eigenschaften wurden in meinem ersten Adventures in Visual Basic .NET-Artikel mit dem Titel Our First Adventure (in Englisch) erläutert. Wenn Sie diese Eigenschaften noch nicht in Aktion erlebt haben, können Sie diesem Artikel weitere Informationen entnehmen. Kurz gesagt macht die Opacity-Eigenschaft Formulare durchsichtig, indem ein Teil des Hintergrunds durch das Formular hindurch sichtbar wird. Die TransparencyKey-Eigenschaft macht bestimmte Teile eines Formulars für den Hintergrund völlig transparent.
Die Rahmenoption None erklärt sich von selbst. Wenn die FormBorderStyle-Eigenschaft für ein Formular auf None eingestellt ist, verfügt das Formular über keinerlei Rahmen oder Titelleiste.
Die TopMost-Eigenschaft eines Formulars ist in der Regel auf FALSE eingestellt. Wenn der Wert in TRUE geändert wird, bleibt das Formular immer im Vordergrund vor allen anderen Formularen auf dem Desktop. Gerade deshalb eignet sich diese Eigenschaft ideal für Symbolleisten, weshalb dies eine der Verwendungen in ChalkTalk darstellt.
Funktionsweise von ChalkTalk
Das Diagramm in Abbildung 2 zeigt die wichtigsten Komponenten des Programms und ihre Beziehungen zueinander. Es gibt drei Formulare, einschließlich der Symbolleiste.
Abbildung 2. Die drei Formulare von "ChalkTalk"
Die Formulare sind übereinander gestapelt. Bei allen ist die TopMost-Eigenschaft auf den Wert TRUE eingestellt, wodurch sie immer im Vordergrund vor allen anderen Elementen auf dem Desktop angezeigt werden.
Das hinterste Formular ist die Oberfläche, die zum Zeichnen der Skizzen, Rechtecke und Ellipsen auf dem Bildschirm verwendet wird. Es heißt frmDrawingSurface. Da der Rest des Desktops unter diesem Formular sichtbar ist, muss das Formular selbst zwangsläufig unsichtbar sein. Dieser Effekt wird erreicht, indem für den Hintergrund von frmDrawingSurface eine orangebraune Farbe verwendet und anschließend die TransparencyKey-Eigenschaft des Formulars auf die gleiche Farbe eingestellt wird.
Um den gesamten Bildschirm auszufüllen, wird das Formular maximiert und die FormBorderStyle-Eigenschaft auf den Wert None eingestellt. Diese Kombination macht das Formular völlig unsichtbar, mit Ausnahme der Grafiken, die darauf gezeichnet werden. Da die Grafikobjekte nicht die gleiche Farbe wie die TransparencyKey-Eigenschaft besitzen, werden diese normal angezeigt und sind nicht durchsichtig.
Das nächste Formular dient zum Erkennen von Mausaktionen. Das Formular frmDrawingSurface eignet sich dafür aus folgendem Grund nicht: Wenn Sie auf einen Teil des Formulars klicken, der mithilfe von TransparencyKey transparent gemacht wurde, klicken Sie gewissermaßen auf das Objekt hinter dem Formular "hindurch". Deshalb befindet sich unmittelbar über frmDrawingSurface ein weiteres Formular zur Erkennung der Maus. Es hat die Bezeichnung frmMouseSurface.
frmMouseSurface ist ebenfalls unsichtbar. Wie wird dies erreicht? Die Opacity-Eigenschaft des Formulars ist auf 1 % eingestellt. Dies reicht zwar nicht aus, um das Formular sichtbar zu machen, aber es ist ausreichend, um alle Mausbewegungen auf dem Bildschirm durch das Formular abzufangen.
Wie frmDrawingSurface wird dieses Formular ebenfalls maximiert, wobei FormBorderStyle auf den Wert None eingestellt ist. Aus diesem Grund entsprechen die Bildschirmkoordinaten von frmMouseSurface genau denen von frmDrawingSurface. Sobald das Formular frmMouseSurface Mausaktionen registriert, werden die für die Durchführung von Grafikoperationen benötigten Informationen an frmDrawingSurface weitergegeben, das dann das Objekt, das die Mausaktion darstellt, auf seine eigene Oberfläche zeichnet.
Die Symbolleiste dient lediglich zur Verwaltung. Sie verfügt über Schaltflächen zur Änderung der Zeichenmodi (Freihand, Rechtecke, Ellipsen) und zur Durchführung von Aktionen, wie etwa Löschen. Außerdem wird sie zum Schließen von ChalkTalk verwendet.
Ein Crashkurs in GDI+
Um die Durchführung der Zeichenoperationen nachvollziehen zu können, müssen Sie einiges über die Zeichenfunktionen von Windows Forms verstehen. Die Klassen für diese Funktionen befinden sich im Namespace System.Drawing und die Funktionalität wird kollektiv als "GDI+" bezeichnet.
Im Mittelpunkt von GDI+ steht das Graphics-Objekt. Dieses repräsentiert einen rechteckigen Bereich des Bildschirms, der einem Formular oder einem Steuerelement zugewiesen ist. Es entspricht einem Fenster-Handle in der Microsoft Windows®-API.
Ein Graphics-Objekt verfügt über Operationen zum Zeichnen von Linien, Ellipsen, Rechtecken, Text usw. auf das von ihm kontrollierte Rechteck. Einige dieser Operationen wurden im Artikel Erstellen von Visual Basic .NET-Steuerelementen erläutert. Der Zugriff auf die Operationen erfolgt über Methoden der Graphics-Klasse. Die folgenden Methoden werden in ChalkTalk verwendet:
Methode |
Argumente |
---|---|
DrawLine |
Pen-Objekt zum Zeichnen, Start- und Endpunkt für die Linie. |
DrawEllipse() |
Pen-Objekt zum Zeichnen, Eckkoordinaten des Rechtecks, in das die Ellipse gezeichnet wird. |
DrawRectangle() |
Pen-Objekt zum Zeichnen, Eckkoordinaten des Rechtecks. |
Diese Operationen werden in der Regel im Paint-Ereignis eines Formulars oder eines Steuerelements ausgeführt. Die Ereignisargumente eines Paint-Ereignisses enthalten einen Verweis auf das Graphics-Objekt, das zum Zeichnen verwendet wird. Wir könnten also eine Linie, eine Ellipse und ein Rechteck auf einem Formular mithilfe dieser Logik im Paint-Ereignis des Formulars zeichnen:
Dim p As New Pen(Color.Red) Dim g As Graphics = e.Graphics ' Eine Linie von (0,0) zu (150,150) zeichnen g.DrawLine(p, 0, 0, 150, 150) ' Unten links eine Ellipse zeichnen g.DrawEllipse(p, 0, 50, 100, 150) ' Oben rechts ein Rechteck zeichnen g.DrawRectangle(p, 50, 0, 150, 100)xx
Das Ergebnis entspricht in etwa Abbildung 3.
Abbildung 3. Das Ergebnis eines Teils der GDI+-Logik im "Paint"-Ereignis eines Formulars
Beachten Sie, dass alle verwendeten Grafikmethoden ein Pen-Objekt benötigen. Dieses Objekt enthält die Farbe und Stärke des zum Zeichnen verwendeten Stifts. Angenommen, die Linie zur Instanziierung des Pen-Objekts würde folgendermaßen geändert:
Dim p As New Pen(Color.Blue, 6.0)
Die Farbe ist jetzt blau und die Stärke des Stifts wird angegeben. Da die Standarddicke den Wert 1 besitzt, ist dieser Stift jetzt dicker als der ursprüngliche. Der gezeichnete Bildschirm entspricht nun der Abbildung 4.
Abbildung 4. Das Ergebnis der Änderung der Farbe und Stärke des zum Zeichnen verwendeten Stifts
Einige Zeichenmethoden des Graphics-Objekts verwenden ein Brush-Objekt (Pinsel) anstatt eines Stifts. Pinsel dienen dazu, Bereiche mit Farbe, Bitmaps oder Strukturen zu füllen. ChalkTalk erstellt jedoch nur Umrisse, weshalb keine Pinsel zum Einsatz kommen.
Speichern von Grafikoperationen
Da ChalkTalk in der Lage sein soll, seine Grafikobjekte erneut zu zeichnen, müssen alle Informationen festgehalten werden, die zu ihrer Erstellung erforderlich sind. ChalkTalk realisiert diese Aufgabe mit einer Reihe von Klassen. Es gibt eine Klasse für ein gezeichnetes Liniensegment, eine für eine Ellipse und eine weitere für ein Rechteck.
Alle diese Klassen erben von einem Basisobjekt mit der Bezeichnung DrawingBase. Das Basisobjekt verwaltet die Informationen, die für den Stift benötigt werden. Die Unterklassen fügen je nach Bedarf Informationen hinzu, die zum Zeichnen der Objekte benötigt werden, für die sie verantwortlich sind.
Anschließend wird eine Auflistung zum Speichern aller Zeichenobjekte verwendet. Die Auflistung wird durch frmDrawingSurface verwaltet und dient im Paint-Ereignis dieses Formulars dazu, alle Zeichnungen auf dem Formular zu aktualisieren. Rechtecke und Ellipsen bestehen nur aus einem Objekt in der Auflistung. Eine Skizze ist tatsächlich eine Folge vieler Liniensegmente, wobei jedes Liniensegment in einem separaten Objekt in der Auflistung gespeichert ist.
Der folgende Code wird für die Zeichenobjekte verwendet:
Public MustInherit Class DrawingBase Public MustOverride Sub DrawOn(ByVal grfDrawingSurface As Graphics) Private mPenColor As Color Private msngPenWidth As Single Public Property PenColor() As Color Get Return mPenColor End Get Set(ByVal Value As Color) mPenColor = Value End Set End Property Public Property PenWidth() As Single Get Return msngPenWidth End Get Set(ByVal Value As Single) If Value > 0 Then msngPenWidth = Value Else Throw New ArgumentException() End If End Set End Property End Class Public MustInherit Class DrawingShapeBase Inherits DrawingBase Private mRectangle As Rectangle Public Property Rectangle() As Rectangle Get Return mRectangle End Get Set(ByVal Value As Rectangle) mRectangle = Value End Set End Property End Class Public Class DrawRectangle Inherits DrawingShapeBase Public Overrides Sub DrawOn(ByVal grfDrawingSurface As Graphics) Dim p As New Pen(Me.PenColor, Me.PenWidth) grfDrawingSurface.DrawRectangle(p, Me.Rectangle) p.Dispose() End Sub End Class Public Class DrawEllipse Inherits DrawingShapeBase Public Overrides Sub DrawOn(ByVal grfDrawingSurface As Graphics) Dim p As New Pen(Me.PenColor, Me.PenWidth) grfDrawingSurface.DrawEllipse(p, Me.Rectangle) p.Dispose() End Sub End Class Public Class DrawLine Inherits DrawingBase Dim mStartPoint As Point Dim mEndPoint As Point Public Overrides Sub DrawOn(ByVal grfDrawingSurface As Graphics) Dim p As New Pen(Me.PenColor, Me.PenWidth) grfDrawingSurface.DrawLine(p, Me.StartPoint, Me.EndPoint) p.Dispose() End Sub Public Property StartPoint() As Point Get Return mStartPoint End Get Set(ByVal Value As Point) mStartPoint = Value End Set End Property Public Property EndPoint() As Point Get Return mEndPoint End Get Set(ByVal Value As Point) mEndPoint = Value End Set End Property End Class
Dies ist ein ziemlich gutes Beispiel einer kleinen Vererbungshierarchie, die insgesamt drei Ebenen umfasst. Die Hierarchie sieht wie in Abbildung 5 aus.
Abbildung 5. Vererbungshierarchie
Die rosafarbenen Klassen haben den Wert MustInherit. DrawingBase deklariert zum Beispiel eine Methode namens DrawOn zur Durchführung der Zeichnung, aber es implementiert diese Methode nicht. Die blau gefärbten Klassen sind solche, die tatsächlich instanziiert und verwendet werden. Sie müssen die DrawOn-Methode implementieren, um vollständig zu sein. Jede davon implementiert die Methode unterschiedlich, damit sie das korrekte Objekt auf dem Bildschirm zeichnen können.
Verwalten der Maus
Sobald frmMouseSurface ein MouseDown-Ereignis erkennt, werden Informationen zum Starten einer Zeichenoperation gesammelt. So sieht der Code aus:
Select Case Me.CurrentDrawingMode Case DrawingMode.FreeHand mnCurrentX = e.X mnCurrentY = e.Y tmrFreehandTimer.Enabled = True Case DrawingMode.Ellipse, DrawingMode.Rectangle mnCurrentX = e.X mnCurrentY = e.Y End Select
Für jede Zeichenoperation werden die Koordinaten des Anfangspunkts benötigt. Also werden diese aufgezeichnet. Für Ellipsen oder Rechtecke stellen diese Koordinaten die Anfangsecke dar. Anschließend werden die Koordinaten der Abschlussecke im MouseUp-Ergebnis aufgelistet und es wird ein Zeichenobjekt mit diesen Informationen instanziiert.
Im Freihandmodus startet Code im MouseDown-Ereignis auch einen Zeitgeber. Anschließend wird bei jedem Zeitgebertick überprüft, ob die Maus seit dem letzten Tick verschoben wurde. Falls ja, wird ein Zeichenobjekt für ein Liniensegment erstellt.
So sieht der Code für das MouseUp-Ereignis aus:
Select Case Me.CurrentDrawingMode Case DrawingMode.Ellipse Dim Rect As Rectangle Rect.X = System.Math.Min(mnCurrentX, e.X) Rect.Y = System.Math.Min(mnCurrentY, e.Y) Rect.Width = System.Math.Abs(e.X - mnCurrentX) Rect.Height = System.Math.Abs(e.Y - mnCurrentY) Dim objDrawingObject As New DrawEllipse() objDrawingObject.PenColor = Me.PenColor objDrawingObject.PenWidth = Me.PenWidth objDrawingObject.Rectangle = Rect mfrmDrawingSurface.AddDrawingObject(objDrawingObject) mbDrawingInProgress = False Case DrawingMode.Rectangle tmrFreehandTimer.Enabled = False Dim Rect As Rectangle Rect.X = System.Math.Min(mnCurrentX, e.X) Rect.Y = System.Math.Min(mnCurrentY, e.Y) Rect.Width = System.Math.Abs(e.X - mnCurrentX) Rect.Height = System.Math.Abs(e.Y - mnCurrentY) Dim objDrawingObject As New DrawRectangle() objDrawingObject.PenColor = Me.PenColor objDrawingObject.PenWidth = Me.PenWidth objDrawingObject.Rectangle = Rect mfrmDrawingSurface.AddDrawingObject(objDrawingObject) mbDrawingInProgress = False Case DrawingMode.FreeHand tmrFreehandTimer.Enabled = False End Select Me.ToolbarForm.Focus()
Die letzte Zeile in diesem Code bedarf einer zusätzlichen Erläuterung. Sobald alle Zeichenoperationen abgeschlossen sind, muss der Fokus wieder zurück auf das Formular mit der Symbolleiste verlegt werden. Wenn dies nicht geschehen würde, wäre kein Zugriff auf die Symbolleiste mit der Maus möglich, da sich diese hinter frmMouseSurface befinden würde.
So sieht der Tick-Ereigniscode des Zeitgebers aus:
Select Case Me.CurrentDrawingMode Case DrawingMode.FreeHand If mnCurrentX = Me.MousePosition.X _ And mnCurrentY = Me.MousePosition.Y Then Exit Sub End If Dim StartPoint As Point StartPoint.X = mnCurrentX StartPoint.Y = mnCurrentY mnCurrentX = Me.MousePosition.X mnCurrentY = Me.MousePosition.Y Dim EndPoint As Point EndPoint.X = mnCurrentX EndPoint.Y = mnCurrentY Dim objDrawingObject As New DrawLine() objDrawingObject.PenColor = Me.PenColor objDrawingObject.PenWidth = Me.PenWidth objDrawingObject.StartPoint = StartPoint objDrawingObject.EndPoint = EndPoint mfrmDrawingSurface.AddDrawingObject(objDrawingObject) End Select
Das Downloadprojekt enthält tatsächlich etwas mehr Code als in den verschiedenen Ereignissen weiter oben. Der zusätzliche Code zeichnet eine temporäre gestrichelte Linie an der Stelle, an der die endgültige Abbildung der Ellipse oder des Rechtecks erfolgt. Der Rahmen dieses Artikels reicht nicht zu einer ausführlichen Erklärung dieser Logik. Wenn Sie aber den vorausgegangenen Code verstehen, sollte ein Verständnis des zusätzlichen Codes nicht allzu schwierig sein.
Beachten Sie, dass alle Zeichenobjekte an frmDrawingSurface mit der AddDrawingObject-Methode übergeben werden. So sieht der Code der Methode in frmDrawingSurface aus:
Public Sub AddDrawingObject(ByVal objDrawingObject As DrawingBase) colDrawingObjects.Add(objDrawingObject) objDrawingObject.DrawOn(Me.CreateGraphics) End Sub
Diese Methode fügt einfach das neue Zeichenobjekt zu ihrer Auflistung hinzu (die beim Laden des Formulars instanziiert wurde) und weist anschließend das neue Zeichenobjekt an, sich selbst zu zeichnen.
Wenn ChalkTalk minimiert und wiederhergestellt wird, muss die gesamte Auflistung der Zeichenobjekte auf dem Bildschirm erneut gezeichnet werden. Das Formular frmDrawingSurface erledigt dies in seinem Paint-Ereignis, das wie folgt aussieht:
Private Sub frmDrawingSurface_Paint(ByVal sender As Object, _ ByVal e As System.Windows.Forms.PaintEventArgs) _ Handles MyBase.Paint Dim objDrawingObject As DrawingBase For Each objDrawingObject In colDrawingObjects objDrawingObject.DrawOn(e.Graphics) Next End Sub
Wie Sie sehen können, durchläuft das Paint-Ereignis die gesamte Auflistung und weist jedes Objekt an, sich im Graphics-Objekt des Formulars zu zeichnen.
Zusätzlicher Code und weitere Funktionen
Das Projekt enthält weiteren Code zur Verwaltung der Stifteinstellungen sowie Zeichenmodi und zur Verarbeitung von Aktionen wie dem Löschen der Symbolleiste. Jedoch handelt es sich dabei um relativ einfachen Code, der hier nicht ausführlich erläutert werden muss.
Schlussfolgerung
ChalkTalk kombiniert eine Reihe erweiterter Windows Forms-Funktionen zur Erstellung eines nützlichen Programms. Dazu gehören die Opacity-, TransparencyKey- und TopMost-Formulareigenschaften, die neue Rahmenart None für ein Formular sowie die Zeichenfunktionen von GDI+. Die Realisierung dieses Programms in Visual Basic 6.0 wäre ein Albtraum voller langwieriger API-Aufrufe gewesen und es hätte wahrscheinlich zwei Wochen oder länger gedauert, ein korrektes Arbeiten aller Funktionen zu gewährleisten (sofern dies überhaupt möglich gewesen wäre). Mithilfe von Visual Basic .NET wurde ChalkTalk als völlig neues Programm in weniger als fünf Stunden erstellt.
Es gibt eine Reihe offensichtlicher Optimierungen und Verbesserungen, die an ChalkTalk durchgeführt werden können. So könnte beispielsweise die Wiederverwendung von Pen-Objekten die Grafikoperationen beschleunigen. Ein neuer Zeichenmodus zur Eingabe von Text wäre ideal und das Speichern von Abbildungen eines gezeichneten Bildschirms wäre wünschenswert. Ich werde neue Versionen von ChalkTalk mit diesen Verbesserungen auf meiner eigenen Website unter der Adresse http://www.dotnetmasters.com (in Englisch) zur Verfügung stellen.
Neben der unglaublichen Produktivität und Flexibilität von Visual Basic .NET liegt die eigentliche Erkenntnis dieses Artikels darin, dass man immer über technische Optionen informiert sein sollte, selbst wenn Sie sich über die Art ihrer Verwendung nicht bewusst sind. Einige Funktionen sehen auf den ersten Blick wenig beeindruckend aus, können sich aber als die genau richtige Lösung für Ihr Problem erweisen.