| Semitransparente 'Forms' erstellen |
|
| Development |
| Sunday, 29 October 2006 17:58 |
|
Ausgehend von meinem letzten Beitrag zu Projekt Sunshine möchte ich heute ein paar Tipps & Tricks darüber geben, wie man den Semitransparenz-Effekt selbst produzieren kann. Gleich vorab möchte ich sagen, dass diverse Lösungsansätze nicht von mir stammen, sondern aus diversen Blogs der VFP-Community entlehnt sind. Ich habe lediglich die verschiedenen Teillösungen ein wenig zusammengeschrieben und daraus generische Komponenten erzeugt. Oder zumindest versucht... :icon_biggrin: Als Programmierbasis verwende ich VFP 9.0 SP1 und unsere Komponentenbibliothek Acodey, welche in diesem Zusammenhang lediglich einige Helfermethoden zur Verfügung stellt, welche kein Hindernis zur Verwendung ohne Acodey darstellen. Transparenz in VFP-Forms realisieren #Define GWL_EXSTYLE -20 #Define WS_EX_LAYERED 0x80000 Declare SetWindowLong In WIN32API ; Declare SetLayeredWindowAttributes In WIN32API ; Local lnOpacity Sieht ja schon mal recht einfach und effektiv aus. Nur gibt's dabei leider ein Problem. Erstens funktioniert es nur bei Top-Level-Formularen (ShowWindow = 2) und es werden sämtliche Controls auf dem Formular gleichermaßen transparent angezeigt. Sobald die Form In-Screen verwendet wird, haben die Codeanweisungen keine Wirkung. Well, zumindest kann man damit stylische Popups oder Tooltipps schreiben. Insbesondere wenn man den Opacity-Grad mittels Timer verändert. Anbei mal ein Screenshot mit dem Ergebnis:
In den mitgelieferten Solution Samples in VFP ist übrigens ein entsprechendes Beispiel zu finden. Do Home(2) + "solutionsolution.app" Dort dann unter Forms :: Transparent forms schauen. Formulare vor der Kamera Soweit sieht das ja schon ganz gut aus, aber... der eigentliche Effekt der Semitransparenz haben wir bisher noch nicht erreichen können. Und das größte Problem ist, dass die transparenten Stellen des Formulars immer transparent, also auch Controls 'verschwinden'. Bernard Bout beschreibt in seinem Artikel Visual Foxpro shows its Glass! zwar die Vorgehensweise unter Verwendung von zwei überlagerten Formularen, aber ich halte das für zu aufwendig. Letztendlich ist es Ansichtssache. Controls mit BackStyle = 0
Weiterhin stattet man dann den Container mit einigen Eigenschaften, wie ein Formular aus: Caption (mit Assign), Icon (mit Assign), Movable, nXCoord und nYCoord (wird für MouseMove gebraucht). Dazu kommen noch ein paar Methoden wie Show, Hide, EventClick und EventMouseMove und fertig ist die Basisklasse. Warum der ganze Aufwand eigentlich? Nun, der Trick hierbei liegt in der Möglichkeit, dass man auf diese Art und Weise sowohl vollständige Freiheit in der optischen Gestaltung von 'Formularen' besitzt und absolut problemlos mit Opacity in diversen Teilbereichen des Containers arbeiten kann. Den Container mit den Formfähigkeiten ausstatten Procedure This.Caption_Assign *=================================================== * Weitergabe des Parameters an das zuständige Control. *=================================================== Lparameters vNewVal This.lblCaption.Caption = m.vNewVal Procedure This.Icon_Assign This.imgIcon.Picture = m.vNewVal Dies ermöglicht sowohl im Init wie auch zu späteren Zeitpunkten die einfache Veränderung dieser beiden Eigenschaften. Als nächstes wollen wir sicherstellen, dass unser Container verschiebbar wird. Dazu implementieren wir die verschiedenen Mouse-Methoden des Shapes, welches unsere Titlebar simulieren wird: Procedure This.shpTitlebar.MouseDown LPARAMETERS nButton, nShift, nXCoord, nYCoord This.Parent.nXCoord = m.nXCoord - Objtoclient(This.Parent, OBJTOCLIENT_LEFT) Procedure This.shpTitlebar.MouseUp This.Parent.nXCoord = 0 Procedure This.shpTitlebar.MouseLeave This.Parent.nXCoord = 0 Procedure This.shpTitlebar.MouseMove This.Parent.EventMouseMove(nButton, nShift, nXCoord, nYCoord) Selbstverständlich kann dies auch per BindEvent()-Funktion gelöst werden. Dabei jedoch an den fünften Parameter (nFlags = 1) denken. Die eigentliche Umsetzung der Bewegung findet dann wieder im Container statt: Procedure This.EventMouseMove *=================================================== * Bewegung des Containers als Reaktion der Mausschubserei umsetzen *=================================================== Lparameters tnButton, tnShift, tnXCoord, tnYCoord With This Sofern der Container als beweglich konfiguriert ist, führen wir die entsprechende Aktion auch durch. Warum die Verlagerung auf den Container? Ganz einfach, es könnten ja noch weitere Controls das Moven auslösen, oder die Titlebar setzt sich aus mehreren Controls (Images oder so... ) zusammen. Für unseren Basiscotainer gehen wir von einer rechteckigen Titlebar aus und arbeiten jetzt noch ein wenig mit der ZOrder des Shapes. Das Shape sollte auf alle Fälle über dem Caption-Label und dem Icon-Image liegen, jedoch unter dem Controlbox-Image, da die Buttons ja noch klickbar sein sollen. Apropos Controlbox... dieser wenden wir uns jetzt zu. In seinem Artikel Using the Alpha Channel in Visual Foxpro Images verwendet Bernard Bout unsichtbare Commandbuttons (übrigens Style = 1 und nicht Visible = .F.) für die Realisierung des gewünschten Effekts. Das erscheint mir ehrlich gesagt überflüssig, da das Image-Control über eine Click-Methode verfügt und man mittels der Funktion ObjToClient() rausfinden kann, wo auf dem Image geklickt wurde. Das Konzept ähnelt dabei einer ImageMap aus HTML; Regionen können unterschiedliche Aktionen auslösen. In VFP-Code sieht das Ganze dann so aus: Procedure This.imgControlBox.Click *=================================================== * Click-Ereignisse auf die Grafik 'simulieren'. * Wir verwenden eine Fallunterscheidung auf Basis der Position des * Mausklicks, und entscheiden damit, welches 'Ereignis' ausgelöst wird. * Zur Orientierung bestimmen wir die linke Ecke der VistaButtons. *=================================================== With This m.lnLeftMin = ObjToClient(This, OBJTOCLIENT_LEFT) Do Case Auch hier wieder die Ursache warum ich das so mache. Erstens, braucht man weniger Controls, die zwei bzw. drei CommandButtons sind schlichtweg überflüssig und man kann auf diese Weise auch wiederum irregulare Shapes verwenden. Selbst bei Nutzung von einzelnen Grafiken zur Darstellung der Buttons braucht es lediglich deren Click-Methode. Man denke hierbei an zusätzliche Form-Funktionalitäten wie etwa Menü, Stick und Shade (Linux-Kenner wissen Bescheid). Die Reaktion auf die Buttons wird wiederum im Container in der Methode EventClick implementiert: Procedure This.EventClick *=================================================== * Reaktion auf die Click-Ereignisse der VistaButtons. *=================================================== Lparameters tcSource With This *------------------------------------------------------------- Selbstverständlich können auch andere Controls mit Click oder MouseDown-Ereignis auf diese Verarbeitungsmethode des Containers umgeleitet werden. Damit kann man auch die Synchronisierung gleichen Verhalten erreichen. Im OK- bzw. Abbrechen-Button ruft man lediglich parametrisiert This.EventClick auf und das war's. Somit haben wir die Grundfunktionalität des Containers erzeugt. Werfen wir noch einen Blick in die Init-Methode, in der einiges zusammengesteckt wird: Procedure This.Init *=================================================== * Initialisierung der Container-Form auslösen. *=================================================== Lparameters toForm With This *------------------------------------------------------------------ m.llOk = DoDefault() If m.llOk If m.llOk Return m.llOk Durch das Anchoring von VFP 9.0 werden noch ein paar Positionierungen korrigiert. Somit braucht man sich zur Designzeit nicht um solche Kleinigkeiten kümmern. Über die Eigenschaft This.lCustomTitlebar haben wir die oben bereits genannte Flexibilität, dass wir die Titlebar auch an beliebiger Stelle auf dem Form platzieren können, etwa wenn man einen Offset nach rechts oder unten braucht. Gut, der bisherige Code war erst die Pflicht, kommen wir nun zur Kür und zum eigentlich Ziel des Artikels; der Erstellung von semitransparenten Formularen. GDIPlusX aus VFPX hilft für die optische Darstellung
Procedure This.CreateBackground *=================================================== * Erstellen der 'Background'-Grafik im Fluge... mittels GDIPlusX aus VFPX * GDIPlusX wird im Hauptprogramm bereits aktiviert. * _SCREEN.AddProperty("System", ; * NEWOBJECT("xfcSystem", LOCFILE("system.vcx","vcx"))) *=================================================== With This Local llOk m.llOk = .T. If m.llOk m.llOk = DoDefault() EndIf If m.llOk m.lnWidth = This.Width * Initialize the graphics object * Make all image transparent * Draw the 'background' polygon w/ rounded corners. * Draw the 'titlebar' polygon w/ rounded corners. * Save as PNG to keep transparencies .AddObject("Background", "Image") Die Kommentare im Quellcode erklären die Vorgehensweise ausreichend. Dennoch möchte ich hier kleinere Anmerkungen zum Code geben. Die Farbgebung der beiden grafischen Elemente - Titlebar und Background - erfolgt durch die Verwendung der Container-Eigenschaften ForeColor (für Titlebar) und BackColor (für Background). Da der Container eh vollständig transparent ist, kann man die Properties bedenkenlos zweckentfremden. Zumindest spart man sich hierdurch eigene Namen und kann den Color Picker im PropertySheet verwenden. Zusätzlich habe ich entsprechende Eigenschaften für die Opacity - ForeOpacity und BackOpacity - in Prozent angelegt. Somit können wir durch Setzen von vier Eigenschaften am Container den gewünschten Effekt unserer Semitransparenz in beliebigen Farben regulieren.
Mit einer wenig Umstellung im Code könnte man auch die nachträgliche Veränderung der Hintergrundgrafik durch erneute Generierung realisieren. Ob das sinnvoll ist, sei dem Entwickler bzw. dem Anwender selbst überlassen. Verwaltung der 'Forms' an zentraler Stelle Schaut man sich übrigens die Vorgehensweise bei VFP-Forms an, erkennt man auch sehr schnell, dass diese ebenfalls über den _Screen verwaltet werden: _Screen-Forms ist ein Array mit sämtlichen Referenzen der Formobjekte. Und durch Nachbildung dieser Funktionalität erreichen wir das gleiche Resultat. Wir aggregieren unsere Container ebenfalls an das _Screen-Form(set) und legen die Verwaltung in einer Collection (anstelle eines Arrays) ab. Dadurch werden die Containerinstanzen an eine globale Stelle aggregiert und stehen in der gesamten Anwendung jederzeit zur Verfügung. Durch Vergabe von dynamischen Namen können wir ebenfalls stressfrei multiple Instanzen der gleichen Containerklasse erzielen und nutzen. Dazu erstellen wir uns einen generischen Container-Loader, der diese Aufgabe übernimmt. Die Loaderklasse erstellen wir übrigens in einem PRG namens container.prg. :icon_twisted: Auf diese Weise können wir unsere Containerobjekte über zwei gleichwertige Wege erzeugen: In Analogie zum Aufruf von VFP-Formularen mittels DO Form <SCX> WITH <Parameter> bietet unser Programm eine vergleichbare Funktionalität gemäß folgender Syntax: DO Container With <Klasse>, <Bibliothek>, <Parameter> Auf Grund der Aggregation des ContainerLoaders an _Screen können wir ebenfalls folgenden Aufruf nutzen: _Screen.Containers.LoadContainer(<Klasse>, <Bibliothek>, <Parameter>) Durch die Verwendung einer Collection für unsere Container anstelle eines Arrays wie bei _Screen.Forms haben wir die Möglichkeit sowohl über einen Index als auch über einen Schlüssel (Namen) auf die Objektreferenz zuzugreifen.
|









