It's easier to implement in C#, but not impossible in Java though you will need to setup native Windows calls via JNI code, JNA or with Java JDK Panama project. A not-accepted answer in the comment mentioned by @Abra has a good way to implement using the Windows API SHOpenFolderAndSelectItems. SHOpenFolderAndSelectItems moves the Explorer view to the foreground if it is hidden, changes the selected files according to the new parameters, and works for multiple selections in the same directory.
This is an all-in-one definition of a class that implements SHOpenFolderAndSelectItems
in pure Java using Early Access JDK17 Project Panama so there is no guarantee it will work at the next release cycle, but demonstrates how native code may get easier to do after JDK17.
The main is very straightforward but requires JDK Panama release and parameters to enable:
set JAVAHOME=C:\java\jdk-17.panama
%JAVAHOME%\bin\java --enable-native-access=ALL-UNNAMED --add-modules jdk.incubator.foreign -cp your.jar panamatest.WindowsExplorer C:\Temp\xyz.txt
public static void main(String[] args) throws Throwable {
WindowsExplorer explorer = new WindowsExplorer();
if (args.length == 0)
throw new IllegalArgumentException("Usage: "+explorer.getClass().getName()+" {folder} [{files}]");
Path[] files = Arrays.stream(args).map(Path::of).toArray(Path[]::new);
explorer.select(files);
}
I have inlined various methods from my own classes below into one implementation class so it not very pretty structure.
import static jdk.incubator.foreign.CLinker.*;
import jdk.incubator.foreign.*;
public class WindowsExplorer {
private static final CLinker CLINKER = CLinker.getInstance();
private static final SymbolLookup LOADER = SymbolLookup.loaderLookup();
static {
System.loadLibrary("ole32");
System.loadLibrary("shell32");
}
private static final int S_OK = 0;
//https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shopenfolderandselectitems
private static final MethodHandle SHOpenFolderAndSelectItems = CLINKER.downcallHandle(
LOADER.lookup("SHOpenFolderAndSelectItems").get()
, MethodType.methodType(int.class, MemoryAddress.class, int.class, MemoryAddress.class, int.class)
, FunctionDescriptor.of(C_LONG, C_POINTER, C_INT, C_POINTER, C_LONG));
// https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-couninitialize
private static final MethodHandle CoUninitialize = CLINKER.downcallHandle(
LOADER.lookup("CoUninitialize").get(), MethodType.methodType(void.class), FunctionDescriptor.ofVoid());
private static final MethodHandle CoInitialize = CLINKER.downcallHandle(
LOADER.lookup("CoInitialize").get()
, MethodType.methodType(int.class, MemoryAddress.class)
, FunctionDescriptor.of(C_LONG, C_POINTER));
// https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shparsedisplayname
private static final MethodHandle SHParseDisplayName = CLINKER.downcallHandle(
LOADER.lookup("SHParseDisplayName").get()
, MethodType.methodType(int.class, MemoryAddress.class, MemoryAddress.class, MemoryAddress.class, int.class, MemoryAddress.class)
, FunctionDescriptor.of(C_LONG, C_POINTER, C_POINTER, C_POINTER, C_LONG, C_POINTER));
private static void checkThat(boolean condition, Supplier<String> error) {
if (!condition) {
String message = error.get();
throw new RuntimeException(message);
}
}
private static void checkResult(int hRes, String func) {
checkThat(hRes == S_OK, () -> "Error: "+func+" result code "+hRes+" / 0x"+Integer.toHexString(hRes));
}
static MemorySegment toCString(String s, Charset charset, SegmentAllocator allocator) {
var bytes = s.getBytes(charset);
// Put it in a MemorySegment with extra termination \0
MemorySegment fixed = allocator.allocate(bytes.length + 4);
fixed.copyFrom(MemorySegment.ofArray(bytes));
return fixed;
}
/** Set up PCIDLIST structure for a path */
private static MemoryAddress SHParseDisplayName(SegmentAllocator allocator, String path) throws Throwable {
MemorySegment cFile = toCString(path, StandardCharsets.UTF_16LE, allocator);
MemoryAddress pdc = MemoryAddress.NULL;
int sfgaoIn = 0;
MemorySegment ptr = allocator.allocate(C_POINTER, MemoryAddress.NULL.toRawLongValue());
MemorySegment psfgaoOut = allocator.allocate(C_LONG, 0);
int hInst = (int) SHParseDisplayName.invokeExact(cFile.address(), pdc, ptr.address(), sfgaoIn, psfgaoOut.address());
checkResult(hInst, "SHParseDisplayName");
return MemoryAccess.getAddress(ptr);
}
/**
* Launches to show Windows Explorer to a folder,
* and passing list of children to select within the folder.
* @throws Throwable
*/
public void select(Path ... children) throws Throwable {
if (children.length == 0)
throw new IllegalArgumentException("No parameters provided for Windows Explorer");
Path first = children[0];
Path folder = first.getParent();
String desc = "select #="+children.length+" FIRST="+first;
System.out.println(desc);
String dir = folder.toAbsolutePath().toString();
try(ResourceScope scope = ResourceScope.newConfinedScope()) {
SegmentAllocator allocator = SegmentAllocator.arenaAllocator(scope);
int hRes = (int) CoInitialize.invokeExact(MemoryAddress.NULL);
checkResult(hRes, "CoInitialize");
try {
MemoryAddress pidlFolder = SHParseDisplayName(allocator, dir);
int cidl = children.length;
MemoryAddress apidl = MemoryAddress.NULL;
if (cidl > 0) {
MemorySegment pidlChildren = allocator.allocateArray(C_POINTER, cidl);
for (int i = 0; i < cidl; i++) {
MemoryAddress pidlChild = SHParseDisplayName(allocator, children[i].toAbsolutePath().toString());
MemoryAccess.setAddressAtIndex(pidlChildren, i, pidlChild);
}
apidl = pidlChildren.address();
}
int dwFlags = 0;
int hres = (int) SHOpenFolderAndSelectItems.invokeExact(pidlFolder.address(), cidl, apidl, dwFlags);
checkResult(hres, "SHOpenFolderAndSelectItems");
}
finally {
CoUninitialize.invokeExact();
}
}
}
}