1. Herfried K. Wagner’s VB.Any
  2. Visual Basic
  3. Code

Fenster und Formulare

Überwachen des Aktivierens und Deaktivierens eines Formulars

Um mitzubekommen, wenn ein Formular den Eingabefokus erhält oder verliert, reichen die Ereignisse Activate bzw. GotFocus und LostFocus nicht aus. Die im Folgenden angegebene, einfach zu implementierende Lösung bedient sich eines Timer-Steuerelements, das in regelmäßigen Abständen prüft, ob das Formular noch aktiv ist. Bei einer Statusänderung wird ein Pseudoereignis aufgerufen:

Private Declare Function GetActiveWindow Lib "user32.dll" ( _
) As Long

Private m_FormFocus As Boolean

Private Sub Form_Load()
    With Me.Timer1
        .Interval = 20
        .Enabled = True
    End With
End Sub

Private Sub Timer1_Timer()
    If GetActiveWindow = Me.hWnd Then
        If Not m_FormFocus Then
            m_FormFocus = True
            Call Form_StateChanged(m_FormFocus)
        End If
    Else
        If m_FormFocus Then
            m_FormFocus = False
            Call Form_StateChanged(m_FormFocus)
        End If
    End If
End Sub

Private Sub Form_StateChanged(ByVal Activated As Boolean)
    Debug.Print _
        IIf( _
            Activated, _
            "The window got active", _
            "The window got inactive" _
        )
End Sub

„Wiederherstellen“ von Fenstern

Die WindowState-Eigenschaft von Formularen dient dazu, den Status eines Fensters zu ermitteln oder zu setzen. So kann man beispielsweise durch entsprechendes Setzen der Eigenschaft ein Fenster minimieren oder maximieren. Es geht jedoch eine Möglichkeit ab, das Fenster wiederherzustellen. Wird ein Fenster nämlich minimiert und will das Programm das Formular wieder einblenden, dann ist es unzureichend, die Eigenschaft WindowState auf den Wert vbNormal zu setzen. Dadurch würde das Fenster normal angezeigt, selbst wenn es vor dem Minimieren maximiert angezeigt wurde. Die im Folgenden angegebene Prozedur RestoreWindow bewirkt, daß das Fenster in den letzten (nicht minimierten) Status zurückversetzt wird:


' Letzter nicht minimierter Status ("Normal" oder "Maximiert").
Private m_LastState As FormWindowStateConstants

Private Sub Form_Resize()
    
    ' Wenn der neue Status nicht "Minimiert" ist, Status als letzten
    ' nicht minimierten Status speichern.
    If _
        Me.WindowState <> vbMinimized And _
        Me.WindowState <> m_LastState _
    Then
        m_LastState = Me.WindowState
    End If
End Sub

Private Sub RestoreWindow()
    If Me.WindowState = vbMinimized Then
        Me.WindowState = m_LastState
    End If
End Sub

Der oben angeführte Ansatz nimmt allerdings recht viel Platz in Anspruch. Weiters ist diese Funktionalität ja bereits in Windows implementiert, was einen Aufruf in das Windows-API nahelegt: Die API-Funktion ShowWindow in Verbindung mit der Konstanten SW_RESTORE ist genau zu diesem Zweck geeignet:

Private Declare Function ShowWindow Lib "user32.dll" ( _
    ByVal hWnd As Long, _
    ByVal nCmdShow As Long _
) As Long

Private Const SW_RESTORE As Long = 9&

Public Sub RestoreWindow()
    Call ShowWindow(Me.hWnd, SW_RESTORE)
End Sub

Ermitteln, ob eine InputBox über „Abbrechen“ geschlossen wurde

Visual Basic stellt für Benutzereingaben eine vordefinierte Funktion InputBox zur Verfügung, deren Rückgabewert die vom Benutzer eingegebene Zeichenfolge enthält. Das Eingabeformular enthält eine Schaltfläche, über die der Eingabevorgang abgebrochen werden kann. InputBox stellt keine dokumentierte Möglichkeit bereit, herauszufinden, ob der Benutzer das Fenster durch Abbruch oder durch Bestätigung der Eingabe geschlossen hat. Diese Unterscheidung ist aber trotzdem über einen kleinen Trick mit der Funktion StrPtr, die einen Zeiger auf ein String-Objekt zurück gibt, möglich:

Private Sub Main()
    Dim UserInput As String
    UserInput = _
        InputBox( _
            "Please type in some text or press the cancel button.", _
            "Test" _
        )
    If StrPtr(UserInput) = 0 Then
        Call MsgBox("The cancel button was pressed!")
    Else
        Call MsgBox("You entered """ & UserInput & """!")
    End If
End Sub

Abfrage beim Schließen eines Fensters über die Systemschaltfläche „Schließen“

Im Ereignis QueryUnload eines Formulars kann der Grund für das Schließen des Formulars ermittelt werden. In einigen Fällen ist es sinnvoll, den Benutzer darauf hinzuweisen, daß er versucht, ein Formular zu schließen. Dies ist beispielsweise bei MDI-Formularen der Fall. Der folgende Beispielcode zeigt, wie der Benutzer zur Bestätigung des Vorgangs aufgefordert werden kann und wie bei negativer Antwort des Benutzers das Schließen des Formulars unterbunden werden kann:

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
    If UnloadMode = vbFormControlMenu Then
        Const Question As String = "Fenster wirklich schließen?"
        If MsgBox(Question, vbQuestion Or vbYesNo) = vbNo Then
            Cancel = Not Cancel
        End If
    End If
End Sub

Ein alternativer Einsatzzweck wäre, daß Benutzereingaben nach dem Schließen des Formulars nicht verloren gehen sollen. Das Fenster soll also nur ausgeblendet, nicht jedoch wirklich geschlossen werden. Zu diesem Zweck könnte man ähnlich vorgehen, wie dies im obenstehenden Code gezeigt wird, wobei über den Aufruf Call Me.Hide bewirkt werden kann, daß das Formular nur versteckt und nicht entladen wird.

Der Wert der Variablen Cancel ist in diesem Beispiel nicht von Bedeutung. Das Entladen des Formulars tritt nämlich nur ein, wenn Cancel unverändert bleibt. Durch Negation wird unkompliziert der Wert der Variablen verändert.

Schließen eines fremdes Fenster anhand seines Fenstertitels

Folgender Code sucht nach einem Fenster mit einem bestimmten Titel und schließt dieses, wenn es geöffnet ist:

Private Declare Function FindWindow _
    Lib "user32.dll" _
    Alias "FindWindowA" _
( _
    ByVal lpClassName As String, _
    ByVal lpWindowName As String _
) As Long

Private Declare Function PostMessage _
    Lib "user32.dll" _
    Alias "PostMessageA" _
( _
    ByVal hWnd As Long, _
    ByVal wMsg As Long, _
    ByVal wParam As Long, _
    ByVal lParam As Long _
) As Long

Private Const WM_CLOSE As Long = &H10&

Private Sub Main()
    Dim hWnd As Long
    hWnd = FindWindow(vbNullString, "Rechner")
    If hWnd <> 0 Then
        If PostMessage(hWnd, WM_CLOSE, 0&, 0&) = 0& Then
            Call MsgBox("Fehler!")
        Else
            Call MsgBox("Der Rechner sollte nun geschlossen sein!")
        End If
    Else
        Call MsgBox("Der Rechner ist nicht geöffnet!")
    End If
End Sub

Deaktivieren von Alt+F4 zum Schließen von Fenstern

Das Schließen von Fenstern durch Drücken der Tastenkombination Alt+F4 kann mit Visual Basic-eigenen Mitteln unterbunden werden. Dazu kann folgender Code einem Formular hinzugefügt werden, wobei die KeyPreview-Eigenschaft des Formulars auf True gesetzt sein muß:

Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)
    If Shift = vbAltMask Then
        If KeyCode = vbKeyF4 Then
            KeyCode = 0
        End If
    End If
End Sub

Hinzufügen eines Fensterschattens unter Windows XP

Seit Windows XP können Fenster mit einem Schlagschatten ausgestattet werden. Dies ist bei Menüs standardmäßig der Fall, kann aber auch für eigene Fenster, nicht jedoch darauf plazierte Steuerelemente, aktiviert werden:

Private Declare Function SetClassLong _
    Lib "user32.dll" _
    Alias "SetClassLongA" _
( _
    ByVal hWnd As Long, _
    ByVal nIndex As Long, _
    ByVal dwNewLong As Long _
) As Long

Private Declare Function GetClassLong _
    Lib "user32.dll" _
    Alias "GetClassLongA" _
( _
    ByVal hWnd As Long, _
    ByVal nIndex As Long _
) As Long

Private Const GCL_STYLE As Long = (-26&)

Private Const CS_DROPSHADOW As Long = &H20000

Private Sub Form_Load()
    Call SetClassLong( _
        Me.hWnd, _
        GCL_STYLE, _
        GetClassLong(Me.hWnd, GCL_STYLE) Or CS_DROPSHADOW _
    )
End Sub

Beheben von Fokusproblemen bei Menüs für Symbole im Infobereich

Beispiele zum Hinzufügen eines Symbols in den Infobereich der Taskleiste gibt es im Web wie Sand am Meer. Auch in der Knowledge Base gibt es dazu mindestens einen Artikel. Anwendungen unter Windows können über die Funktion Shell_NotifyIcon ein Symbol im Infobereich hinzufügen und manipulieren.

[Illustration] Beispiel für den Infobereich der Taskleiste

Einige der Beispiele, die man findet, demonstrieren auch die Verwendung von Kontextmenüs auf dem Symbol. Ein Grossteil dieser Beispiele ist jedoch nicht unproblematisch und weist einen unschönen Nebeneffekt auf: Das über dem Symbol angezeigte Kontextmenü kann nicht mit der Tastatur bedient werden, so ist etwa das Schließen des Menüs durch Drücken der Esc-Taste nicht möglich. Dieses Problem ist seit geraumer Zeit bekannt und es wurden bereits Lösungsansätze entwickelt:

Während das erste Beispiel von Matt E. Hart [MVP] bereits unter Windows 95 seinen Dienst versagte und das Problem nicht behob, erwies sich das zweite Beispiel von Markus Warm als vielversprechend. Dort reagierte das Menü nämlich auf die Tastatur, nachdem es ein Mal angezeigt wurde. Trotzdem war das Beispiel nicht dahingehend erweiterbar, gleich beim ersten Öffnen des Menüs wie gewünscht seinen Dienst zu verrichten.

Eine relativ stabile Lösung, deren Implementierung besonders einfach ist und die unter Windows Me und Windows 2000 funktioniert, sieht wie folgt aus:

  1. Dem Projekt wird ein Formular hinzugefügt und dieses beispielsweise FDummy genannt. Dieses Formular wird zur Lebenszeit der Anwendung nie sichtbar gemacht, es muß auch keine weitere Implementierung enthalten.

  2. Man erstellt ein Kontextmenü mit dem Menüeditor von Visual Basic und benennt es beispielsweise mnuPopupNotifyIcon, den Eintrag auf höchster Ebene mnuPopup.

  3. Weiters ist die Deklaration der Funktion SetForegroundWindow erforderlich:

    Private Declare Function SetForegroundWindow Lib "user32.dll" ( _
        ByVal hWnd As Long _
    ) As Long
  4. Anschließend fügt man dem Projekt den restlichen Code zur Verwaltung des Symbols im Infobereich hinzu (dieser Code kann im Web bezogen werden).

  5. Das Anzeigen des Kontextmenüs ist dann denkbar einfach:

    Call SetForegroundWindow(FDummy.hWnd)
    Call FDummy.PopupMenu(Me.mnuPopup, , , , mnuPopupNotifyIcon)

Verschieben eines Formulars mit der Maus

Eine etwas unkonventionelle Art, dem Benutzer die Möglichkeit zu geben, ein Formular (oder ein beliebiges Steuerelement, das Mausereignisse empfängt) mit der Maus zu verschieben, ist mit reinen Bordmitteln von Visual Basic umsetzbar. Dazu wird die aktuelle Mausposition bei Drücken einer Maustaste gespeichert und der Verschiebungsmodus aktiviert. Bei Eintreten des MouseMove-Ereignisses wird die aktuelle Position basierend auf der ursprünglichen Position des Formulars und der aktuellen Mausposition berechnet. Wird die Maustaste losgelassen, dann ist der Verschiebungsvorgang beendet:

Private m_OldX As Long
Private m_OldY As Long
Private m_IsMoving As Boolean

Private Sub Form_Load()
    m_IsMoving = False
End Sub

Private Sub Form_MouseDown( _
    Button As Integer, _
    Shift As Integer, _
    X As Single, _
    Y As Single _
)
    m_OldX = X
    m_OldY = Y
    m_IsMoving = True
End Sub

Private Sub Form_MouseMove( _
    Button As Integer, _
    Shift As Integer, _
    X As Single, _
    Y As Single _
)
    If m_IsMoving Then
        Me.Top = Me.Top - m_OldY + Y
        Me.Left = Me.Left - m_OldX + X
    End If
End Sub

Private Sub Form_MouseUp( _
    Button As Integer, _
    Shift As Integer, _
    X As Single, _
    Y As Single _
)
    m_IsMoving = False
End Sub

Zentrieren eines Formulars

Manchmal ist es notwendig, ein Formular zu zentrieren, ohne seine StartUpPosition einzustellen. Zu diesem Zweck kann die Methode Move des Formulars mit entsprechenden Positionskoordinaten aufgerufen werden. Diese Methode arbeitet schneller als das getrennte Setzen der Eigenschaften Left und Top. Move entspricht der API-Funktion MoveWindow:

Call Me.Move((Screen.Width - Width) * 0.5, (Screen.Height - Height) * 0.5)

Modales Anzeigen eines Formulars ohne vbModal

Wenn man unter Visual Basic versucht, von einem modal angezeigten Formular (FOptions) aus, ein nicht-modales Formular (FControl) anzuzeigen, wird ein Fehler ausgelöst. Eine Lösung des Problems bietet folgender Code, bei dem zuerst das aufrufende Formular deaktiviert und anschließend das nicht-modale Formular angezeigt wird:


' Deaktivieren des Elternformular des "modalen" Dialogs.
FOptions.Enabled = False

' Anzeigen des Dialogs.
Call FControl.Show

Weiters muß im Unload-Ereignis von FControl der nachfolgende Code hinzugefügt werden, um das Optionsformular wieder zu aktivieren:

FOptions.Enabled = True

Es ist zu beachten, daß diese Methode nicht den gleichen Effekt hat wie das herkömmliche modale Anzeigen von Formularen. Wird ein Formular modal angezeigt und man wählt sein Elternfenster in der Taskleiste, dann blinkt die Titelleiste des modalen Dialogs. Durch Deaktivieren des Elternfensters wird dieser hilfreiche visuelle Effekt unterbunden.

Entladen aller Formulare einer Anwendung

Die End-Anweisung sollte nicht zum Beenden eines Programms verwendet werden, da dadurch nicht alle vom Programm belegten Speicherressourcen ordnungsgemäß freigegeben werden. End verhindert nämlich, daß Code zum Aufräumen, also etwa das geordnete Schließen von Datenbankverbindungen und Dateien sowie zerstören von Objekten, ausgeführt wird. Anstelle dieses Befehls könnte man folgenden Code verwenden, der beispielsweise im Unload-Ereignis des Hauptformulars abgelegt sein kann:

Private Sub Form_Unload(Cancel As Integer)
    Dim Form As Form
    For Each Form In Forms
        Call Unload(Form)
    Next Form
End Sub

Wichtig ist, daß vor dem Entladen der Formulare alle aktiven Timer-Steuerelemente deaktiviert und Verweise auf andere Objekte wie etwa andere Formulare oder Klassen aufgelöst werden. Wenn die Verweise entfernt wurden, beendet sich die Anwendung von selbst.

Verhindern des Flackerns bei der Aktualisierung von Fenstern

Besonders bei größeren Formularen mit vielen Steuerelementen fällt es störend auf, daß die Steuerelemente bzw. das Formular während des Aktualisierens flackert. Folgender Code unterbindet während der Aktualisierung ein Neuzeichnen des Fensters, dessen Zugriffsnummer übergeben wird. Das Fenster wird erst dann neu gezeichnet, wenn die Prozedur UnlockWindow aufgerufen wird:

Private Declare Function LockWindowUpdate Lib "user32.dll" ( _
    ByVal hWnd As Long _
) As Long

Private Sub LockWindow(ByVal hWnd As Long)
    Call LockWindowUpdate(hWnd)
End Sub

Private Sub UnlockWindow()
    Call LockWindowUpdate(0&)
End Sub

Folgender Code wird an der Stelle, an der die Aktualisierung der Anzeige stattfinden soll, eingesetzt (statt Me.hWnd kann auch die Zugriffsnummer eines anderen Fensters oder Steuerelements übergeben werden:

Call LockWindow(Me.hWnd)

' Aktualisierungscode.

Call UnlockWindow

Von der Verwendung von LockWindowUpdate zum Unterdrücken der Fensteraktualisierung während mehrerer Manipulationen wird abgeraten. Die vorzuziehende Methode wäre, die Funktion SendMessage mit der Konstanten WM_SETREDRAW und der Zugriffsnummer des Fensters, das nicht neu gezeichnet werden soll, zu senden. Dazu werden die folgenden Deklarationen benötigt:

Private Declare Function SendMessage _
    Lib "user32.dll" _
    Alias "SendMessageA" _
( _
    ByVal hWnd As Long, _
    ByVal wMsg As Long, _
    ByVal wParam As Long, _
    ByRef lParam As Long _
) As Long

Private Const WM_SETREDRAW As Long = &HB&

Folgender Code wird an der Stelle, an der die Aktualisierung erfolgen soll, verwendet. Control bezeichne im folgenden Code das Steuerelement, dessen Aktualisierung unterbunden werden soll:


' Aktualisieren unterbinden.
Call SendMessage(Control.hWnd, WM_SETREDRAW, 0&, 0&)

' Steuerelement hier manipulieren.

' Aktualisieren wieder zulassen.
Call SendMessage(Control.hWnd, WM_SETREDRAW, 1&, 0&)

Mit dieser Thematik beschäftigt sich auch der Artikel „Gefrierschrank“ von Harald M. Genauck [MVP].

Entfernen von Einträgen aus dem Systemmenü

Über die Funktion DeleteMenu lassen sich einzelne Menüeinträge aus dem Systemmenü (oder einem herkömmlichen Hauptmenü) eines Formulars entfernen. Die Funktion GetSystemMenu dient dazu, die Zugriffsnummer des Systemmenüs eines Formulars zu ermitteln. Im folgenden Listing sind die entsprechenden Funktionsdeklarationen wiedergegeben:

Private Declare Function GetSystemMenu Lib "user32.dll" ( _
    ByVal hWnd As Long, _
    ByVal bRevert As Long _
) As Long

Private Declare Function DeleteMenu Lib "user32.dll" ( _
    ByVal hMenu As Long, _
    ByVal nPosition As Long, _
    ByVal wFlags As Long _
) As Long

Private Const MF_BYPOSITION As Long = &H400&

Durch Übergabe der Konstante MF_BYPOSITION im Parameter wFlags wird festgelegt, daß der in nPosition übergebene Wert die Ordnungszahl des Menüpunkts im Menü darstellt. Die Bedeutungen der Ordnungszahlen sind in folgender Übersicht angegeben:

Ordnungszahlen der Menüeinträge in Systemmenüs
Ordnungszahl Bedeutung
0 Wiederherstellen
1 Verschieben
2 Größe ändern
3 Minimieren
4 Maximieren
5 Trennlinie
6 Schließen

Übersicht über die Konstanten für die Menüeinträge.

Folgender Aufruf würde den Eintrag mit „Maximieren“ aus dem Systemmenü eines Formulars entfernen:

Dim hMenu As Long, n As Long
hMenu = GetSystemMenu(Me.hWnd, False)
n = DeleteMenu(hMenu, 4&, MF_BYPOSITION)
If n = 0& Then
    Call MsgBox("Menüeintrag kann nicht gelöscht werden.")
End If

Es gibt aber auch noch eine weitere Möglichkeit, den „Schließen“-Eintrag aus dem Systemmenü und die dazugehörige Systemschaltfläche aus der Titelleiste eines Formulars zu entfernen bzw. die Systemschaltfläche zu deaktivieren, die auf Aufrufen der Funktion RemoveMenu basiert. Mit diesem Code wird zuerst geprüft, ob überhaupt noch Einträge zum Entfernen vorhanden sind. Nach dem Entfernen wird die Menüleiste neu gezeichnet:

Private Declare Function GetSystemMenu Lib "user32.dll" ( _
    ByVal hWnd As Long, _
    ByVal bRevert As Long _
) As Long

Private Declare Function GetMenuItemCount Lib "user32.dll" ( _
    ByVal hMenu As Long _
) As Long

Private Declare Function DrawMenuBar Lib "user32.dll" ( _
    ByVal hWnd As Long _
) As Long

Private Declare Function RemoveMenu Lib "user32.dll" ( _
    ByVal hMenu As Long, _
    ByVal nPosition As Long, _
    ByVal wFlags As Long _
) As Long

Private Const MF_BYPOSITION As Long = &H400&
Private Const MF_REMOVE As Long = &H1000&

Private Sub Form_Load()
    Dim hMenu As Long, n As Long
    hMenu = GetSystemMenu(Me.hWnd, 0&)
    If hMenu <> 0& Then
        n = GetMenuItemCount(hMenu)
        If n > 0& Then
            Call RemoveMenu(hMenu, CLng(n - 1), MF_BYPOSITION Or MF_REMOVE)
            Call RemoveMenu(hMenu, CLng(n - 2), MF_BYPOSITION Or MF_REMOVE)
            Call DrawMenuBar(Me.hWnd)
        End If
    End If
End Sub

Anstatt Menüeinträge anhand ihrer Position zu entfernen, empfiehlt es sich, bestimmte Menüeinträge anhand benannter Befehle mittels MF_BYCOMMAND zu entfernen. Folgendes Beispiel zeigt die Vorgehensweise zum Entfernen des Menüeintrags und der Systemschaltfläche zum Minimieren eines Formulars (dies ist seit VB5 über eine Formulareigenschaft möglich):

Private Declare Function DeleteMenu Lib "user32.dll" ( _
    ByVal hMenu As Long, _
    ByVal nPosition As Long, _
    ByVal wFlags As Long _
) As Long

Private Declare Function GetSystemMenu Lib "user32.dll" ( _
    ByVal hWnd As Long, _
    ByVal bRevert As Long _
) As Long

Private Declare Function GetWindowLong _
    Lib "user32.dll" _
    Alias "GetWindowLongA" _
( _
    ByVal hWnd As Long, _
    ByVal nIndex As Long _
) As Long

Private Declare Function SetWindowLong _
    Lib "user32.dll" _
    Alias "SetWindowLongA" _
( _
    ByVal hWnd As Long, _
    ByVal nIndex As Long, _
    ByVal dwNewLong As Long _
) As Long

Private Const GWL_STYLE As Long = (-16&)

Private Const MF_BYCOMMAND As Long = &H0&

Private Const SC_MINIMIZE As Long = &HF020&

Private Const WS_MINIMIZEBOX As Long = &H20000

Private Sub Form_Load()
    Call DeleteMenu( _
        GetSystemMenu(Me.hWnd, 0&), _
        SC_MINIMIZE, _
        MF_BYCOMMAND _
    )
    Call SetWindowLong( _
        Me.hWnd, _
        GWL_STYLE, _
        CLng(GetWindowLong(Me.hWnd, GWL_STYLE) Xor WS_MINIMIZEBOX) _
    )
End Sub

Anzeigen eines Anwendungsfenster vor allen anderen Anwendungen

Oft hört man die Frage, wie man es denn hinbekommt, das Fenster seiner Anwendung wirklich in den Vordergrund zu bringen, sodaß es über allen anderen Fenstern angezeigt wird. Die Funktionen SetActiveWindow, BringWindowToFront und noch einige andere Funktionen sind hierzu nicht ausreichend, über einen Aufruf dieser Funktionen kann lediglich erreicht werden, daß die Schaltflächen der Fenster in der Taskleiste blinken. Das Zauberwort heißt in diesem Fall SetWindowPos.

Über einen kleinen Trick kann man es mit dieser API-Funktion erreichen, daß das Formular wirklich im Vordergrund erscheint, ohne lästig zu blinken und dauerhaft im Vordergrund zu sein. Der folgende Code muß in einem Formular abgelegt werden, auf dem ein Timer-Steuerelement mit dem Namen tmrSetOnTop plaziert ist:

Private Declare Function SetWindowPos Lib "user32.dll" ( _
    ByVal hWnd As Long, _
    ByVal hWndInsertAfter As Long, _
    ByVal x As Long, _
    ByVal y As Long, _
    ByVal cx As Long, _
    ByVal cy As Long, _
    ByVal wFlags As Long _
) As Long

Private Const HWND_TOPMOST As Long = -1&
Private Const HWND_NOTOPMOST As Long = -2&

Private Const SWP_NOSIZE As Long = &H1&
Private Const SWP_NOMOVE As Long = &H2&
Private Const SWP_SHOWWINDOW As Long = &H40&

Private Const BaseError As Long = 512&

Private Sub Form_Load()
    Me.AutoRedraw = True
End Sub

Private Sub tmrSetOnTop_Timer()
    Call SetTopWindow(Me.hWnd)
    Me.Print "Set to top at " & Time$
End Sub

Public Sub SetTopWindow(ByVal hWnd As Long)
    If _
        SetWindowPos( _
            hWnd, _
            HWND_TOPMOST, _
            0&, _
            0&, _
            0&, _
            0&, _
            SWP_NOSIZE Or SWP_NOMOVE Or SWP_SHOWWINDOW _
        ) = 0& _
    Then
        Call Err.Raise( _
            vbObjectError + BaseError + 1, _
            "SetTopWindow", _
            "Error bringing window on top." _
        )
    Else
        If _
            SetWindowPos( _
                hWnd, _
                HWND_NOTOPMOST, _
                0&, _
                0&, _
                0&, _
                0&, _
                SWP_NOSIZE Or SWP_NOMOVE Or SWP_SHOWWINDOW _
            ) = 0& _
        Then
            Call Err.Raise( _
                vbObjectError + BaseError + 1, _
                "SetTopWindow", _
                "Error bringing window on top." _
            )
        End If
    End If
End Sub