书上只是写了界面,顺便把功能完善了。
QLineEditDropList类 - 在普通的QLineEdit上,增加了下拉框(历史记录)。
package jqt; import io.qt.core.QStringList; import io.qt.core.QStringListModel; import io.qt.core.Qt; import io.qt.gui.QFocusEvent; import io.qt.gui.QMouseEvent; import io.qt.widgets.QCompleter; import io.qt.widgets.QLineEdit; import io.qt.widgets.QWidget; public class QLineEditDropList extends QLineEdit{ //增加下拉框 QStringListModel model; QCompleter completer; QStringList history; public final Signal1<QMouseEvent> doubleClicked = new Signal1(); public QLineEditDropList(){ this(null); } public QLineEditDropList(QWidget parent){ super(parent); history = new QStringList(); model = new QStringListModel(this); completer = new QCompleter(model, this); completer.setCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive); completer.setCompletionMode(QCompleter.CompletionMode.UnfilteredPopupCompletion); setCompleter(completer); } @Override public void focusInEvent(QFocusEvent e){ super.focusInEvent(e); completer.complete(); } @Override public void mouseDoubleClickEvent(QMouseEvent e){ doubleClicked.emit(e); super.mouseDoubleClickEvent(e); } public void add(String text){ if (text.isEmpty()) return; // 去重并限制数量 history.removeAll(text); history.append(text); model.setStringList(history); } }
QFindFileDialog类
package jqt; import io.qt.core.*; import io.qt.widgets.*; import java.io.IOException; import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; import java.util.regex.Pattern; public class QFindFileDialog extends QDialog{ QLabel namedLabel; QLabel lookInLabel; QLineEditDropList lookInLineEdit; QLineEdit namedLineEdit; QCheckBox includeSubdirectoriesCheckBox; QTableWidget tableWidget; QLabel messageLabel; QPushButton findButton; QPushButton stopButton; QStringList labels; Boolean stopSearch = false, includeSubdirectories = true; QDateTime lastCheck = QDateTime.currentDateTime().addSecs(-60);// 1分钟前 //final QEvent.Type INVOKE_EVENT_TYPE = QEvent.Type.resolve(QEvent.Type.User.value() + 1); public QFindFileDialog(){ this(null); } public QFindFileDialog(QWidget parent){ super(parent); namedLabel = new QLabel("文件名(&F)"); namedLineEdit = new QLineEdit(); namedLabel.setBuddy(namedLineEdit); lookInLabel = new QLabel("指定目录(&D)"); lookInLineEdit = new QLineEditDropList(); lookInLabel.setBuddy(lookInLineEdit); includeSubdirectoriesCheckBox = new QCheckBox("包含子目录"); labels = new QStringList(); labels.append("文件名字"); labels.append("是否修改"); labels.append("大小(字节)"); labels.append("所在位置"); tableWidget = new QTableWidget(); tableWidget.setColumnCount(4); tableWidget.setHorizontalHeaderLabels(labels); // 设置表格的列自适应策略 QHeaderView header = tableWidget.horizontalHeader(); header.setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents); // 内容自适应 header.setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents); // 内容自适应 header.setSectionResizeMode(2, QHeaderView.ResizeMode.ResizeToContents); // 内容自适应 header.setStretchLastSection(true); // 最后一列拉伸填充 // 设置表格大小策略 tableWidget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding); messageLabel = new QLabel(""); messageLabel.setFrameShape(QFrame.Shape.Panel); messageLabel.setFrameShadow(QFrame.Shadow.Sunken); findButton = new QPushButton("查找(&V)"); stopButton = new QPushButton("停止(&S)"); QGridLayout leftLayout = new QGridLayout(); leftLayout.setRowStretch(3, 1); // 让表格所在行(第3行)可以拉伸 leftLayout.addWidget(namedLabel, 0, 0); leftLayout.addWidget(namedLineEdit, 0, 1); leftLayout.addWidget(lookInLabel, 1, 0); leftLayout.addWidget(lookInLineEdit, 1, 1); leftLayout.addWidget(includeSubdirectoriesCheckBox, 2, 0, 1, 2); leftLayout.addWidget(tableWidget, 3, 0, 1, 2); leftLayout.addWidget(messageLabel, 4, 0, 1, 2); QVBoxLayout rightLayout = new QVBoxLayout(); rightLayout.addWidget(findButton); rightLayout.addWidget(stopButton); rightLayout.addStretch(); QHBoxLayout mainLayout = new QHBoxLayout(); // 设置主布局拉伸因子(左侧优先拉伸) mainLayout.setStretchFactor(leftLayout, 1); mainLayout.setStretchFactor(rightLayout, 0); mainLayout.addLayout(leftLayout); mainLayout.addLayout(rightLayout); setLayout(mainLayout); setWindowTitle("搜索文件"); includeSubdirectoriesCheckBox.setChecked(true); findButton.clicked.connect(this::find); stopButton.clicked.connect(this::stop); includeSubdirectoriesCheckBox.checkStateChanged.connect(this::includeSubDirs); initDrivers(); } public void initDrivers(){ QList<QStorageInfo> drives = QStorageInfo.mountedVolumes(); for(QStorageInfo drive : drives){ String root = drive.rootPath(); if (root.length() >= 2 && root[1] == ':' && ((Character)root[0]).isLetter()){ lookInLineEdit.add(root); } } } public void find(){ findButton.setEnabled(false); stopButton.setEnabled(true); tableWidget.setRowCount(0); // 行数设为0 stopSearch = false; messageLabel.setText("正在搜索..."); new Thread(()->{ try{ searchFiles(namedLineEdit.text(), lookInLineEdit.text()); } catch(Exception e){ messageLabel.setText("搜索出错: " + e.getMessage()); } finally{ findButton.setEnabled(true); stopButton.setEnabled(false); } }).start(); } public void stop(){ findButton.setEnabled(true); stopButton.setEnabled(false); stopSearch = true; } public void includeSubDirs(){ includeSubdirectories = includeSubdirectoriesCheckBox.isChecked(); } /** * 搜索指定目录下符合文件名模式的文件,并返回这些文件所在目录的列表 * * @param fileNamePattern 文件名模式(支持通配符*和?,自动处理转义) * @param directory 搜索的起始目录 * @return 包含匹配文件所在目录的列表(去重) or 数量 */ public long searchFiles(String fileNamePattern, String directory) { Path startDir = Paths.get(directory); final Path finalStartDir = Paths.get(directory).toAbsolutePath().normalize(); String regex = convertWildcardToRegex(fileNamePattern); Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE); //Set<Path> directories = new LinkedHashSet<>(); Files.walkFileTree(startDir, new SimpleFileVisitor<Path>() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { if (stopSearch){ messageLabel.setText("搜索停止"); return FileVisitResult.TERMINATE; } // 统一转换为标准路径后再比较 Path normalizedDir = dir.toAbsolutePath().normalize(); if (!includeSubdirectories && !normalizedDir.equals(finalStartDir)) { return FileVisitResult.SKIP_SUBTREE; // 阻止进入子目录 } return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { if (stopSearch) { messageLabel.setText("搜索停止"); return FileVisitResult.TERMINATE; } if (pattern.matcher(file.getFileName().toString()).matches()) { //directories.add(file.getParent()); int row = tableWidget.rowCount(); tableWidget.insertRow(row); tableWidget.setItem(row, 0, new QTableWidgetItem(file.getFileName().toString())); tableWidget.setItem(row, 1, new QTableWidgetItem(isFDModified(file.toString()).toString())); tableWidget.setItem(row, 2, new QTableWidgetItem(getFDSize(file.toString()).toString())); tableWidget.setItem(row, 3, new QTableWidgetItem(file.getParent().toString())); } return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(Path file, IOException e) { if (e instanceof AccessDeniedException) { System.err.println("访问被拒绝: " + file); return FileVisitResult.SKIP_SUBTREE; // 跳过无权限目录 } return FileVisitResult.CONTINUE; } }); /* return directories.stream() .map(Path::toString) .collect(Collectors.toList()); */ if (!stopSearch) messageLabel.setText("找到%1个文件".arg(tableWidget.rowCount())); return tableWidget.rowCount(); } /** * 将通配符模式转换为正则表达式 * * @param pattern 包含通配符的文件名模式 * @return 对应的正则表达式 */ private String convertWildcardToRegex(String pattern) { StringBuilder regex = new StringBuilder(); for (char c : pattern.toCharArray()) { switch (c) { case '*': regex.append(".*"); break; case '?': regex.append("."); break; case '.': case '^': case '$': case '+': case '|': case '(': case ')': case '[': case ']': case '{': case '}': case '\\': regex.append('\\').append(c); break; default: regex.append(c); } } return "^" + regex + "$"; // 完全匹配文件名 } // 获取目录或者指定文件大小(字节) public Long getFDSize(String path) { QFileInfo fileInfo = new QFileInfo(path); // 如果是文件,直接返回文件大小 if (fileInfo.isFile()) { return fileInfo.size(); } long totalSize = 0; QDir dir = new QDir(path); dir.setFilter(QDir.Filter.Dirs, QDir.Filter.Files, QDir.Filter.NoDotAndDotDot, QDir.Filter.Hidden, QDir.Filter.NoSymLinks); QList<QFileInfo> entries = dir.entryInfoList(); for (QFileInfo entry : entries) { if (entry.isDir()) { totalSize += getFDSize(entry.absoluteFilePath()); } else { totalSize += entry.size(); } } return totalSize; } // 检查目录或者指定文件是否被修改(与上次记录时间比较) public Boolean isFDModified(String path) { QFileInfo info = new QFileInfo(path); if (!info.exists()) return false; if (info.isFile()) { return info.lastModified() > lastCheck; } if (info.isDir()) { if (info.lastModified() > lastCheck) { return true; } // 递归检查子项 QDir dir = new QDir(path); dir.setFilter(QDir.Filter.Dirs, QDir.Filter.Files, QDir.Filter.NoDotAndDotDot, QDir.Filter.Hidden, QDir.Filter.NoSymLinks); QList<QFileInfo> entries = dir.entryInfoList(); for (QFileInfo entry : entries) { if (entry.lastModified() > lastCheck) { return true; } if (entry.isDir() && isFDModified(entry.absoluteFilePath())) { return true; } } } return false; } }
调用函数
package one; import jqt.Jqt; import jqt.QFindFileDialog; import java.util.List; public class Main{ public static void main(String[] args){ Jqt qt = new Jqt(args, null); QFindFileDialog fd = new QFindFileDialog(); fd.show(); qt.run(); } }
测试结果

浙公网安备 33010602011771号