Owner changes (owner, setOwnerHandle), title-bar buttons
(isMinimiseButtonVisible, isMaximiseButtonVisible, isSystemMenuVisible),
and icons (HICON) were already implemented. Style setters now refresh the
non-client frame immediately. SendMessage and PostMessage preserve native
pointer widths in VBA7, including 64-bit message results and icon handles.
owner reads the owner rather than a child window's parent. It returns Nothing
for an unowned window; assigning Nothing removes the owner. setOwnerHandle
rejects child windows, whose relationship must be changed through Parent.
Dim wnd As stdWindow
Set wnd = stdWindow.CreateFromIUnknown(MyForm)
wnd.isTaskbarVisible = True
wnd.isMinimiseButtonVisible = True
wnd.isMaximiseButtonVisible = TrueisTaskbarVisible configures a top-level window's taskbar eligibility using
WS_EX_APPWINDOW and WS_EX_TOOLWINDOW. It hides a visible window before changing
the styles, then shows it without activation so the Shell can refresh its button.
A hidden window stays hidden, and its owner is preserved. This getter reports
configured eligibility, including for hidden windows; it does not query Explorer's
buttons, grouping, or pinned applications. Child windows are ineligible and the
setter rejects them. Hiding a taskbar button also removes the window from Alt+Tab.
The existing isAppWindow and isToolWindow properties remain lower-level style
switches. They do not perform the Shell's hide/change/show sequence. This approach
keeps stdWindow independent of stdCOM. The taskbar COM example in stdCOM is
still a useful starting point for future thumbnail, overlay, or progress APIs.
Keep the wrapper in a module or form field for as long as it should receive messages:
'Standard module MyModule:
Private watched As stdWindow
Public Sub StartWatching(ByVal form As Object)
Set watched = stdWindow.CreateFromIUnknown(form)
watched.Subclass stdCallback.CreateFromModule("MyModule", "OnWindowMessage")
End Sub
Public Function OnWindowMessage(ByVal hwnd As LongPtr, ByVal message As Long, _
ByVal wParam As LongPtr, ByVal lParam As LongPtr) As Variant
Const WM_APP_PING As Long = &H8001&
If message = WM_APP_PING Then OnWindowMessage = 123
'Implicit Empty forwards every other message through DefSubclassProc.
End Function
Public Sub StopWatching()
If Not watched Is Nothing Then watched.Unsubclass
Set watched = Nothing
End SubSubclass(callback As stdICallable, Optional enableIDEsafety As Boolean = True) accepts stdCallback, stdLambda, or any
implementation of the interface. Each invocation receives a zero-based array of
hwnd, message, wParam, and lParam through RunEx. Return Empty to forward
the message, or an integer representable as LongPtr to handle it. Zero is a
handled result. A handled result bypasses the remaining chain. Use explicit Long
constants for messages such as &H8001&; VBA otherwise interprets some hexadecimal
literals as signed Integers.
Only windows belonging to the current VBA thread can be subclassed. Each wrapper
can install one callback; calling Subclass again replaces it. Independent
wrappers on the same HWND form a chain using SetWindowSubclass and
DefSubclassProc. Unsubclass removes only that wrapper and is idempotent.
WM_NCDESTROY detaches the wrapper before notifying its callback, then always
forwards the destruction message. The wrapper also detaches on normal object
termination. Removal, nested messages, destruction, and releasing the caller's
last wrapper reference from inside a callback are supported. Avoid reference
cycles (for example, a bound callback retaining the same window); explicitly
unsubscribe to break them.
Callback exceptions and invalid return-value conversions are contained and
forwarded. Inspect SubclassErrorNumber and SubclassErrorDescription for the
last error since installation; reinstalling clears them. Callbacks should return
quickly and avoid modal UI or DoEvents unless reentrancy is intended.
The thunk checks the editor mode before touching the VBA callback pointer:
- In Run mode it dispatches the callback normally.
- In Break mode it forwards messages without entering VBA and keeps the subclass installed, allowing Continue to resume callbacks. A dying window still detaches.
- When Design/reset mode (or an unknown mode) is observed, it removes the native
subclass and forwards.
isSubclassedreconciles that removal when queried again.
This follows the native EbMode guard pattern. A missing named export does not
make EbMode inaccessible: when GetProcAddress cannot find it, stdWindow locates
the internal function through the VBE Proc/MethCallEngine template, using the
discovery technique described by The trick's CTrickTimer.
The resolver checks PE headers and architecture, scans readable executable
sections with a masked full signature, and requires matching templates to agree
on one committed executable target inside the same module. Unsupported or
ambiguous runtime layouts fail installation; no guessed address is called.
The actual EbMode pointer is cached and called directly by the native thunk. This needs no VBProject access, VBIDE reference, PDB download, or helper class. The ordinary window/UserForm tests and resolver tests also pass in a fresh Excel instance where AccessVBOM is disabled. The existing stdWebView helper only tries the named export and does not currently perform this internal-function discovery. VBAStack demonstrates another route through PDB symbols; no symbol resolver is required by this implementation. Its separate pure VBA stack reader uses the Err object's runtime-thread and stack-frame structure offsets without PDBs; those offsets locate stack data rather than the EbMode function.
The enableIDEsafety argument defaults to True. In the tested Excel host a
modeless starter macro returning can itself put the engine in Design mode, so
the default guard then detaches on the next message. Keep VBA running (for
example, during a modal form or an active event loop) when using this guard.
For callbacks between macros, opt out explicitly:
wnd.Subclass stdCallback.CreateFromModule("MyModule", "OnWindowMessage"), FalseThis opt-out restores ordinary modeless/background callbacks and disables the
break/reset guard. You must call Unsubclass before breaking, resetting, using
End, or editing running code in that mode. Changing the argument on a subsequent
Subclass call updates only that wrapper; other subclasses keep their own policy.
Explicit Unsubclass remains the preferred shutdown path. A mode guard reacts to
messages and cannot retroactively protect an already executing callback or promise
to detect a reset if no non-Run mode is observed before later code runs. Avoid
End, Reset, and code edits inside a callback. An abrupt End/reset can bypass VBA
cleanup and leave the 8 KB native guard allocation until
Office exits; ordinary removal releases them. Keep VBE Error Trapping on Break
on Unhandled Errors when intentionally testing handled callback errors.
The native bridge is embedded in stdWindow; no helper .bas or stdCOM import
is needed. It calls the typed stdICallable.Run vtable entry with stack-only
arguments, which forwards to the supplied callable's RunEx, without method-name
lookup. The user callable receives a normal VBA-owned array so it can resize or
retain its arguments. The bridge is retained across the complete COM call. x86 and x64 code is
generated from Tools/stdWindow-subclass/thunk32.asm and thunk64.asm; x64 unwind
metadata is registered with Windows. The shared code page is read/execute, with a
separate writable state page with active-call and registration counts. Normal removal frees the allocation when
idle. Removal inside the last callback defers freeing until a later removal or
normal singleton termination. Raw SetWindowsHookEx hooks remain unimplemented;
subclassing receives messages for a particular existing window.
Creates a stdWindow object from the Desktop. This object is usually used in order to enumerate over all open windows.
Dim window as stdWindow
For each window in stdWindow.CreateFromDesktop.children
Debug.Print window.name
nextCreates a stdWindow object from a supplied hwnd. It is unlikely this function will be needed unless you already have an hwnd from a different library.
Dim window as stdWindow
set window = stdWindow.CreateFromHwnd(Application.Hwnd)Creates a stdWindow for the window underneath a supplied point.
With MouseGetPos()
set window = stdWindow.Create(.x, .y)
End WithCreates a stdWindow object for a supplied IUnknown object which implements either IOleWindow, IInternetSecurityMgrSite or IShellView. Uses shell API's IUnknown_GetWindow function internally. Useful for obtaining the window object of a UserForm object.
Dim uf as UserForm1: set uf = new UserForm1
uf.show false
With stdWindow.CreateFromIUnknown(uf)
.resizable = true
End WithCreates a stdWindow object which represents the currently open windows context menu (right-click menu).
stdAcc.CreateFromHwnd(stdWindow.CreateFromContextMenu.hwnd).children(1).DoDefaultAction- Get handle() as LongPtr
- Get hDC() as LongPtr
- Get Exists as Boolean
- Get/Let Visible() as Boolean
- Get/Let State() as EWndState 'Normal,Minimised,Maximised
- Get IsFrozen() as Boolean
- Get/Let Caption() as string
- Get Class() as string
- Get RectClient() as Long()
- Get/Let RectWindow() as Long()
- Get/Let X() as Long
- Get/Let Y() as Long
- Get/Let Width() as Long
- Get/Let Height() as Long
- Get ProcessID() as long
- Get ProcessName() as string
- Get/Set Parent() as stdWindow
- Get AncestralRoot() as stdWindow
- Get/Let Style() as Long
- Get/Let StyleEx() as Long
- Get/Let UserData() as LongPtr
- Get/Let WndProc() as LongPtr
- Get/Let Resizable() as Boolean
- Get Children() as Collection
- Get Descendents() as Collection
- Get/Let AlwaysOnTop() as Boolean
- SetHook(idHook, hook, hInstance, dwThreadID) as LongPtr
- Redraw()
- SendMessage(wMsg, wParam, lParam)
- PostMessage(wMsg, wParam, lParam)
- SendMessageTimeout(wMsg, wParam, lParam, TimeoutMilliseconds)
- ClickInput(x?, y?, Button?)
- ClickEvent(x?, y?, Button?, isDoubleClick?, wParam?)
- SendKeysInput(sKeys, bRaw?, keyDelay?)
- SendKeysEvent(sKeys, bRaw?, keyDelay?)
- Show()
- Hide()
- Maximize()
- Minimize()
- Activate()
- Close()
- FindFirst(query)
- FindAll(query)
- Screenshot()
- protInit(Byval hwnd as Longptr/Long)
- protGetNextDescendent(stack, DFS, Prev) as stdWindow
- protGetLookups()
This feature is unimplemented and thus this section will be mostly developer docs
In an ideal world this library would work on all operating systems. The library is definitely possible to implement on all operating systems however currently I mostly lack the time to build and test it on all OSes.
The implementation details of implementing this library on Mac OS X involve use of either ObjC directly (via Obj C runtime C API), or use of a JXA daemon which accesses the System Events application. For example, looping through all windows (via Desktop window) can be done with:
Application("System Events").processes().filter(e=>e.windows().length > 0)which returns
[
Application("System Events").applicationProcesses.byName("Terminal"),
Application("System Events").applicationProcesses.byName("Microsoft Excel"),
Application("System Events").applicationProcesses.byName("TextEdit"),
Application("System Events").applicationProcesses.byName("GitHub Desktop"),
Application("System Events").applicationProcesses.byName("Activity Monitor"),
Application("System Events").applicationProcesses.byName("Google Chrome")
]There might have to be some abstractions cross platform and some methods may be system specific. however