How to Keep a Modeless UserForm Always on Top in Excel VBA?

Sharing is caring!

When working with Excel VBA UserForms, you may want a modeless UserForm to stay visible while the user interacts with Excel or other applications. By default, a modeless form can slip behind other windows, making it harder to access.

In this guide, we will explain how to keep your UserForm always on top using the Windows API.

Understanding Modal vs Modeless UserForms

Before applying the solution, it’s important to understand how modal and modeless UserForms behave inside Excel VBA.

Modal UserForm

  • Locks the application until closed.
  • Prevents the user from working with Excel until the form is dismissed.
  • Code execution pauses until the UserForm is closed.

Modeless UserForm

  • Allows multitasking: Excel and other applications remain accessible.
  • Code execution continues after the form is displayed.
  • Useful for tool palettes, dashboards, and quick-access utilities.

Here’s a simple comparison:

UserForm TypeUser InteractionCode Execution
ModalExcel is locked until the form closesPauses at the .Show line
ModelessUser can interact with Excel & other appsContinues immediately after .Show

To show a form modeless, you use:

UserForm1.Show vbModeless

The Problem with Modeless UserForms

While modeless UserForms provide flexibility, they suffer from one drawback: if the user clicks on another window, the form may get hidden behind Excel or other programs. This reduces usability, especially when the form is meant to act as a floating toolbar or persistent panel.

The Solution: Keep UserForm Always on Top

We can solve this issue by using the Windows API. Specifically, we will use:

  • SetWindowPos β†’ To control the form’s z-order (stacking order).
  • FindWindow β†’ To get the handle (hwnd) of the UserForm, since Excel UserForms do not expose .hwnd directly.

Step 1: Add API Declarations

Insert the following into a standard VBA module:

Public Const HWND_TOPMOST = -1
Public Const SWP_NOSIZE = &H1
Public Const SWP_NOMOVE = &H2
Public Const SWP_NOACTIVATE = &H10

Public Declare PtrSafe Function SetWindowPos Lib "user32" _
    (ByVal hwnd As LongPtr, ByVal hWndInsertAfter As LongPtr, _
    ByVal X As Long, ByVal Y As Long, ByVal cx As Long, _
    ByVal cy As Long, ByVal uFlags As Long) As Long

Public Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" ( _
    ByVal lpClassName As String, _
    ByVal lpWindowName As String) As LongPtr

Step 2: Create Helper Functions

Add a function to get the window handle of a UserForm using its caption, and a subroutine to make it topmost:

Public Function GetHwndFromForm(frm As Object) As LongPtr
    GetHwndFromForm = FindWindow(vbNullString, frm.Caption)
End Function

Public Sub SetTopMost(hwnd As LongPtr)
    SetWindowPos hwnd, HWND_TOPMOST, 0, 0, 0, 0, _
        SWP_NOMOVE Or SWP_NOSIZE Or SWP_NOACTIVATE
End Sub

Step 3: Show the UserForm and Apply Always-On-Top

Now, when you show your modeless UserForm, call the SetTopMost sub immediately:

Private Sub txtView_FPAnnualAgenda_DblClick(ByVal Cancel As MSForms.ReturnBoolean)

    With ufViewFPAnnualAgenda
        .Show vbModeless
        SetTopMost GetHwndFromForm(ufViewFPAnnualAgenda)
        .txtText.Value = txtView_FPAnnualAgenda.Value
        .txtText.Tag = txtView_FPAnnualAgenda.Value
    End With

End Sub

Bonus: Using a Timer to Refresh Always-On-Top

Sometimes, other applications may override z-order. To handle this, you can use a timer inside your UserForm to refresh the always-on-top state.

Private Sub UserForm_Activate()
    Application.OnTime Now + TimeValue("00:00:02"), "KeepOnTop"
End Sub

Public Sub KeepOnTop()
    SetTopMost GetHwndFromForm(ufViewFPAnnualAgenda)
    Application.OnTime Now + TimeValue("00:00:02"), "KeepOnTop"
End Sub

This ensures the UserForm is repositioned on top every 2 seconds.

Considerations Before Using Always-On-Top

  • User experience: Always-on-top forms can be useful for tool panels, but overusing them may annoy users.
  • Performance: Calling API functions repeatedly has minimal impact but should be used wisely.
  • Compatibility: Some full-screen programs (e.g., games) may override your z-order settings.
  • Mac limitation: This method works only on Windows because it depends on the Windows API.

Final Thoughts

By combining the SetWindowPos and FindWindow APIs, you can keep a modeless UserForm always visible in Excel VBA. This technique is particularly useful for tool palettes, dashboards, or real-time data monitors where the UserForm must remain in view while the user continues working across Excel and other applications.

Frequently Asked Questions

Can I use this always-on-top UserForm technique with multiple forms?

Yes. You can apply the SetTopMost procedure to multiple UserForms. Just call it with each form’s handle obtained through GetHwndFromForm. Each UserForm can be kept on top individually.

Why do I get a compile error when using .hwnd on my UserForm?

Excel VBA UserForms do not expose the .hwnd property directly. That’s why you see a compile error. To fix this, you must use the Windows API function FindWindow with the UserForm’s caption to retrieve its handle.

Will this solution work in Excel for Mac?

No. The method depends on the Windows API, which is available only on Windows. Excel for Mac does not support these API calls. Mac users would need an alternative approach using macOS system APIs.

Can a modeless UserForm stay on top of all applications, not just Excel?

Yes. By using the HWND_TOPMOST flag in the SetWindowPos API, the UserForm can remain on top of all windows, not just Excel. However, some programs such as full-screen apps or games may override this behavior.

Is there a way to toggle the always-on-top behavior?

Yes. You can create a Boolean flag (e.g., AlwaysOnTop) and check its value before calling SetTopMost. This allows you to turn the always-on-top behavior on or off as needed from your VBA code.

Will repeatedly setting a UserForm on top slow down Excel?

The performance impact is usually minimal. Even if you use a timer to refresh the topmost state every few seconds, the API call is lightweight. Still, you should use it only when necessary to avoid unnecessary system calls.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *

2 Comments

  1. Very well written post – however I’m having problems getting it to work.

    I have the API declaration at the top of a VBA module.
    Public Const HWND_TOPMOST = -1
    Public Const SWP_NOSIZE = &H1
    Public Const SWP_NOMOVE = &H2
    Public Const SWP_NOACTIVATE = &H10

    Public Declare PtrSafe Function SetWindowPos Lib “user32” _
    (ByVal hwnd As LongPtr, ByVal hWndInsertAfter As LongPtr, _
    ByVal X As Long, ByVal Y As Long, ByVal cx As Long, _
    ByVal cy As Long, ByVal uFlags As Long) As Long

    I call this “SetTopMost” sub immediately after showing the userform:

    Public Sub SetTopMost(hwnd As LongPtr)
    SetWindowPos hwnd, HWND_TOPMOST, 0, 0, 0, 0, _
    SWP_NOMOVE Or SWP_NOSIZE Or SWP_NOACTIVATE
    End Sub

    like this:

    Private Sub txtView_FPAnnualAgenda_DblClick(ByVal Cancel As MSForms.ReturnBoolean)

    With ufViewFPAnnualAgenda
    .Show 0
    SetTopMost ufViewFPAnnualAgenda.hwnd
    .txtText.Value = txtView_FPAnnualAgenda.Value
    .txtText.Tag = txtView_FPAnnualAgenda.Value
    End With

    End Sub

    But I’m getting a compile error: “Method or data member not found” with the .hwnd property highlighted in my function call above.

    1. Hello Del,

      The issue happens because Excel UserForms don’t actually expose the .hwnd property, which is why you’re seeing the compile error.

      Instead, you’ll need to use the Windows API function FindWindow to get the handle of the UserForm based on its caption, and then pass that handle into the SetTopMost routine.

      I’ve updated the article to include this fix with full working code so you can copy it directly. That should solve the problem and keep your modeless UserForm always on top.

      If you still face any problems, let me know.