Im Gegensatz zu einem gewöhnlichen Form bietet Ihnen ein MDI-Form keine Tasten-Ereignisse. Wenn kein MDI-Kind-Form geöffnet ist, kann Ihre Anwendung somit nicht auf Tastatureingaben reagieren. Zwar könnten Sie irgendein Steuerelement auf dem MDI-Form unterbringen, das den Fokus innehaben kann und so ersatzweise Tastenanschläge annehmen könnte. Aber vielleicht soll Ihr MDI-Form eben gerade kein weiteres, sichtbares Steuerelement enthalten.
Die naheliegendste Lösung wäre ein sichtbares, aber durchsichtiges Steuerelement, das den Fokus auf sich zieht, wenn das MDI-Form das aktive Fenster ist und kein Kind-Form angezeigt wird. Immerhin können Sie etwa bei einem UserControl die Eigenschaft BackStyle auf "0 - Transparent" setzen. Doch sobald Sie es auf einem MDI-Form platzieren (und zwangsläufig müssen Sie es am Rand des Arbeitsbereichs andocken), erscheint zur Laufzeit eine graue Fläche - egal, wie schmal Sie das Steuerelement auch machen. Es hilft auch nichts, auf die Durchsichtigkeit zu verzichten und statt dem Steuerelement dessen die Farbe des Arbeitsbereichs zu geben - die trennende Kante des 3D-Rahmens bleibt weiterhin unschön verschoben. Wegen des Andockzwangs für ein sichtbares Steuerelement können Sie dieses jedoch auch nicht irgendwo innerhalb des Arbeitsbereichs unterbringen.
Der Weg führt über ein unsichtbares und durchsichtiges UserControl (Eigenschaft InvisibleAtRuntime auf True gesetzt). Auch wenn das Steuerelement dann über keine Visible-Eigenschaft verfügt und auch die SetFocus-Methode nicht mehr zulässig ist, können Sie zur Laufzeit die VB-eigene Verwaltung über API-Funktionen austricksen. Ein Aufruf der API-Funktion ShowWindow lässt es "sichtbar" werden. Es bleibt trotzdem durchsichtig, ist aber nun dazu bereit, den Fokus erhalten zu können. Dann verschieben Sie es mittels der SetParent-Funktion aus dem Arbeitsbereich des MDI-Forms (der ein eigenes Fenster ist) in das Fenster des MDI-Forms und entziehen es so der VB- und MDI-Verwaltung. Nun geben Sie ihm noch mit der API-Funktion SetFocus (als Alias "SetFocusAPI" deklariert, um dem Konflikt mit der VB-Methode SetFocus zu entgehen) den Fokus. Und schon kommen die Tastatureingaben in den Key-Ereignissen des UserControls an, die sie nun selbst als Events nach außen an das MDI-Form weiterreichen können. Und wenn nun ein Kind-Form geöffnet wird, das die Tastatureingaben selbst annehmen soll, verliert das UserControl automatisch den Fokus - Sie brauchen sich gar nicht weiter darum zu kümmern.
Public Event KeyDown(KeyCode As Integer, Shift As Integer)
Public Event KeyPress(KeyAscii As Integer)
Public Event KeyUp(KeyCode As Integer, Shift As Integer)
Private Declare Function GetFocus Lib "user32" () As Long
Private Declare Function GetForegroundWindow Lib "user32" () _
As Long
Private Declare Function SetFocusAPI Lib "user32" _
Alias "SetFocus" (ByVal hwnd As Long) As Long
Private Declare Function SetParent Lib "user32" _
(ByVal hWndChild As Long, ByVal hWndNewParent As Long) _
As Long
Private Declare Function ShowWindow Lib "user32" _
(ByVal hwnd As Long, ByVal nCmdShow As Long) As Long
Public Sub Show()
Const SW_NORMAL = 1
ShowWindow .hwnd, SW_NORMAL
SetParent .hwnd, .Parent.hwnd
SetFocusAPI .hwnd
End Sub
Private Sub UserControl_KeyDown(KeyCode As Integer, _
Shift As Integer)
RaiseEvent KeyDown(KeyCode, Shift)
End Sub
Private Sub UserControl_KeyPress(KeyAscii As Integer)
RaiseEvent KeyPress(KeyAscii)
End Sub
Private Sub UserControl_KeyUp(KeyCode As Integer, _
Shift As Integer)
RaiseEvent KeyUp(KeyCode, Shift)
End Sub
Soweit funktioniert das schon ganz gut. Nun ist noch eine nächste Aufgabe zu lösen. Wenn nämlich ein anderes Anwendungsfenster aktiviert wird, verliert nicht nur das MDI-Form seinen Aktivitätsstatus, sondern auch das UserControl verliert den Fokus. Wird das MDI-Form wieder aktiv, bekommt das UserControl jedoch nicht automatisch wieder den Fokus. Und da es in VB (außer per relativ aufwändigem Subclassing) keine Möglichkeit gibt, den Wechsel von einer anderen Anwendung zur eigenen Anwendung zu erkennen, müssen Sie auf andere Weise dafür sorgen, dass das UserControl wieder den Fokus erhält, so lange kein Kind-Form geöffnet ist.
Am einfachsten geht dies über einen auf dem UserControl platzierten Timer, der in mittels der API-Funktion GetForegroundWindow kurzen Abständen (Interval = 50) prüft, ob das MDI-Form aktiv ist. Ist es der Fall, wird über die API-Funktion GetFocus geprüft, ob das UserControl bereits den Fokus hat. Ist dies nicht der Fall wird das UserControl über den oben schon dargestellten Mechanismus (re)aktiviert, der in das Timer-Ereignis verschoben wird (die Show-Methode entfällt statt dessen).
Private Sub tmr_Timer()
Const SW_NORMAL = 1
With UserControl
If GetForegroundWindow() = UserControl.Parent.hwnd Then
If GetFocus <> .hwnd Then
ShowWindow .hwnd, SW_NORMAL
SetParent .hwnd, .Parent.hwnd
SetFocusAPI .hwnd
End If
End If
End With
End Sub
Aber woher weiß der Timer nun, ob Kind-Forms geöffnet sind oder nicht? Eine kleine Modifikation des Codes im Timer-Ereignis löst auch diese letzte Aufgabe: So wie über die Parent-Eigenschaft des UserControls das Handle des MDI-Forms ermittelt werden kann, können Sie auch die Eigenschaft ActiveForm des MDI-Forms prüfen. Ist diese Nothing, ist kein Kind-Form geöffnet.
Private Sub tmr_Timer()
Const SW_NORMAL = 1
With UserControl
If GetForegroundWindow() =.Parent.hwnd Then
If GetFocus <> .hwnd Then
If .Parent.ActiveForm Is Nothing Then
ShowWindow .hwnd, SW_NORMAL
SetParent .hwnd, .Parent.hwnd
SetFocusAPI .hwnd
End If
End If
End If
End With
End Sub
Der Rest ist nur noch ein wenig Kosmetik. Damit Sie das UserControl zur Designzeit bequem handhaben können, ist es dort noch nicht durchsichtig und zeigt in einem Label die Beschriftung "MDIKeys". Und auch der Timer darf zur Designzeit nicht aktiv sein.
Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
If Ambient.UserMode Then
UserControl.BackStyle = 0
lbl.Visible = False
tmr.Enabled = True
End If
End Sub
Private Sub UserControl_Resize()
If Not Ambient.UserMode Then
With lbl
UserControl.Size .Width + 2 * .Left, .Height + 2 * .Top
End With
End If
End Sub
|