0

i'm trying to make a program that downloads some files, and now i want to open the folder (on the windows explorer) where those files are (it's always the same folder) and select the last file that was downloaded, i found this code to do it on java:

private void openFile(File f) {
    try {
        Runtime.getRuntime().exec("explorer.exe  /select," + f.getAbsolutePath());            
    } catch (Exception ex) {
        System.out.println("Error - " + ex);
    }
}

It works, but every time i press the button to open the folder it opens a new folder to select the file so i end up with many windows opened. Is there a way to do this on the folder that is already open?

I've been looking on the web for this but i only found the same code that i'm already using. All of those other posts about this issue are from many years ago so i don't know if it used to work that way on previous versions.

Thank you so much if any of you can help me.

Juan David
  • 11
  • 1
  • 1
    Throw it away and use a `JFileChooser`. – user207421 Jan 12 '21 at 03:31
  • I'd be interested to know how this selects the last file downloaded. I've not tried this but my assumption, based upon my knowledge of Windows Explorer would be that the sort order last used by your end user when that directory was previously opened in Windows Explorer could affect things. – Compo Jan 12 '21 at 03:38
  • 1
    I already have the File object on the program so i don't understand what do i need a filechooser for. The program download the files, and i want to show those downloaded files on the explorer and highlight the last one downloaded but it open a new window every time i execute the command. – Juan David Jan 12 '21 at 08:36
  • 2
    Does this help? [Make current explorer.exe window select a file](https://stackoverflow.com/questions/28763353/make-current-explorer-exe-window-select-a-file) – Abra Jan 12 '21 at 11:28
  • 2
    Looks like it's easier to do in C#. [How can I set an existing Explorer.exe instance to select a file?](https://stackoverflow.com/questions/8182494/how-can-i-set-an-existing-explorer-exe-instance-to-select-a-file) – Abra Jan 12 '21 at 11:47
  • So it's impossible with java. Thank you i'll try to learn c# to try to do it that way. – Juan David Jan 12 '21 at 15:43

1 Answers1

2

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();
            }
        }
    }
}
DuncG
  • 12,137
  • 2
  • 21
  • 33