Ein Hintergrundbild in einem Form oder einem MDI-Form ist ein netter Anblick, wenn es das Form füllt. Ein Logo, das kleiner als der Arbeitsbereich des Form ist, sieht dagegen nicht so umwerfend aus, wenn es in der linken oberen Ecke kleben muss, da die Zuweisung eines Bildes an die Picture-Eigenschaft eines Forms oder MDI-Forms keinerlei andere Positionierung zulässt. Mit dem hier beschriebenen ActiveX-Steuerelement BackPic können Sie dagegen ein Bild nicht nur automatisch in einem Form oder in einem MDI-Form zentriert anzeigen. Sondern darüber hinaus kann BackPic auch ein Bild automatisch proportional oder füllend in ein Form einpassen, oder auch die gesamte Fläche des Forms mit dem Bild gekachelt füllen.
Sie können über die Eigenschaft Style die Anzeigeart wählen und die Anzeige über die Eigenschaft Enabled ein- oder ausschalten. Der Eigenschaft Picture weisen Sie das anzuzeigende Bild zu, das allerdings zur Laufzeit ein eventuell der Picture-Eigenschaft des Forms bzw. MDI-Forms ersetzt. Damit das BackPic-Steuerelement problemlos auf einem MDI-Form platziert werden kann, ist die Eigenschaft InvisibleAtRuntime des UserControls auf True gesetzt. Wir können uns seine Oberfläche aber dennoch zunutze machen, wie Sie im folgenden sehen werden.
Die eigentliche Arbeit der Bildanzeige wird in der privaten Prozedur zRefresh erledigt, die von mehreren Stellen im Code des UserControls aus aufgerufen wird. Schauen wir uns jedoch zunächst die Implementierung der drei Eigenschaften des BackPic-Steuerelements an.
Private pEnabled As Boolean
Public Property Get Enabled() As Boolean
Enabled = pEnabled
End Property
Public Property Let Enabled(ByVal New_Enabled As Boolean)
If pEnabled <> New_Enabled Then
pEnabled = New_Enabled
If Ambient.UserMode Then
zRefresh
End If
End If
PropertyChanged "Enabled"
End Property
Der Wert der Eigenschaft Enabled wird in der privaten Variablen pEnabled festgehalten. Sie wird nur dann neu gesetzt, wenn sich ihr Wert ändern soll. Im Laufzeit-Modus des UserControls ( Ambient.UserMode = True) wird die Anzeige aktualisiert. Ist pEnabled gleich False, wird das angezeigte Bild gelöscht, andernfalls wird es angezeigt.
Private pPicture As StdPicture
Public Property Get Picture() As StdPicture
Set Picture = pPicture
End Property
Public Property Let Picture(New_Picture As StdPicture)
zSetPicture New_Picture
End Property
Public Property Set Picture(New_Picture As StdPicture)
zSetPicture New_Picture
End Property
Private Sub zSetPicture(New_Picture As StdPicture)
Set pPicture = New_Picture
If Ambient.UserMode Then
zRefresh
Else
Set UserControl.Picture = pPicture
UserControl_Resize
End If
PropertyChanged "Picture"
End Sub
Eine Änderung des in der privaten Variablen pPicture abgelegten Bild-Objekts ist dagegen nur auf sehr aufwändige Weise schlüssig zu ermitteln. Daher aktualisieren wir die Anzeige im Laufzeit-Modus jedes Mal, wenn die Eigenschaft gesetzt wird. Im Entwicklungsmodus zeigen wir das Bild im UserControl selbst an. Der Anwender des Steuerelements hat damit genau so eine optische Kontrolle darüber, welches Bild geladen ist, als wenn es in die Picture-Eigenschaft des Forms geladen worden wäre. Da in Visual Basic die Zuweisung eines Bildes zur Form-Eigenschaft Picture auch ohne das Set-Schlüsselwort erfolgen kann, ist die Picture-Eigenschaft hier sowohl mit einer Set- als auch mit einer Let-Prozedur implementiert. Diese beiden Prozeduren reichen die Übergabe einfach an die private Prozedur zSetPicture weiter, in der die eigentliche Zuweisung erfolgt.
Im Ereignis Resize des UserControls wird dafür gesorgt, dass zur Entwicklungszeit das UserControl nie größer als das Bild werden kann. Die Größe des Bildes ermitteln wir wie in "Hoch mal breit?" beschrieben. Dass die Prozedur bei den in ihr selbst verursachten Größenänderungen mehrmals durchlaufen wird, verhindern wir mit der statischen Variablen sInProc ( "Aller guten Dinge ist eins"). Damit das BackPic-Steuerelement zur Entwicklungszeit vor dem Form-Hintergrund sichtbar bleibt, wird über ein Shape-Steuerelement, dessen DrawMode-Eigenschaft auf 6 (- Invers) eingestellt ist, eine gestrichelte Umrisslinie dargestellt.
Private Sub UserControl_Resize()
Dim nPicWidth As Single
Dim nPicHeight As Single
Static sInProc As Boolean
If sInProc Then
Exit Sub
Else
sInProc = True
End If
If Not Ambient.UserMode Then
With UserControl
If Not zIsNothing(pPicture) Then
nPicWidth = .ScaleX(pPicture.Width, vbHimetric, vbTwips)
nPicHeight = .ScaleY(pPicture.Height, vbHimetric, vbTwips)
If .Width > nPicWidth Then
.Width = nPicWidth
End If
If .Height > nPicHeight Then
.Height = nPicHeight
End If
End If
shp.Move 0, 0, .ScaleWidth, .ScaleHeight
End With
End If
sInProc = False
End Sub
Die Style-Eigenschaft schließlich bestimmt die Anzeigeart des Bildes. Ihr können die vier enumerierten Werte der bpStyleConstants zugewiesen werden.
Public Enum bpStyleConstants
bpCenter
bpAdjust
bpTile
bpStretch
End Enum
Private pStyle As bpStyleConstants
Public Property Get Style() As bpStyleConstants
Style = pStyle
End Property
Public Property Let Style(ByVal New_Style As bpStyleConstants)
Select Case New_Style
Case pStyle
Case bpCenter To bpStretch
pStyle = New_Style
If Ambient.UserMode Then
zRefresh
End If
Case Else
Err.Raise 380
End Select
PropertyChanged "Style"
End Property
Hier achten wir wieder darauf, ob der zugewiesene Wert eine Änderung der Darstellung erfordert. Wird der bereits eingestellte Wert zugewiesen, passiert gar nichts. Wird ein ungültiger Wert zugewiesen, also ein Wert, der nicht in der Enumeration enthalten ist, wird der Fehler 380 (Ungültiger Wert für die Eigenschaft) ausgelöst. Ansonsten erfolgt die Zuweisung und zur Laufzeit wird die Anzeige über den Aufruf der privaten Prozedur zRefresh aktualisiert.
Beim erstmaligen Platzieren eines BackPic-Steuerelements auf einem Form wird zunächst als Voreinstellung die Variable pEnabled auf True gesetzt. Dann erfolgt eine Prüfung, ob sich das Steuerelement auf einem Form oder MDI-Form befindet, da es nur auf solchen verwendet werden kann. Ist dies nicht der Fall, wird ein Fehler mit einer entsprechenden Beschreibung ausgelöst. Anschließend wird mit einem Aufruf von zSetPicture ein eventuell bereits vorhandenes Bild in der Picture-Eigenschaft des Forms übernommen und es erfolgt eine Rückfrage, ob nach der Übernahme das Bild aus der Picture-Eigenschaft des Forms gelöscht werden soll. Die Prüfung, ob das Form bereits ein Bild enthält, erfolgt hier über die private Funktion zIsNothing. Diese Funktion stellt im Gegensatz zu einer einfachen Prüfung mit "Is Nothing" sicher, dass auch ein leeres StdPicture-Objekt erkannt wird ( "Wenn Nichts nicht Nichts ist").
Private Sub UserControl_InitProperties()
Dim nControl As Control
pEnabled = True
With UserControl
.BackColor = Ambient.BackColor
If TypeOf .Parent Is MDIForm Then
ElseIf TypeOf UserControl.Parent Is Form Then
Else
Err.Raise vbObjectError + 10000, Ambient.DisplayName, _
"BackPic kann nur auf Forms und MDIForms in Visual Basic " & _
"verwendet werden!"
Exit Sub
End If
With .Parent
For Each nControl In .Controls
If nControl.Name <> Ambient.DisplayName Then
If TypeName(nControl) = TypeName(Me) Then
Err.Raise vbObjectError + 10001, Ambient.DisplayName, _
"Es kann nur 1 BackPic-Steuerelement auf einem Form " & _
"platziert werden!"
Exit Sub
End If
End If
Next
If Not zIsNothing(.Picture) Then
zSetPicture .Picture
If MsgBox("Picture-Objekt des Forms löschen?", _
vbYesNo Or vbDefaultButton2 Or vbQuestion, Ambient.DisplayName) _
= vbYes Then
Set .Picture = Nothing
End If
End If
End With
End With
End Sub
Private Function zIsNothing(Obj As Object, _
Optional ByVal CheckHandleIfPicture As Boolean = True) As Boolean
Dim nIsNothing As Boolean
nIsNothing = CBool(Obj Is Nothing)
If Not nIsNothing Then
If CheckHandleIfPicture Then
If TypeOf Obj Is StdPicture Then
nIsNothing = Not CBool(Obj.Handle)
End If
End If
End If
zIsNothing = nIsNothing
End Function
Das Zusammenspiel des BackPic-Steuerelements mit dem Form, auf dem es platziert ist, beruht auf der in "Lausch-Eingriffe" beschriebenen Technik des Abfangens der Form-Ereignisse. Wie Sie weiter unten noch sehen werden, brauchen wir die Information, wann sich die Größe des Forms ändert.
Daher ermitteln wir hier im ReadProperties-Ereignis des UserControls den Typ des Forms und weisen ihn der entsprechenden mit WithEvents deklarierten Form-Variablen zu. Damit wir später nicht ständig zwischen beiden Möglichkeiten (Form oder MDIForm) unterscheiden müssen, weisen wir das Form auch noch der privaten Variablen mForm zu. Sie ist vom Datentyp Form - dies ist zulässig, da ein MDI-Form immer auch als vom Datentyp Form erkannt und akzeptiert wird. Speziell zur sauberen Aktualisierung eines MDI-Forms brauchen wir das Fenster-Handle des Forms. Bei MDI-Forms hat der Arbeitsbereich ein eigenes Fenster-Handle, wohingegen die hWnd-Eigenschaft eines MDI-Forms das Handle des Hauptfensters zurück gibt. Da das Handle des Arbeitsbereichs jedoch das erste Kind-Fenster des MDI-Fensters darstellt, kann es problemlos mit einem Aufruf der API-Funktion GetWindow und dem Flag GW_CHILD ermittelt werden. Bei einem normalen Form brauchen wir das Fenster-Handle nicht zu ermitteln.
Private Declare Function GetWindow Lib "user32" _
(ByVal hwnd As Long, ByVal wCmd As Long) As Long
Private mClientWnd As Long
Private mForm As Form
Private WithEvents eMDIForm As MDIForm
Private WithEvents eForm As Form
Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
Const GW_CHILD = 5
If Ambient.UserMode Then
shp.Visible = False
If TypeOf UserControl.Parent Is MDIForm Then
Set eMDIForm = UserControl.Parent
Set mForm = eMDIForm
mClientWnd = GetWindow(mForm.hwnd, GW_CHILD)
ElseIf TypeOf UserControl.Parent Is Form Then
Set eForm = UserControl.Parent
Set mForm = eForm
Else
Err.Raise vbObjectError + 10000, Ambient.DisplayName, _
"BackPic kann nur auf Forms und MDIForms in Visual Basic " & _
"verwendet werden!"
End If
Else
Set UserControl.Picture = pPicture
End If
Me.Enabled = PropBag.ReadProperty("Enabled", True)
pStyle = PropBag.ReadProperty("Style", bpCenter)
zSetPicture PropBag.ReadProperty("Picture", Nothing)
End Sub
Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
PropBag.WriteProperty "Enabled", pEnabled, True
PropBag.WriteProperty "Picture", pPicture, Nothing
PropBag.WriteProperty "Style", pStyle, bpCenter
End Sub
Wie bereits erwähnt, erfolgt die eigentliche Bearbeitung der Anzeige des Bildes in der privaten Prozedur zRefresh. Sie wird aufgerufen (wie Sie in den obenstehenden Code-Ausschnitten sicher bereits gesehen haben), wenn sich eine der Eigenschaften des BackPic-Steuerelements geändert hat. Sie muss ebenfalls aufgerufen werden, wenn sich die Größe des Forms geändert hat. Da wir sowohl für ein einfaches Form als auch ein MDI-Form eine Ereignisempfänger-Variable deklariert haben, können wir auch für beide Form-Typen das Resize-Ereignis mit den entsprechenden Ereignis-Prozeduren abfangen. Ist das Form nicht minimiert, wird dort die Prozedur zRefresh aufgerufen.
Weiterhin wird sie von der öffentlichen Methode Refresh aufgerufen, über die gegebenenfalls von außen per Code eine Aktualisierung der Anzeige angestoßen werden kann.
Private Sub eForm_Resize()
If eForm.WindowState <> vbMinimized Then
zRefresh
End If
End Sub
Private Sub eMDIForm_Resize()
If eMDIForm.WindowState <> vbMinimized Then
zRefresh
End If
End Sub
Public Sub Refresh()
zRefresh
End Sub
Kommen wir nun zur privaten Prozedur zRefresh. Die benötigten Deklarationen und Dimensionierungen der verwendeten lokalen Variablen lauten:
Private Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
Private Declare Function GetClientRect Lib "user32" _
(ByVal hwnd As Long, lpRect As RECT) As Long
Private Declare Function InvalidateRect Lib "user32" _
(ByVal hwnd As Long, lpRect As RECT, ByVal bErase As Long) _
As Long
Private Sub zRefresh()
Dim nRect As RECT
Dim nPicWidth As Single
Dim nPicHeight As Single
Dim nDestScale As Single
Dim nImageScale As Single
Dim nLeft As Single
Dim nTop As Single
Dim nRow As Long
Dim nCol As Long
Dim nScaleHeight As Single
Dim nScaleWidth As Single
Wir prüfen zuerst, ob die Eigenschaft Enabled des Steuerelements auf True gesetzt ist, weil es anderenfalls keinen Sinn hat, den Anzeigemechanismus abzuarbeiten. Ebenso ist die Bearbeitung sinnlos, wenn das Steuerelement sich nicht auf einem Form bzw. MDI-Form befinden sollte - ergibt die Prüfung der Form-Variablen mForm Nothing, wird die Prozedur gleich wieder verlassen. Die Prozedur wird auch verlassen, falls die Variable pPicture kein Bildobjekt enthalten sollte.
If pEnabled Then
If mForm Is Nothing Then
Exit Sub
End If
If zIsNothing(pPicture) Then
Exit Sub
End If
Das ganze weitere Prinzip beruht darauf, dass wir auf dem UserControl, dessen Größe nun dem Arbeitsbereich des Forms entspricht, das Bild in der gewünschten Weise abbilden und anschließend das Gesamtbild der Picture-Eigenschaft des Forms zuweisen wollen. Bei normalen Forms könnten wir zwar den Umweg über das UserControl sparen. Doch da der Performance-Unterschied entgegen aller Vermutung nicht spürbar ist, verzichten wir auf die separate Auslegung für MDI-Forms und normale Forms.
Zunächst wird das zur Laufzeit unsichtbar bleibende UserControl auf die Größe des Arbeitsbereichs des Forms gesetzt. Bei MDI-Forms ist dies der Bereich, in dem die MDI-Child-Forms geöffnet werden. Dieser Bereich wird gegebenenfalls von an den Rändern angedockten anderen Steuerelementen verkleinert. Bei gewöhnlichen Forms schränken an den Rändern angedockte Steuerelemente den Arbeitsbereich nicht ein.
With mForm
UserControl.Size .ScaleWidth, .ScaleHeight
End With
Dann ermitteln wir die Größe des Bildes und halten zur Verbesserung der Performance die Größe des Arbeitsbereichs in den lokalen Variablen nScaleWidth und nScaleHeight fest.
With UserControl
nPicWidth = .ScaleX(pPicture.Width, vbHimetric, vbPixels)
nPicHeight = .ScaleY(pPicture.Height, vbHimetric, vbPixels)
nScaleWidth = .ScaleWidth
nScaleHeight = .ScaleHeight
Nun bereiten wir das UserControl für die folgenden Anzeigeoperationen vor. Wir weisen ihm die Hintergrundfarbe des Forms zu und setzen die Eigenschaft AutoRedraw auf True. Dies ist notwendig, weil das UserControl ja nicht sichtbar ist und wir das gezeichnete Ergebnis bewahren müssen. Anschließend löschen wir mit einem Cls die alte Darstellung (die zuvor erfolgte Zuweisung der Hintergrundfarbe hätte die Darstellung nur dann gelöscht, wenn sich die Hintergrundfarbe geändert hätte).
.BackColor = mForm.BackColor
.AutoRedraw = True
.Cls
Jetzt sind wir soweit, dass wir die Einstellung der Eigenschaft Style auswerten und dementsprechend das anzuzeigende Bild aufbauen können.
Select Case pStyle
Case bpCenter
Dies ist der einfachste aller Fälle - das Bild wird einfach zentriert in die Fläche des UserControls gemalt:
.PaintPicture pPicture, (nScaleWidth - nPicWidth) \ 2, _
(nScaleHeight - nPicHeight) \ 2
Case bpAdjust
Diese Einstellung bedeutet, dass das Bild größtmöglichst in der Fläche dargestellt werden soll, ohne dass die Seitenverhältnisse geändert werden. Die Berechnung der Größe und der Position des Bildes erfolgt dem Prinzip nach dem Verfahren aus "Zwangspassnahmen":
If CBool(nScaleWidth > 0) And CBool(nScaleHeight > 0) Then
nDestScale = nScaleWidth / nScaleHeight
nImageScale = nPicWidth / nPicHeight
If nDestScale >= nImageScale Then
nPicWidth = nPicWidth / (nPicHeight / nScaleHeight)
nPicHeight = nScaleHeight
nLeft = (nScaleWidth - nPicWidth) \ 2
Else
nPicHeight = nPicHeight / (nPicWidth / nScaleWidth)
nPicWidth = nScaleWidth
nTop = (nScaleHeight - nPicHeight) \ 2
End If
.PaintPicture pPicture, nLeft, nTop, nPicWidth, nPicHeight
End If
Case bpTile
Die aufwändigste Operation ist die Kachelung, die um so länger dauert, je größer die Fläche und je kleiner das darzustellende Bild ist. Hierbei muss das Bild Zeile für Zeile und Spalte für Spalte je einmal gemalt werden.
Do While nTop < nScaleHeight
Do While nLeft < nScaleWidth
.PaintPicture pPicture, nLeft, nTop
nLeft = nLeft + nPicWidth
Loop
nLeft = 0
nTop = nTop + nPicHeight
Loop
Case bpStretch
Ähnlich einfach wie das Zentrieren ist das füllende Einpassen des Bildes in die Fläche. Dabei wird das Bild auf die volle Breite bzw. volle Höhe der Fläche gedehnt oder gestaucht. Dabei wird allerdings das ursprüngliche Seitenverhältnis des Bildes ignoriert und es wird verzerrt dargestellt.
.PaintPicture pPicture, 0, 0, .ScaleWidth, .ScaleHeight
End Select
Jetzt weisen wir die Darstellung des Bildes im UserControl der Picture-Eigenschaft des Form zu und setzen ressourcenschonend AutoRedraw wieder auf False.
Set mForm.Picture = .Image
.AutoRedraw = False
End With
Der folgende Else-Zweig wird ausgeführt, wenn die Enabled-Eigenschaft des BackPic-Steuerelements auf False gesetzt ist - die Anzeige des Bildes im Form wird gelöscht.
Else
Set mForm.Picture = Nothing
End If
Leider hat ein MDI-Form ein Problem damit, ein zur Laufzeit per Code frisch zugewiesenes Bild bzw. dessen Löschung überhaupt darzustellen - die Zuweisung eines Bildes oder von Nothing zeigt einfach keine Wirkung. Da ein MDI-Form auch nicht über eine Refresh-Eigenschaft verfügt, bleibt uns nichts anderes übrig, als über API-Funktionen die Darstellung anzustoßen. Die API-Funktion InvalidateRect teilt Windows mit, dass ein bestimmter rechteckiger Bereich eines Fensters ungültig geworden ist und löst sofort eine Neudarstellung aus, wenn ihr letzter Parameter True ist. Ehe wir uns nun langwierig mit der Umrechnung der in Twips vorliegenden Maße des Arbeitsbereichs aufhalten, ermitteln wir das Rechteck mit einem Aufruf der API-Funktion GetClientRect, bevor wir InvalidateRect aufrufen. Hier sehen Sie auch den Sinn der Variablen mClientWnd, in der wir im ReadProperties-Ereignis das Fenster-Handle des MDI-Arbeitsbereichs festgehalten haben.
If TypeOf mForm Is MDIForm Then
GetClientRect mClientWnd, nRect
InvalidateRect mClientWnd, nRect, True
End If
End Sub

|