我有一个JFileChooser,我需要以编程方式将其currentDirectory设置为包含多个SMB共享(例如\\blah
)的networking主机。 从技术上讲,这不是一个“目录”,而是一个表示可用共享列表的shell文件夹。
JFileChooser在导航到特定共享(例如\\blah\someShare
)时没有问题,但不能处理主机“目录”本身(例如\\blah
)。
用户可以通过“Network”shell文件夹,或通过查找特定共享并导航到其父目录来导航到JFileChooser中的这些“目录”。 debugging显示,这个目录下的这个目录被表示为一个Win32ShellFolder2
。 我所有以编程方式设置currentDirectory的尝试都失败了。
可以创buildnew File("\\\\blah")
,但从Java的angular度来看并不存在。
chooser.setCurrentDirectory(new File("\\\\blah"));
因为JFileChooser
检查给定的目录是否存在而失败,并且new File("\\\\blah").exists()
返回false。
File dir = new File("\\\\blah").getCanonicalFile();
失败,例外:
java.io.IOException: Invalid argument at java.io.WinNTFileSystem.canonicalize0(Native Method) at java.io.WinNTFileSystem.canonicalize(WinNTFileSystem.java:428) at java.io.File.getCanonicalPath(File.java:618) at java.io.File.getCanonicalFile(File.java:643)
File dir = ShellFolder.getShellFolder(new File("\\\\blah"));
失败,例外:
java.io.FileNotFoundException at sun.awt.shell.ShellFolder.getShellFolder(ShellFolder.java:247)
File dir = new Win32ShellFolderManager2().createShellFolder(new File("\\\\blah"));
失败,例外:
java.io.FileNotFoundException: File \\blah not found at sun.awt.shell.Win32ShellFolderManager2.createShellFolder(Win32ShellFolderManager2.java:80) at sun.awt.shell.Win32ShellFolderManager2.createShellFolder(Win32ShellFolderManager2.java:64)
Path dir = Paths.get("\\\\blah");
失败,例外:
java.nio.file.InvalidPathException: UNC path is missing sharename: \\blah at sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:118) at sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:77) at sun.nio.fs.WindowsPath.parse(WindowsPath.java:94) at sun.nio.fs.WindowsFileSystem.getPath(WindowsFileSystem.java:255) at java.nio.file.Paths.get(Paths.java:84)
曾几何时,我面对这样一个任务,我可以说这真的很烦人。 首先听起来很简单,但是当你开始挖掘和尝试时,会出现越来越多的问题。 我想谈谈我的旅程。
从我所了解的情况来看,这里的事情是\\ComputerName\
并不是真正的文件系统。 内容取决于您的身份验证凭据,这是一个abstaction层。 只有windows机器才会这么做,这将破坏Java的系统独立性。 总结起来,它不是一个File
对象可以指向的东西。 您可以使用Samba库jcifs,但在它们的实现中, SmbFile
类需要用户身份验证,并且与java File
类不兼容。 所以你不能在jFileChooser
使用它。 可悲的是,他们没有改变它的意图,因为你可以在这里阅读。
我试图自己开发一个文件包装器,充当File
和SmbFile
Class的混合。 但是因为它给我带来了噩梦,所以我放弃了。
然后,我有了写一个简单的对话框的想法,列出了以前用jcifs
扫描的网络共享,并让用户选择其中之一。 然后一个jFileChooser
与选定的份额应该显示出来。
当我实现这个想法的时候,整个问题的超级简单的解决方案把我踢到了一边。
因为指向\\ComputerName\ShareName
并单击One level higher
按钮绝对没有问题,所以必须可以重现此步骤。 它是。 实际上,在jFileChooser
的引擎下,我发现像MyComputer
或Network
这样的地方是ShellFolders
,它们是File
Objects的特例。 但是这些Shell文件夹受到保护,而不是Java API的一部分。
所以我不能直接实例化这些。 但是我可以访问FileSystemView
来处理文件系统上的系统相关视图,例如为特殊位置创建这些Shell文件夹。
所以很长的文字为一个简短的答案。 如果您知道一个共享名,请为此共享名创建一个文件。 然后使用FileSystemView
来获取其父文件。 瞧,你可以使用生成的File
对象来扩展ShellFolder
与jFileChooser
。
File f = new File("\\\\ComputerName\\ShareFolder"); FileSystemView fsv = FileSystemView.getFileSystemView(); f = fsv.getParentDirectory(f); JFileChooser fileChooser = new JFileChooser(); fileChooser.setCurrentDirectory(f);
最后一个注意:这个解决方案不会要求你提供登录信息。 因此,在这里使用它们之前,必须在Windows中访问这些共享。
编辑:对不起,长文本。 除夕和我喝醉了。 现在我想补充一点,我发现了另一种方式。
FileSystemView fsv = FileSystemView.getFileSystemView(); File Desktop = fsv.getRoots()[0];
在Windows系统上,这应该给你的桌面文件夹。 如果你在这里列出所有的文件:
for(File file : Desktop.listFiles()) System.out.println(file.getName());
你会注意到一些奇怪的名字:
::{20D04FE0-3AEA-1069-A2D8-08002B30309D} // My Computer ::{F02C1A0D-BE21-4350-88B0-7367FC96EF3C} // Network ::{031E4825-7B94-4DC3-B131-E946B44C8DD5} // User Directory
我不知道这些代码对于所有的Windows版本都是一样的,但似乎是针对Windows7的。 所以你可以使用这个来获得网络外壳文件夹,然后在这台计算机上分享。
File Network = fsv.getChild(Desktop, "::{F02C1A0D-BE21-4350-88B0-7367FC96EF3C}"); File Host = fsv.getChild(Network, "COMPUTERNAME"); // Must be in Capital Letters
这里的问题是,这将需要大约10秒,因为网络文件夹被扫描的内容。
我发现了一个特定于Windows的解决方案,它允许从单独的名称(例如\\blah
或\\blah\
)导航到任何可访问的计算机节点,而不需要枚举Network shell文件夹,也不需要预先知道给定的网络共享节点。
在调试这个问题的时候,我发现一个ShellFolder
必须为给定的计算机路径创建才能导航到它。 Win32ShellFolderManager2.createShellFolder()
将调用给定文件上的File.getCanonicalPath()
,该文件将依次调用WinNTFileSystem.canonicalize()
。 计算机路径上最后一次调用总是失败。 经过大量的实验,我能够通过将File对象包装在绕过WinNTFileSystem.canonicalize()
东西中,为任何可访问的计算机路径创建ShellFolder
:
/** * Create a shell folder for a given network path. * * @param path File to test for existence. * @return ShellFolder representing the given computer node. * @throws IllegalArgumentException given path is not a computer node. * @throws FileNotFoundException given path could not be found. */ public static ShellFolder getComputerNodeFolder(String path) throws FileNotFoundException { File file = new NonCanonicalizingFile(path); if (ShellFolder.isComputerNode(file)) { return new Win32ShellFolderManager2().createShellFolder(file); } else { throw new IllegalArgumentException("Given path is not a computer node."); } } private static final class NonCanonicalizingFile extends File { public NonCanonicalizingFile(String path) { super(path); } @Override public String getCanonicalPath() throws IOException { // Win32ShellFolderManager2.createShellFolder() will call getCanonicalPath() on this file. // Base implementation of getCanonicalPath() calls WinNTFileSystem.canonicalize() which fails on // computer nodes (eg "\\blah"). We skip the canonicalize call, which is safe at this point because we've // confirmed (in approveSelection()) that this file represents a computer node. return getAbsolutePath(); } }
无可否认,这个解决方案有一些边界情况(例如\\blah\
works,但\\blah\someShare\..\
不会),理想情况下OpenJDK应该在这些问题上解决这些问题。 这也是一个特定于操作系统和特定实现的解决方案,不能在OpenJDK-on-Windows安装程序之外使用。
将它与JFileChooser
集成的最简单方法是覆盖它的approveSelection()
方法。 这允许用户在对话框中键入一个计算机路径( \\blah
或\\blah\
),然后按Enter键在那里导航。 当给出不存在或不可访问的路径时显示警报消息。
JFileChooser chooser = new JFileChooser() { @Override public void approveSelection() { File selectedFile = getSelectedFile(); if (selectedFile != null && ShellFolder.isComputerNode(selectedFile)) { try { // Resolve path and try to navigate to it setCurrentDirectory(getComputerNodeFolder(selectedFile.getPath())); } catch (FileNotFoundException ex) { // Alert user if given computer node cannot be accessed JOptionPane.showMessageDialog(this, "Cannot access " + selectedFile.getPath()); } } else { super.approveSelection(); } } }; chooser.showOpenDialog(null);
或者, FileSystemView
可以通过覆盖它的createFileObject(String)
方法来检查计算机路径。 这允许将计算机路径传递给JFileChooser(String,FileSystemView)
构造函数,并允许用户导航到可访问的计算机路径。 但是,仍然没有简单的方法向用户发送无法访问的计算机路径,而不必重写JFileChooser.approveSelection()
:
public static class ComputerNodeFriendlyFileSystemView extends FileSystemView { private final FileSystemView delegate; public ComputerNodeFriendlyFileSystemView(FileSystemView delegate) { this.delegate = delegate; } @Override public File createFileObject(String path) { File placeholderFile = new File(path); if (ShellFolder.isComputerNode(placeholderFile)) { try { return getComputerNodeFolder(path); } catch (FileNotFoundException ex) { return placeholderFile; } } else { return delegate.createFileObject(path); } } // All code below simply delegates everything to the "delegate" @Override public File createNewFolder(File containingDir) throws IOException { return delegate.createNewFolder(containingDir); } @Override public boolean isRoot(File f) { return delegate.isRoot(f); } @Override public Boolean isTraversable(File f) { return delegate.isTraversable(f); } @Override public String getSystemDisplayName(File f) { return delegate.getSystemDisplayName(f); } @Override public String getSystemTypeDescription(File f) { return delegate.getSystemTypeDescription(f); } @Override public Icon getSystemIcon(File f) { return delegate.getSystemIcon(f); } @Override public boolean isParent(File folder, File file) { return delegate.isParent(folder, file); } @Override public File getChild(File parent, String fileName) { return delegate.getChild(parent, fileName); } @Override public boolean isFileSystem(File f) { return delegate.isFileSystem(f); } @Override public boolean isHiddenFile(File f) { return delegate.isHiddenFile(f); } @Override public boolean isFileSystemRoot(File dir) { return delegate.isFileSystemRoot(dir); } @Override public boolean isDrive(File dir) { return delegate.isDrive(dir); } @Override public boolean isFloppyDrive(File dir) { return delegate.isFloppyDrive(dir); } @Override public boolean isComputerNode(File dir) { return delegate.isComputerNode(dir); } @Override public File[] getRoots() { return delegate.getRoots(); } @Override public File getHomeDirectory() { return delegate.getHomeDirectory(); } @Override public File getDefaultDirectory() { return delegate.getDefaultDirectory(); } @Override public File createFileObject(File dir, String filename) { return delegate.createFileObject(dir, filename); } @Override public File[] getFiles(File dir, boolean useFileHiding) { return delegate.getFiles(dir, useFileHiding); } @Override public File getParentDirectory(File dir) { return delegate.getParentDirectory(dir); } }
用法:
ComputerNodeFriendlyFileSystemView fsv = new ComputerNodeFriendlyFileSystemView(FileSystemView.getFileSystemView()); JFileChooser chooser = new JFileChooser("\\\\blah", fsv); chooser.showOpenDialog(null);