How to make a Java Swing app as a child of Windows native HWND / Set up ScreenSaver preview panel
NOTE: The code examples here will not compile and omits the error code checks, but is just to show your the order of Windows API calls needed to dock any Java Swing frame inside Windows native HWND parent - for your own implementation in JNI /JNA or JDK16+ Panama.
Firstly work out parent Windows long hWndParent
.
For Windows Screensavers this is passed into the "SCR" executable as with "/C" or "/P" (upper/lower) plus parent HWND argument.
It may have SPACE or ":" separating the flag and HWND, or as two arguments FLAG + HWND so
make sure you parse long hWndParent handle from the number passed in to main(String[] args)
as:
args = {"/P 1234567"};
// OR {"/c", "1234567"}
// OR {"/c:1234567"}
Create your Java Swing UI - either JFrame or sub-class without title bars. You have to make it visible so that you can retrieve the HWND.
JFrame frame = new JFrame();
frame.setUndecorated(true);
frame.setVisible(true);
Find your Java Swing HWND long hWndChild
. There are other SO posts on getting HWND of Swing components, but I call the EnumWindows
API.
You have to supply a callback WNDENUMPROC lpEnumFunc
function to EnumWindows
. My example passes in the Java process PID so it also works for ANY native Windows process ID too:
/**
* EnumWindows function (winuser.h)
* https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumwindows
* https://learn.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms633498(v=vs.85)
* BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam);
*/
/**
* GetWindowThreadProcessId function (winuser.h)
* https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowthreadprocessid
* DWORD GetWindowThreadProcessId(HWND hWnd, LPDWORD lpdwProcessId);
*/
This is a stripped down outline of calls to retrieve HWND in JDK Panama:
class User32 {
private final List<Long> hWnds = new ArrayList<>();
// BOOL EnumWindowsProc(HWND hwnd, LPARAM lParam);
int EnumWindowsCallback(long hwnd, long pidToFind) { // MemoryAddress in Panama
// Set up lpdwProcessId+ use with:
int threadId = (int)GetWindowThreadProcessId(hwnd, lpdwProcessId);
long procId = // read address from lpdwProcessId
if(procId == pidToFind) {
hWnds.add(hwnd);
}
return TRUE;
}
long getHWND(long findPid) {
// Set up lpEnumFunc as pointer to this.EnumWindowsCallback:
EnumWindows(lpEnumFunc, findPid);
return hWnds.get(0);
}
}
long findPid = ProcessHandle.current().pid();
long hWndChild = new User32().getHWND(findPid);
Size your preview panel / Java Swing component long hWndChild
to match the Windows native hWndParent
:
/**
* GetClientRect function (winuser.h)
* https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getclientrect
* BOOL GetClientRect(HWND hWnd, LPRECT lpRect);
*/
Rectangle clientRect = // call GetClientRect(hWndParent, yourRECT) => convert to Java Rectangle ;
frame.setBounds(clientRect);
Dock your preview panel / Java Swing component long hWndChild
to match the Windows native hWndParent
- this ensures Z-order is correct:
dockToParent(hWndChild, hWndParent);
public static void dockToParent(long hWndChild, long hWndNewParent) {
// Set the preview window as the parent of this window
/**
* Set Windows Parent
* SetParent function (winuser.h)
* https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setparent
* HWND SetParent(HWND hWndChild, HWND hWndNewParent);
*/
SetParent(hWndChild, hWndNewParent);
// Make child window so it will close when the parent dialog closes
int GWL_STYLE = -16;
/**
* GetWindowLongPtrW function (winuser.h)
* https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowlongptrw
* LONG_PTR GetWindowLongPtrW(HWND hWnd, int nIndex);
*/
long gwl = (long)GetWindowLongPtrW(hWndChild, GWL_STYLE);
// https://learn.microsoft.com/en-us/windows/win32/winmsg/window-styles
int WS_CHILD = 0x40000000;
/**
* SetWindowLongW function (winuser.h)
* https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowlongw
* LONG SetWindowLongW(HWND hWnd, int nIndex, LONG dwNewLong);
*/
int prev = (int)SetWindowLongW(hWndChild, GWL_STYLE, (int)gwl | WS_CHILD);
}
At this point, your Java Swing frame should be fixed inside the Windows HWND parent window, and it stays on top of, moves around with, and disappears when that window is closed.