|
Eine Visual Basic-MessageBox oder eine InputBox wird von Windows
automatisch etwa in der Mitte des Arbeitsbereichs des Desktops
angezeigt. Mit einem kleinen Trick können Sie jedoch eine
MessageBox (oder InputBox) recht genau an eine beliebige Position
bugsieren - indem Sie eben diesen Arbeitsbereich kurzzeitig ändern.
Sie können nämlich Windows über die API-Funktion SystemParametersInfo
einen neuen aktuellen Arbeitsbereich angeben und diesen sofort nach
dem Erscheinen der MessageBox wieder auf seine vorherige Größe
zurücksetzen.
Einen kleinen Haken hat dieser Trick allerdings: Sollte Ihre
Anwendung gerade in diesem Moment abstürzen, bevor die Rücksetzung
erfolgen konnte, bleibt der Arbeitsbereich auch für alle anderen
Anwendungen verändert - alle Fenster können dann nur noch in
diesem Bereich bewegt werden und maximierte Fenster erhalten
lediglich die Größe des veränderten Arbeitsbereichs.
Wenn Sie jedoch nicht warten, bis der modale Aufruf der
MessageBox zurückkehrt, ist die Wahrscheinlichkeit eines Absturzes
gerade in diesem Moment sehr gering. Das sofortige Rücksetzen
können Sie einem Timer mit einem extrem kurzen Intervall (1)
überlassen, der vor dem Aufruf der MessageBox gestartet (Enabled
= True) wird. Beim Bearbeiten des Timer-Ereignisses stoppt er
sich wieder selbst (Enabled = False) und sorgt für die
Wiederherstellung des Arbeitsbereichs. Natürlich müssen Sie
letzteren ebenfalls vor dem Aufruf der MessageBox erst einmal
sichern. Hier tritt übrigens ein interessanter Unterschied zwischen
der Laufzeit einer Anwendung in der Entwicklungsumgebung und der
Laufzeit als kompilierte, ausführbare Datei (EXE) zutage: Im
IDE-Lauf ist die MessageBox 100%ig modal - das
Timer-Ereignis wird erst nach der Rückkehr des MessageBox-Aufrufs
ausgelöst. Zur Laufzeit als EXE bleibt der Timer jedoch auch
während der Anzeige der MessageBox aktiv: Das Timer-Ereignis wird
daher unmittelbar nach dem Aufruf der MessageBox ausgelöst,
während diese selbst weiterhin modal zu der Anwendung bleibt.
Wenn Sie ganz sicher gehen wollen, sichern Sie den originalen
Arbeitsbereich mit SaveSetting-Aufrufen in der Registrierung und
lesen die Werte anschließend zum Wiederherstellen von dort aus.
Damit nicht nach einem Absturz falsche Werte gesichert und
wiederhergestellt werden, koppeln Sie die Sicherung mit einem Flag:
Die Werte dürfen nur dann ausgelesen werden, wenn das Flag gesetzt
ist. Nach dem Auslesen löschen Sie das Flag wieder. Nur wenn das
Flag nicht gesetzt ist, können die aktuellen Werte gesichert
werden, d.h. vorhandene, mit dem Flag "geschützte" Werte
können nicht überschrieben werden.
Das kleine Beispiel-Projekt zeigt Ihnen, wie die Aufruffolge
aussehen sollte. Die Beep-Anweisung im Timer-Ereignis dient nur zur
Verdeutlichung, dass das Ereignis tatsächlich zur EXE-Laufzeit
sofort nach dem MessageBox-Aufruf ausgelöst wird.
Private Sub cmdShowMsgBox_Click()
StoreArea
SetArea -Me.Width \ 2, -Me.Height \ 2, Me.Width, _
Me.Height * 0.85, True
tmr.Enabled = True
MsgBox "Platzierte MsgBox"
End Sub
Private Sub tmr_Timer()
tmr.Enabled = False
RestoreArea
Beep
End Sub
Das Sichern des Arbeitsbereichs erfolgt mit der Prozedur
StoreArea. Ihr können Sie optional die Angaben für AppName und
Section für die SaveSetting-Aufrufe übergeben. Fehlen diese, wird
der Titel der Anwendung als AppName und "WorkArea" für
Section angenommen.
Public Sub StoreArea(Optional AppName As String, _
Optional Section As String)
Dim nAppName As String
Dim nSection As String
Dim nSaved As Boolean
nAppName = Trim$(AppName)
If Len(AppName) = 0 Then
nAppName = App.Title
End If
nSection = Trim$(Section)
If Len(nSection) = 0 Then
nSection = "WorkArea"
End If
nSaved = CBool(GetSetting(nAppName, nSection, "AreaSaved", 0))
If Not nSaved Then
SystemParametersInfoArea SPI_GETWORKAREA, 0, mSavedArea, 0
With mSavedArea
SaveSetting nAppName, nSection, "AreaLeft", .Left
SaveSetting nAppName, nSection, "AreaTop", .Top
SaveSetting nAppName, nSection, "AreaRight", .Right
SaveSetting nAppName, nSection, "AreaBottom", .Bottom
SaveSetting nAppName, nSection, "AreaSaved", -1
End With
End If
End Sub
Die Wiederherstellung erfolgt mit RestoreArea - auch hier können
Sie wieder AppName und Section angeben:
Public Sub RestoreArea(Optional AppName As String, _
Optional Section As String)
Dim nAppName As String
Dim nSection As String
Dim nSaved As Boolean
nAppName = Trim$(AppName)
If Len(AppName) = 0 Then
nAppName = App.Title
End If
nSection = Trim$(Section)
If Len(nSection) = 0 Then
nSection = "WorkArea"
End If
nSaved = CBool(GetSetting(nAppName, nSection, "AreaSaved", 0))
If nSaved Then
On Error Resume Next
With mSavedArea
.Left = GetSetting(nAppName, nSection, "AreaLeft")
.Top = GetSetting(nAppName, nSection, "AreaTop")
.Right = GetSetting(nAppName, nSection, "AreaRight")
.Bottom = GetSetting(nAppName, nSection, "AreaBottom")
End With
If Err.Number = 0 Then
If SystemParametersInfoArea(SPI_SETWORKAREA, 0, _
mSavedArea, 0) Then
SaveSetting nAppName, nSection, "AreaSaved", 0
End If
End If
End If
End Sub
Über die Prozedur ClearStoredArea können Sie das Flag
gegebenenfalls separat löschen:
Public Sub ClearStoredArea(Optional AppName As String, _
Optional Section As String)
Dim nAppName As String
Dim nSection As String
nAppName = Trim$(AppName)
If Len(AppName) = 0 Then
nAppName = App.Title
End If
nSection = Trim$(Section)
If Len(nSection) = 0 Then
nSection = "WorkArea"
End If
SaveSetting nAppName, nSection, "AreaSaved", 0
End Sub
Mit der Prozedur SetArea setzen Sie nun den gewünschten
(Arbeits-)Bereich, in dessen Mitte etwa die MessageBox erscheinen
soll. Sie können die absolute Position des Bereichs in Twips in den
Parametern Left, Top, Width und Height direkt angeben. Lassen Sie
Left und/oder Top weg, wird statt dessen die aktuelle
Cursor-Position verwendet. Lassen Sie Width und/oder
Height weg, wird die Breite bzw. Höhe des Bereichs auf 2
Pixels reduziert (kleinere Werte verursachen einen Absturz!).
Setzen Sie den letzten optionalen Parameter FromCursorPos auf True,
werden die Werte von Left und Top relativ zur Cursor-Position
gesetzt - Breite und Höhe bleiben weiterhin relativ zu Left und
Top.
Public Sub SetArea(Optional ByVal Left As Variant, _
Optional ByVal Top As Variant, _
Optional ByVal Width As Variant, _
Optional ByVal Height As Variant, _
Optional ByVal FromCursorPos As Boolean)
Dim nRect As Rect
Dim nPoint As POINTAPI
GetCursorPos nPoint
With nRect
If IsMissing(Left) Then
.Left = nPoint.X
Else
If FromCursorPos Then
.Left = nPoint.X + (Left \ Screen.TwipsPerPixelX)
Else
.Left = Left \ Screen.TwipsPerPixelX
End If
End If
If IsMissing(Top) Then
.Top = nPoint.Y
Else
If FromCursorPos Then
.Top = nPoint.Y + (Top \ Screen.TwipsPerPixelY)
Else
.Top = Top \ Screen.TwipsPerPixelY
End If
End If
If IsMissing(Width) Then
.Right = .Left + 2
Else
.Right = .Left + (Width \ Screen.TwipsPerPixelX)
End If
If IsMissing(Height) Then
.Bottom = .Top + 2
Else
.Bottom = .Top + (Height \ Screen.TwipsPerPixelY)
End If
End With
SystemParametersInfoArea SPI_SETWORKAREA, 0, nRect, 0
End Sub
Damit eine MessageBox tatsächlich zentriert in einem Bereich
angezeigt wird, muss dieser allerdings groß genug sein - die
MessageBox muss vollständig in den Bereich hineinpassen können.
Anderenfalls, etwa beim Weglassen der Angaben für Width und Height
und der daraus resultierenden Größe des Bereichs von 2 x 2
Pixels, wird die linke obere Ecke der MessageBox in diesem
winzigen Bereich positioniert. Sie können auch einfach die Left/Top/Width/Height-Werte
eines Forms übergeben - die MessageBox wird dann über diesem Form
zentriert. In dem Beispiel-Projekt sehen Sie, dass ich den Wert für
Height mit dem Faktor 0,85 ein klein wenig reduziert
habe - die MessageBox wird so noch genauer vertikal zentriert (Je
nach Bildschirmeinstellungen kann allerdings ein anderer Wert als
0,85 kann notwendig sein!).
Das Prinzip funktioniert übrigens auch, wenn Sie
Standard-Dialoge positionieren möchten.
Wenn Sie die Werte des Arbeitsbereichs nicht in der Registrierung
sichern und auf die dadurch mögliche Sicherung durch das Flag
verzichten möchten, können Sie die Prozeduren GetArea und
ResetArea verwenden, um den Bereich direkt zu sichern und
wiederherzustellen.
Public Sub GetArea(Left As Long, Top As Long, Right As Long, _
Bottom As Long)
SystemParametersInfoArea SPI_GETWORKAREA, 0, mSavedArea, 0
With mSavedArea
Left = .Left
Top = .Top
Right = .Right
Bottom = .Bottom
End With
End Sub
Public Sub ResetArea()
SystemParametersInfoArea SPI_SETWORKAREA, 0, mSavedArea, 0
End Sub
Falls alles schief gehen sollte, können Sie mit der Prozedur
FullScreenArea den Arbeitsbereich auf die volle Bildschirmgröße
setzen - allerdings werden die Taskleiste usw. hierbei nicht
berücksichtigt, die normalerweise den Arbeitsbereich reduzieren.
nach einem Windows-Neustart ist allerdings der Arbeitsbereich wieder
auf seinen Standardwerte zurückgesetzt.
Public Sub FullScreenArea()
Dim nRect As Rect
With nRect
.Right = (Screen.Width \ Screen.TwipsPerPixelX)
.Bottom = (Screen.Height \ Screen.TwipsPerPixelY)
End With
SystemParametersInfoArea SPI_SETWORKAREA, 0, nRect, 0
End Sub
|