NSOpenPanel首次可选文件,二次因inode变更无法选择的问题咨询
NSOpenPanel无法选中inode变更文件的问题解决
问题分析
1. 行为是否符合定义?
这是Cocoa文件面板的预期缓存行为。NSOpenPanel会缓存目录的元数据(包括文件inode),当你通过renameTo替换原文件导致inode变更后,面板的缓存未自动同步,因此会将该文件识别为"无效"状态,无法选中。导航离开再返回目录时,面板会重新加载目录内容,缓存更新,文件即可正常选中。
2. 关于validateVisibleColumns
该属性是NSSavePanel的受限制属性,NSOpenPanel作为子类无法直接访问,因此不能通过此方式解决问题。
解决方案
推荐使用NSWorkspace的noteFileSystemChanged:方法,主动通知文件系统变更,强制NSOpenPanel刷新缓存。有两种实现方式:
方式一:通用刷新(每次打开面板时刷新当前目录)
修改NativeOpenFileDialog.m,在创建NSOpenPanel后,刷新面板当前显示的目录:
#import <Cocoa/Cocoa.h> #include <jni.h> #include "NativeOpenFileDialog.h" JNIEXPORT jobjectArray JNICALL Java_NativeOpenFileDialog_run (JNIEnv *env, jclass jcls) { @autoreleasepool { __block jobjectArray arr; dispatch_sync(dispatch_get_main_queue(), ^{ jclass strcls = (*env)->FindClass(env, "java/lang/String"); NSOpenPanel *panel = [NSOpenPanel openPanel]; // 刷新面板当前目录的文件系统缓存 if (panel.directoryURL) { [[NSWorkspace sharedWorkspace] noteFileSystemChanged:panel.directoryURL.path]; } [panel setCanChooseFiles:YES]; [panel setCanChooseDirectories:YES]; [panel setAllowsMultipleSelection:YES]; [panel setAllowsOtherFileTypes:YES]; if ([panel runModal] != NSModalResponseOK) { arr = (*env)->NewObjectArray(env, 0, strcls, NULL); return; } NSArray *urls = [panel URLs]; int count = urls == NULL ? 0 : [urls count]; arr = (*env)->NewObjectArray(env, count, strcls, NULL); if (urls != NULL) { int i = 0; for (NSURL* url in urls) { NSString *path = [url path]; jstring str = (*env)->NewStringUTF(env, [path UTF8String]); (*env)->SetObjectArrayElement(env, arr, i++, str); } } }); return arr; } }
方式二:针对性刷新(仅刷新修改文件的父目录)
如果需要更高效的刷新,可以在Java层将修改后的文件路径传递给Native方法,仅刷新对应父目录:
1. 更新Java类NativeOpenFileDialog.java
import javax.swing.*; public class NativeOpenFileDialog { static { System.loadLibrary("natopndlg"); }; // 重载方法,传入需要刷新的文件路径 public static native String[] run(String... pathsToRefresh); // 保持原有方法兼容 public static native String[] run(); }
2. 更新Native代码NativeOpenFileDialog.m
#import <Cocoa/Cocoa.h> #include <jni.h> #include "NativeOpenFileDialog.h" // 原有无参方法调用重载方法 JNIEXPORT jobjectArray JNICALL Java_NativeOpenFileDialog_run__ (JNIEnv *env, jclass jcls) { return Java_NativeOpenFileDialog_run___3Ljava_lang_String_2(env, jcls, NULL); } // 带路径参数的重载方法 JNIEXPORT jobjectArray JNICALL Java_NativeOpenFileDialog_run___3Ljava_lang_String_2 (JNIEnv *env, jclass jcls, jobjectArray pathsArray) { @autoreleasepool { __block jobjectArray arr; dispatch_sync(dispatch_get_main_queue(), ^{ // 刷新指定文件的父目录 if (pathsArray != NULL) { jsize count = (*env)->GetArrayLength(env, pathsArray); for (int i = 0; i < count; i++) { jstring pathStr = (jstring)(*env)->GetObjectArrayElement(env, pathsArray, i); const char *pathCStr = (*env)->GetStringUTFChars(env, pathStr, NULL); NSString *path = [NSString stringWithUTF8String:pathCStr]; (*env)->ReleaseStringUTFChars(env, pathStr, pathCStr); NSURL *parentDirURL = [[NSURL fileURLWithPath:path] URLByDeletingLastPathComponent]; if (parentDirURL) { [[NSWorkspace sharedWorkspace] noteFileSystemChanged:parentDirURL.path]; } } } jclass strcls = (*env)->FindClass(env, "java/lang/String"); NSOpenPanel *panel = [NSOpenPanel openPanel]; [panel setCanChooseFiles:YES]; [panel setCanChooseDirectories:YES]; [panel setAllowsMultipleSelection:YES]; [panel setAllowsOtherFileTypes:YES]; if ([panel runModal] != NSModalResponseOK) { arr = (*env)->NewObjectArray(env, 0, strcls, NULL); return; } NSArray *urls = [panel URLs]; int urlCount = urls == NULL ? 0 : [urls count]; arr = (*env)->NewObjectArray(env, urlCount, strcls, NULL); if (urls != NULL) { int i = 0; for (NSURL* url in urls) { NSString *path = [url path]; jstring str = (*env)->NewStringUTF(env, [path UTF8String]); (*env)->SetObjectArrayElement(env, arr, i++, str); } } }); return arr; } }
3. 更新Java层Open.java的按钮逻辑
将修改后的文件路径保存,下次打开面板时传入:
public class Open extends JFrame { private String[] modifiedPaths; // 保存上次修改的文件路径 public Open () { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); getContentPane().add(new JButton(new AbstractAction() { public void actionPerformed (ActionEvent e) { SwingWorker<Void,Void> worker = new SwingWorker<Void,Void>() { String[] files = null; public Void doInBackground () throws Exception { // 调用带刷新参数的Native方法 files = NativeOpenFileDialog.run(modifiedPaths); return null; } protected void done () { if (files != null) { modifiedPaths = files.clone(); // 保存本次修改的路径 for (String s : files) { File old = new File(s); File tmp = new File("tempfile"); try { Files.copy(old.toPath(), tmp.toPath(), StandardCopyOption.REPLACE_EXISTING); if (tmp.renameTo(old)) System.out.println(s + ": ok"); else System.err.println(s + ": rename fail"); } catch (Throwable ex) { System.err.println(s + ": copy fail"); } } } } }; worker.execute(); } })); pack(); setVisible(true); } // ... main方法不变 }
编译验证
重新编译运行:
$ make clean && make $ java Open
首次修改文件后,再次打开NSOpenPanel时,原文件可直接选中,无需导航离开目录。
内容的提问来源于stack exchange,提问作者Jeff Holt
相关产品推荐
相关产品推荐

