Offensichtlich ist es nicht möglich, ein modal angezeigtes Form zu verbergen, ohne die Modalität aufzuheben. Denn es reicht schon das Verbergen durch den Aufruf der Hide-Methode oder durch das Setzen der Visible-Eigenschaft auf False, um die Modalität aufzuheben, so dass der dem modalen Show-Aufruf folgenden Code weiter ausführt wird.
Ein Szenario für ein nicht sichtbares, aber dennoch modales Form wäre etwa ein "Abbrechen"-Form während der Ausführung eines länger andauernden Vorgangs, das erst ab einer bestimmten Dauer des Vorgangs sichtbar werden, anderenfalls jedoch gar nicht erst angezeigt werden und den Anwender somit auch nicht "belästigen" soll.
Eine triviale Möglichkeit der Lösung wäre, das eigentlich doch angezeigte Form vorübergehend aus dem Bildschirm hinaus zu schieben:
Me.Move Screen.Width, Screen.Height
Doch angesichts von Mehrfachbildschirmen und Tools und Grafik-Treibern zur Organisation von virtuellen Bildschirmen ist das womöglich keine gute Idee: Das modale Form könnte damit einfach bloß auf einen anderen "Bildschirm" verschoben werden.
Eine andere, elegantere Möglichkeit bietet das Windows-API mit der Funktion SetWindowRgn. Über diese Funktion können Sie die sichtbare Fläche eines Fensters (Forms) auf Null schrumpfen lassen. Es wird augenscheinlich unsichtbar - aber genau genommen wird es transparent. Aus der Sicht der Windows- und VB-internen Verwaltung bleibt es jedoch "sichtbar", d.h. der Visible-Status bleibt unberührt - und damit bleibt auch die Modalität unberührt.
Die Funktion SetWindowRgn erwartet eine so genannte "Region", die Sie mit der API-Funktion CreateRectRgn anlegen können. Nebenbei bemerkt: CreateRectRgn erzeugt eine rechteckige Region. Sie können zwar auch komplex geformte Regionen mit SetWindowRgn verwenden, doch die einfacher anzulegende rechteckige Region reicht vollauf für unseren Zweck. Da das Rechteck (die Fläche) der anzulegenden Region keine Ausdehnung haben soll, übergeben Sie der Funktion in allen Parametern den Wert 0:
Private Declare Function CreateRectRgn Lib "gdi32" _
(ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, _
ByVal Y2 As Long) As Long
Private Declare Function SetWindowRgn Lib "user32" _
(ByVal hWnd As Long, ByVal hRgn As Long, _
ByVal Redraw As Boolean) As Long
Dim nRgn As Long
nRgn = CreateRectRgn(0, 0, 0, 0)
Diese Region übergeben Sie nun der Funktion SetWindowRgn zusammen mit dem Fenster-Handle (Eigenschaft hWnd) des modal angezeigten Forms:
SetWindowRgn Me.hWnd, nRgn, True
- und das modale Form ist tatsächlich unsichtbar.
Um das Form nun doch sichtbar werden zu lassen, übergeben Sie anstelle einer Region einfach den Wert 0 (eine "Null-Region"):
SetWindowRgn Me.hWnd, 0, True
- und das modale Form wird (wieder) sichtbar.
Der letzte Parameter, in dem Sie den Wert True übergeben, sorgt dafür, dass Windows die Änderung der Region sofort nachhält und darstellt.
Um eine Kleinigkeit müssen wir uns nun aber noch kümmern. Die tatsächliche Unsichtbarkeit eines Fensters bringt es mit sich, dass weder es selbst noch die darauf platzierten Steuerelemente anklickbar sind und überhaupt auf keine Maus-Kommandos mehr reagieren. Der Aktiviert-Status als auch der Tastastur-Fokus bleiben allerdings davon unberührt: Das unsichtbare Form ist trotzdem aktiv und das Steuerelement, das den Fokus inne hat, reagiert ganz normal auf Tastatureingaben. Sie müssten also mindestens das Form im unsichtbaren Zustand sperren (Enabled = False), um unbeabsichtigte Tastatur-Eingaben zu unterbinden.
Wenn es, wie im eingangs angedeuteten Szenario nur um eine verzögerte Sichtbarkeit gehen sollte, würde es eigentlich genügen, die Enabled-Eigenschaft des Forms zur Entwicklungszeit auf False zu setzen, und dann während der Laufzeit nach dem sichtbar machen auf True zu setzen. Doch hier spielt uns VB wegen unserer Trickserei ein paar kleine Streiche (Rache ist ja bekanntlich süß...!): Auf dem sichtbar gewordenen Form hat kein Steuerelement den Fokus inne - auch nicht wie üblich dasjenige mit dem TabIndex 0. Und ein CommandButton, dessen Default-Eigenschaft gesetzt ist, erscheint und funktioniert nicht als Default-Schaltfläche.
Diese kleine Gemeinheit parieren Sie ganz einfach damit, dass Sie nach dem sichtbar Machen den Fokus ausdrücklich auf das Steuerelement, das den TabIndex 0 hat, setzen: Der Fokus ist wieder dort, wo er hingehört, und eine Default-Schaltfläche ist wieder ganz brav eine Default-Schaltfläche.
Damit ist es aber noch nicht genug der "Rache-Akte" seitens des VB-Managements. Denn wenn Sie etwa das modale Form mehrmals nacheinander verbergen und wieder anzeigen wollen (es etwa wie im zu diesem Artikel herunterladbaren Beispiel blinken lassen wollen), nützt beim zweiten Verbergen das bloße Sperren des Forms nichts mehr: Tastatureingaben kommen aus unerfindlichen Gründen trotzdem bei den Steuerelementen des Forms an. In diesem Fall müssen Sie beim Verbergen des Forms alle Steuerlemente einzeln sperren und beim sichtbar Machen wieder entsperren. Letzteres werden Sie natürlich nur bei den Steuerelementen tun wollen, die auch im sichtbaren Zustand des Forms nicht aus anderen Gründen gesperrt bleiben sollen. Dies verkompliziert den Code unter Umständen um einiges - aber schließlich sind Sie hier ja derjenige, der VB austricksen will und der daher die Mühen auf sich nehmen muss!
Keine Bange - Sie haben sich zu früh "gefreut". Verwenden Sie die folgenden Hilfsprozeduren (untergebracht in einem eigenen Standard-Modul), die alles und für alle Fälle für Sie erledigen.
Private Declare Function CreateRectRgn Lib "gdi32" _
(ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, _
ByVal Y2 As Long) As Long
Private Declare Function SetWindowRgn Lib "user32" _
(ByVal hWnd As Long, ByVal hrgn As Long, _
ByVal Redraw As Boolean) As Long
Public Sub FormTransparent(Form As Form, _
Optional DoNotTouchEnabledControls As Variant)
Dim nRgn As Long
nRgn = CreateRectRgn(0, 0, 0, 0)
SetWindowRgn Form.hWnd, nRgn, True
zEnable False, Form, DoNotTouchEnabledControls
End Sub
Public Sub FormOpaque(Form As Form, _
Optional DoNotTouchEnabledControls As Variant)
Dim nControl As Control
Dim nControls() As Control
Dim i As Integer
zEnable True, Form, DoNotTouchEnabledControls
With Form
SetWindowRgn .hWnd, 0, True
ReDim nControls(0 To .Controls.Count)
On Error Resume Next
For Each nControl In .Controls
Set nControls(nControl.TabIndex) = nControl
Next
End With
Err.Clear
For i = 0 To UBound(nControls)
With nControls(i)
.SetFocus
If Err.Number Then
Err.Clear
Else
Exit For
End If
End With
Next 'i
End Sub
Private Sub zEnable(ByVal Enabled As Boolean, Form As Form, _
Optional DoNotTouchEnabledControls As Variant)
Dim nControl As Control
Dim nControls As Collection
Dim i As Integer
Dim nControlsA() As Control
Set nControls = New Collection
If Not IsMissing(DoNotTouchEnabledControls) Then
For i = LBound(DoNotTouchEnabledControls) To _
UBound(DoNotTouchEnabledControls)
nControls.Add DoNotTouchEnabledControls(i), _
CStr(ObjPtr(DoNotTouchEnabledControls(i)))
Next
End If
On Error Resume Next
With Form
For Each nControl In Form.Controls
nControls.Add nControl, CStr(ObjPtr(nControl))
If Err.Number = 0 Then
nControl.Enabled = Enabled
End If
Err.Clear
Next
.Enabled = Enabled
End With
End Sub
Bei den Prozeduren FormTransparent und FormOpaque können Sie optional ein Array von Steuerelementen übergeben, die von der Sperrung im unsichtbaren Zustand unberührt bleiben sollen (etwa ein zur blinkenden Anzeige benötigter Timer). Die private Hilfsprozedur zEnable übergeht die in dem Array enthaltenen Steuerelemente beim Sperren bzw. Entsperren.
Diese Steuerelemente werden dazu in eine Collection eingefügt, wobei als Schlüssel jeweils der mittels der (undokumentierten) VB-Funktion ObjPtr ermittelte und in einen String konvertierte (VB-interne) Objekt-Zeiger dient. Kann beim nachfolgenden Durchlauf durch die Controls-Collection des Forms ein Steuerelement nicht auch noch in diese Collection eingefügt werden, weil es bereits darin enthalten ist, wird es übersprungen und dessen Enabled-Eigenschaft bleibt unverändert.
Um den Fokus nach dem sichtbar Machen in der Prozedur FormOpaque auf das erste mögliche Steuerelement zu setzen (es ist ja nicht garantiert, dass das Steuerelement mit dem TabIndex 0 tatsächlich auch selbst sichtbar und entsperrt ist), werden alle Steuerelemente anhand ihres TabIndex in ein Array eingefügt (Steuerelemente ohne TabIndex-Eigenschaft werden dabei per Fehlerbehandlung mit "On Error Resume Next" einfach übersprungen). Anschließend wird in einer Schleife versucht, beginnend beim ersten Element (Index 0) des Arrays, den Fokus auf das im jeweiligen Element abgelegte Steuerelement zu setzen. Klappt das nicht, etwa weil das betreffende Steuerelement unsichtbar oder gesperrt ist, oder weil es nicht über eine Enabled-Eigenschaft verfügt, wird die Schleife weiter durchlaufen. Im Erfolgsfall wird die Schleife verlassen - und der Fokus ist gesetzt.
|