前言
平时整理电脑文件时,经常需要给文件夹改别名、加备注、换自定义图标,但每次都要手动改 desktop.ini,又要设系统属性又要改编码,非常麻烦。于是用 JavaFX 写了个可视化工具 WindirManager,拖拽文件夹就能一键管理别名、备注、图标,顺便统计文件夹大小和文件数量。
本文从工具用途、核心代码实现到打包发布完整讲一遍,文末附打包脚本,复制就能用。

一、工具用途与核心功能
WindirManager 是一个 Windows 平台的文件夹可视化管理工具,基于 JavaFX + JNA 开发,主要解决手动修改 desktop.ini 的痛点。
核心功能
-
文件夹拖拽导入:支持拖拽或点击选择文件夹,自动读取信息
-
文件夹属性一览:实时统计文件夹大小、文件数、子文件夹数、修改时间
-
文件夹别名设置:可视化编辑 LocalizedResourceName,资源管理器直接显示中文名
-
文件夹备注设置:鼠标悬停显示自定义备注说明
-
自定义文件夹图标:从系统 DLL 图标库选取图标,一键写入 desktop.ini
-
INI 配置预览:实时预览即将写入的 desktop.ini 内容,确认后再保存
-
文件列表浏览:展示当前目录下所有文件/文件夹,带图标、类型、大小、修改时间
-
修改日志查询:记录每次文件夹修改操作,支持历史回溯
适用场景
-
给项目文件夹设置中文别名,英文路径不影响开发
-
给常用资料文件夹加备注说明,鼠标悬停就能看到
-
批量美化文件夹图标,让资源管理器更有辨识度
-
快速查看文件夹大小和文件数量,不用右键属性等半天
二、技术栈与环境准备
| 技术 | 版本 | 用途 |
|---|---|---|
| JDK | 17+ | 运行环境 |
| JavaFX | 17.0.6 | UI 界面框架 |
| JNA | 5.14.0 | 调用 Windows DLL 获取系统图标 |
| ini4j | 0.5.4 | 解析与写入 desktop.ini |
| Maven | 3.x | 项目构建与依赖管理 |
| jpackage | JDK 自带 | 打包成 exe 安装包/绿色版 |
特别说明
⚠️ 注意:本工具仅支持 Windows 系统,依赖 Windows API 和 desktop.ini 机制,macOS/Linux 无法使用。
测试环境说明
开发测试环境:Windows 11 + JDK 17 + JavaFX 17.0.6,理论上 Win10 也能用,其他版本没测过。
环境配置
开发前需要配置两个环境变量:
-
JAVA_HOME:指向 JDK 17+ 安装目录
-
JAVAFX_HOME:指向 JavaFX SDK 解压目录(下载地址:openjfx.io)
三、核心代码实现
3.1 可复用拖拽组件:FileDropPane
封装了一个通用的文件拖拽面板组件,支持单选/多选、仅文件/仅目录、点击选择/仅拖拽等多种模式,项目里直接 new 就能用。
public class FileDropPane extends StackPane {
private OnFileDropListener listener;
private boolean onlyDir; // 仅目录
private boolean onlyDrag; // 仅拖拽模式
private boolean multiple; // 多选
// 拖拽进入:高亮样式
private void setOnDragEntered(DragEvent event) {
label.setStyle(hoverStyle);
}
// 拖拽悬浮:校验是否本地文件
private void setOnDragOver(DragEvent event) {
Dragboard dragboard = event.getDragboard();
if (dragboard.hasFiles()) {
event.acceptTransferModes(TransferMode.COPY);
}
event.consume();
}
// 拖拽释放:处理文件列表
private void setOnDragDropped(DragEvent event) {
List<File> files = event.getDragboard().getFiles();
// 单选模式校验
if (!multiple && files.size() > 1) {
UIUtil.alert("拖入失败", "一次只能拖入单个文件");
return;
}
// 仅目录模式校验
if (onlyDir && !files.get(0).isDirectory()) {
UIUtil.alert("拖入失败", "仅允许拖入文件夹");
return;
}
if (listener != null) {
listener.onFileDrop(files.get(0));
}
event.setDropCompleted(true);
event.consume();
}
// 回调接口
public interface OnFileDropListener {
default void onFilesDrop(List<File> files) {}
default void onFileDrop(File file) {}
}
}
使用方式:
FileDropPane fileDropPane = new FileDropPane();
fileDropPane.setMultiple(false); // 单选
fileDropPane.setOnlyDir(true); // 仅文件夹
fileDropPane.setOnlyDrag(false); // 点击+拖拽双模式
fileDropPane.setListener(new FileDropPane.OnFileDropListener() {
@Override
public void onFileDrop(File file) {
// 处理选中的文件夹
dir = file;
readDir();
}
});
3.2 desktop.ini 读写封装:IniFile
desktop.ini 是 Windows 文件夹配置文件,编码特殊(有的是 UTF-8,有的是 GBK),直接读写容易乱码。封装了 IniFile 类,自动识别编码,支持增删改查。
@Data
public class IniFile {
private Charset charset = Charset.forName("UTF-8");
private Map<String, List<IniOption>> selectionMap;
private String selectionGetKey; // 当前操作分区
// 加载INI文件,自动识别编码
public void loadIniFile(String iniFilePath) {
Path path = Path.of(iniFilePath);
Charset iniCharset = MyFileUtil.getIniCharset(path);
this.setCharset(iniCharset);
Ini ini = new Ini();
ini.getConfig().setEscape(false);
ini.load(path.toFile());
// 遍历所有分区和配置项
ini.keySet().forEach(sectionName -> {
List<IniOption> optList = new ArrayList<>();
Profile.Section section = ini.get(sectionName);
for (String optKey : section.keySet()) {
String val = section.get(optKey);
optList.add(new IniOption(optKey, val));
}
addSelection(sectionName, optList);
});
}
// 设置配置项值,不存在则自动创建
public void setIniOption(String optionKey, String value) {
if (this.selectionMap == null) {
this.selectionMap = new HashMap<>();
}
List<IniOption> optList = selectionMap.computeIfAbsent(
selectionGetKey, k -> new ArrayList<>()
);
IniOption option = getIniOption(optionKey);
if (option == null) {
optList.add(new IniOption(optionKey, value));
} else {
option.setValue(value);
}
}
// 生成标准INI格式文本
public String viewContent() {
if (selectionMap == null || selectionMap.isEmpty()) {
return "";
}
StringBuilder builder = new StringBuilder();
selectionMap.forEach((sectionName, optList) -> {
builder.append("[").append(sectionName).append("]\n");
optList.forEach(o -> {
builder.append(o.getKey()).append("=").append(o.getValue()).append("\n");
});
builder.append("\n");
});
return builder.toString();
}
// 配置项内部类
@Data
public static class IniOption {
private String key;
private String value;
private String comment;
}
}
3.3 JNA 调用 Windows API 获取系统图标
通过 JNA 调用 shell32.dll 的 ExtractIcon 方法,从系统 DLL 文件中提取指定索引的图标。这样就能让用户像系统一样选择 shell32.dll、imageres.dll 里的内置图标。
public interface WinShell32Api extends ShellAPI {
WinShell32Api INSTANCE = Native.load("shell32", WinShell32Api.class);
// 从DLL中提取图标
HICON ExtractIcon(HINSTANCE hInst, String lpszExeFileName, int nIconIndex);
}
@Service
public class WinIconServiceImpl implements IWinIconService {
@Override
public WinDllIcon getDllIcon(String dllPath, int iconIndex) {
// 调用JNA提取图标
HICON hIcon = WinShell32Api.INSTANCE.ExtractIcon(
new HINSTANCE(), dllPath, iconIndex
);
// 转换为JavaFX Image对象
BufferedImage bufferedImage = ImageIO.read(...);
Image fxImage = SwingFXUtils.toFXImage(bufferedImage, null);
WinDllIcon icon = new WinDllIcon();
icon.setDllPath(dllPath);
icon.setIconIndex(iconIndex);
icon.setImage(fxImage);
return icon;
}
// 写入desktop.ini并设置系统属性
@Override
public boolean writeDesktopIniFile(Path folder, String iniContent) {
Path iniPath = folder.resolve("desktop.ini");
MyFileUtil.writeFile(iniPath.toString(), iniContent, StandardCharsets.UTF_8);
// 设置隐藏+系统属性,Windows才会生效
Files.setAttribute(iniPath, "dos:hidden", true);
Files.setAttribute(iniPath, "dos:system", true);
Files.setAttribute(folder, "dos:system", true);
return true;
}
}
关键点:desktop.ini 必须设置「隐藏 + 系统」属性,所在文件夹也要设置「系统」属性,否则 Windows 资源管理器不会读取配置。
3.4 主界面搭建
界面用 SplitPane 分成上下两部分,上半部分是拖拽区+属性面板,下半部分是文件列表+INI预览,左右分栏布局。
public void start(Stage primaryStage) {
SplitPane pane = new SplitPane();
SplitPane topPan = new SplitPane();
HBox topLeft = new HBox(); // 拖拽区
VBox topRight = new VBox(); // 属性面板
TableView<FileInfo> table = new TableView<>(); // 文件列表
TextArea iniPreView = new TextArea(); // INI预览
// 上半部分:拖拽区 + 属性面板
topPan.getItems().addAll(topLeft, topRight);
topPan.setDividerPositions(0.1);
// 下半部分:文件列表 + INI预览
HBox center = new HBox();
center.getChildren().addAll(dirChildPane, iniBox);
HBox.setHgrow(dirChildPane, Priority.ALWAYS);
// 整体垂直分割
pane.getItems().addAll(topPan, center);
pane.setOrientation(Orientation.VERTICAL);
pane.setDividerPositions(0.2);
Scene scene = new Scene(pane);
primaryStage.setScene(scene);
primaryStage.show();
}
四、打包发布:jpackage 一键脚本
开发完成后,用 JDK 自带的 jpackage 工具打包成绿色免安装文件夹(app-image),用户解压就能用,不需要装 JRE。
打包脚本
把脚本放在项目的 package 目录下,双击运行,自动执行 Maven 打包 → 清理旧文件 → jpackage 构建 → 压缩成 zip。
@echo off
setlocal enabledelayedexpansion
chcp 65001 >nul
:: ========== 项目配置(每个项目改这里) ==========
set "APP_NAME=WindirManager"
set "APP_VERSION=1.0"
set "MAIN_CLASS=com.jcxg.tools.windirmgmt.AppBootApplication"
set "JAR_NAME=windir_mgmt-1.0-SNAPSHOT-jar-with-dependencies.jar"
:: ==============================================
:: ========== 全局配置(一次配置永久生效) ==========
set "JAVAFX_LIB=%JAVAFX_HOME%\lib"
set "GLOBAL_JDK_MODULES=java.logging,java.desktop,java.xml"
set "GLOBAL_FX_MODULES=javafx.controls,javafx.fxml,javafx.graphics"
set "GLOBAL_JVM_OPTS=--enable-native-access=jna,jna.platform --add-opens java.base/java.lang=jna -Dprism.order=sw -Dfile.encoding=UTF-8"
set "OUT_DIR=dist"
set "APP_TYPE=app-image"
:: ==============================================
:: 项目根目录 = 脚本上级目录
set "PROJECT_ROOT=%~dp0..\"
set "TARGET_JAR=!PROJECT_ROOT!target\%JAR_NAME%"
echo ==============================================
echo JavaFX 打包工具
echo ==============================================
echo 项目名称 :!APP_NAME!
echo 项目根目录 :!PROJECT_ROOT!
echo 打包类型 :!APP_TYPE!
echo ==============================================
echo.
:: 1. Maven 打包
echo [1/5] 执行 Maven 打包
cd /d "!PROJECT_ROOT!"
mvn clean package
if %errorlevel% neq 0 (
echo ✕ Maven 打包失败,终止脚本!
pause
exit /b 1
)
echo ✔ Maven 构建完成
echo.
:: 2. 校验依赖
if not exist "%JAVAFX_LIB%" (
echo ✕ 错误:JAVAFX_HOME 环境变量未配置
pause
exit /b 1
)
echo ✔ JavaFX 依赖校验通过
echo.
:: 3. 清理旧文件
echo [2/5] 清理历史打包缓存
if exist "!PROJECT_ROOT!!OUT_DIR!" (
rmdir /s /q "!PROJECT_ROOT!!OUT_DIR!"
echo ✔ 旧目录清理完成
)
echo.
:: 4. jpackage 打包
echo [3/5] 开始 jpackage 构建
set "ALL_MODULES=!GLOBAL_FX_MODULES!,!GLOBAL_JDK_MODULES!"
jpackage ^
--name !APP_NAME! ^
--app-version !APP_VERSION! ^
--input "!PROJECT_ROOT!target" ^
--main-jar !JAR_NAME! ^
--main-class !MAIN_CLASS! ^
--module-path "!JAVAFX_LIB!" ^
--add-modules !ALL_MODULES! ^
--java-options "!GLOBAL_JVM_OPTS!" ^
--win-console ^
--type !APP_TYPE! ^
--dest "!PROJECT_ROOT!!OUT_DIR!" ^
--jlink-options "--strip-debug" ^
--jlink-options "--no-man-pages" ^
--jlink-options "--no-header-files" ^
--jlink-options "--compress=2"
if %errorlevel% neq 0 (
echo ✕ 打包执行失败
pause
exit /b 1
)
echo ✔ 打包构建完成
echo.
:: 5. 压缩成 zip
echo [4/5] 压缩程序包
set "ZIP_PATH=!PROJECT_ROOT!!OUT_DIR!\!APP_NAME!_v!APP_VERSION!.zip"
where 7z >nul 2>&1
if %errorlevel% equ 0 (
echo ✔ 检测到 7-Zip,使用高压缩
7z a -tzip -mx9 "!ZIP_PATH!" "!PROJECT_ROOT!!OUT_DIR!\!APP_NAME!" >nul
) else (
echo ⚠ 未检测到 7-Zip,使用系统 tar 压缩
tar -a -c -f "!ZIP_PATH!" -C "!PROJECT_ROOT!!OUT_DIR!" !APP_NAME!
)
echo ✅ 压缩包生成:!ZIP_PATH!
echo.
echo [5/5] 全部完成!
echo 输出目录:!PROJECT_ROOT!!OUT_DIR!\!APP_NAME!
echo.
pause
五、踩坑与注意事项
1. JavaFX 打包闪退问题
打包后 exe 双击一闪而过,看不到报错。解决方法:
-
打包时加
--win-console参数,保留控制台窗口,崩溃时能看到错误日志 -
加 JVM 参数
-Dprism.order=sw,强制使用软件渲染,避开显卡驱动兼容性问题 -
加
-Dfile.encoding=UTF-8,避免中文乱码导致崩溃
2. JNA 模块访问权限
JDK 17+ 模块化后,JNA 访问 sun.misc 会报错。必须加两个 JVM 参数:
--enable-native-access=jna,jna.platform
--add-opens java.base/java.lang=jna
3. desktop.ini 不生效
写完配置但文件夹没变,检查这几点:
-
desktop.ini 文件是否设置了「隐藏 + 系统」属性
-
文件夹本身是否设置了「系统」属性
-
INI 文件编码是否正确(UTF-8 带 BOM / GBK 都可能)
-
刷新资源管理器或重启 explorer.exe 再看
4. 打包后删不掉目录
程序闪退之后,Java 后台进程可能残留,锁住了文件夹。解决:
-
任务管理器杀掉对应 Java 进程再删
-
代码里加全局异常捕获,崩溃时正常退出,避免进程残留
5. 图标索引从 0 开始
Windows DLL 里的图标索引是从 0 开始计数的,shell32.dll 里文件夹图标通常是索引 3、4 左右,imageres.dll 里图标更多。点击图标按钮可以循环切换预览。
六、总结
这个工具虽然功能不复杂,但涵盖了 JavaFX 界面开发、JNA 原生调用、INI 文件处理、jpackage 打包发布等完整的桌面应用开发流程,适合作为 JavaFX 入门练手项目。
打包脚本已经封装成通用模板,换个项目改改顶部配置就能复用,不用每次都查 jpackage 参数。
后续可以扩展的方向:批量处理多个文件夹、自定义图标上传、文件夹标签分类、右键菜单集成等。
以上就是这个小工具的全部内容了,功能比较简单,代码也还有很多可以优化的地方,欢迎大家提建议和 PR。
源码已上传至码云: https://gitee.com/javafx-proj/windirmgmt.git