Программа отображает имена файлов в JTextArea, поскольку она обходит дерево каталогов, но я не знаю, как остановить это с помощью нажатия клавиши
Есть два окна: графический интерфейс для пользовательского ввода и окно вывода для списка найденных имен файлов. Выполнение должно быть остановлено пользователем посредством нажатия клавиши и должно оставлять оба окна открытыми, потому что программа обрабатывает подкаталоги, поэтому она может работать долго, возможно, шагая до 100_000 файлов, либо производя тонны вывода, либо вообще ничего, в зависимости от имени файла пользователя шаблон соответствует файлам, найденным в выбранном начальном узле.
Вот мой вопрос:
Как мне искать нажатие клавиш (например, ESC или CTRL-C), чтобы позволить пользователю завершить работу? (Нажатие на красную букву X не является вариантом, поскольку при этом закрываются окна; пользователю необходимо увидеть, что было найдено до завершения. В любом случае это не закрывает ни одно из окон, поскольку все кнопки отключены после начала обхода дерева.)
Я попытался поместить keyListeners в нескольких местах, но после нажатия кнопки "Пуск" все компоненты Swing отключаются.
Кажется, это такая распространенная ситуация, что я удивлен, что не могу найти учебник, ветку или информацию Google, которая прямо отвечает на вопрос. Поэтому я боюсь, что это будет не так просто. Это было бы не удивительно. Возможно, я нашел здесь подсказку, но не могу ее скомпилировать, и содержащаяся там ссылка не приводит к фрагменту кода.
Поиск начинается при нажатии кнопки "Поиск":
private void jbSearchActionPerformed(ActionEvent evt) {
SearchyGUI.doIt();
}
doIt()
метод идет по дереву каталогов по расширению SimplefileVisitor
:
public class OverriddenFileVisitor extends SimpleFileVisitor<Path> {
...
}
public static void doIt(){
try {
visitor = new OverriddenFileVisitor();
info.setVisible(true);
Files.walkFileTree(SearchyGUI.p , visitor);
}
catch (Exception e) { }
}
}
Вывод записывается в jTextArea1
через report()
метод:
public static void report(String s){
Output.jTextArea1.append(s + "\n");
}
Это делается в основном в visitFile()
метод SimpleFileVisitor
:
public FileVisitResult visitFile(Path f, BasicFileAttributes a) throws IOException {
report(foundkt + "--" + f.getFileName().toString());
return FileVisitResult.CONTINUE;
}
Вот основной класс:
public class SearchyGUI {
static Output info;
static Path p ;
static FileVisitor visitor ;
static GUI gui
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
gui = new GUI();
gui.setVisible(true);
}
});
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
info = new Output();
}
});
}
4 ответа
Проблема в том, что вы перегружаете поток GUI, поэтому поток GUI не может обрабатывать события, происходящие от пользователя.
Вам нужно создать new Thread
и делать работу там. Затем, чтобы отобразить вывод из этого потока, вы можете использовать SwingUtilities.invokeLater
или что-то типа того.
API связывания клавиш, вероятно, является лучшим выбором для отслеживания нажатий клавиш.
Я бы также добавил кнопку [Отмена] в пользовательский интерфейс, который выполнял те же действия...
public class CancelAction extends AbstractAction {
public CancelAction() {
putValue(NAME, "Cancel");
}
public void actionPerformed(ActionEvent evt) {
// Perform the cancel operation...
}
}
Тогда кое-где еще в вашем коде...
CancelAction cancelAction = new CancelAction();
JButton cancelButton = new JButton(cancelAction);
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "Cancel");
am.put("Cancel", am);
Теперь другая проблема, с которой вы столкнетесь, заключается в том, что вы выглядите так, будто выполняете долгосрочную задачу в контексте потока диспетчеризации событий. Это будет препятствовать тому, чтобы ваша программа была в состоянии обновить пользовательский интерфейс или позволить пользователю взаимодействовать с пользовательским интерфейсом.
Если вам нужно внести изменения в пользовательский интерфейс (например, показать результаты обработки файла), вы должны попробовать SwingWorker
,
Основная причина в том, что он позволяет вам выполнять долгосрочную задачу в другом потоке, но предоставляет механизм для повторной синхронизации обновлений обратно в EDT, где безопасно вносить изменения в пользовательский интерфейс.
Взгляните на Concurrency in Swing для более подробной информации.
Независимо от того, в каком направлении вы идете, вам нужно будет предоставить ссылку на объект, который выполняет задачу, и предоставить какой-то флаг "отмена", который должен отслеживать объект "задача".
То, как я покинул эту программу прошлой ночью, было неудовлетворительным, так как выход привел к тому, что пользователь не смог увидеть отображенные результаты (это может быть полезно). Поэтому я установил оконные слушатели и использовал событие close для установки логического значения aborted
Значение true, чтобы предотвратить дальнейший вывод в окно, но поток продолжал работать, что приводило к периодическим проблемам, если другой поиск был запущен до завершения потока.
Вот как я это исправил.
FileVisitor
Интерфейс имеет 4 способа реализации для обхода дерева - два для каждого посещаемого файла, два для каждого каталога. Каждый возвращает FileVisitResult
что обычно FileVisitResult.CONTINUE
, Изменяя возвращаемое значение на FileVisitResult.TERMINATE
в потоке посетителя файла, он заканчивается соответствующим образом! То есть я установил флаг, чтобы поток мог проверять и предпринимать соответствующие действия, что и было предложено @MadProgrammer.
public static FileVisitResult disposition = FileVisitResult.CONTINUE;
...
private static void report(String s){
if (! aborted)
try{
Output.jTextArea1.append(s + "\n");
}
catch (Exception e){
aborted = true ;
disposition = FileVisitResult.TERMINATE;
}
}
...
@Override
public FileVisitResult visitFile(Path f, BasicFileAttributes a) throws IOException {
f1 = new File(f.getParent().toString() + "\\" + f.getFileName().toString());
long filesize = f1.length();
report(f.getFileName().toString() + "\t found in " + f.getParent().toString());
return disposition;
}
Я один счастливый турист! Спасибо ОБА за ваши идеи и вклад.
Ну, я остановил это. Я предполагаю, что если вы будете бродить по лесу достаточно долго, вы найдете гнома. Я прочитал подсказку Робина на прошлой неделе и как бы сдался. Затем я читаю еще и еще. А потом еще. Но Робин заверил меня, что гномы действительно существуют в этих лесах!
Код, который я использовал, был модификацией кода, который я нашел для приложения MatLab/Java. (Почему я даже посмотрел на это? Лучшая очевидная подсказка Google.)
Я сделал "посетитель файла" (компонент обходчика дерева каталогов) запускаемым как поток, как посоветовал Робин:
public class OverriddenFileVisitor extends SimpleFileVisitor<Path> implements Runnable{
// ................................................................^^^^^^^^^^^^^^^^^^^
В doIt()
Я внес пару изменений, переместив строки, обрабатывающие каталог, в теперь работающий класс и запустил посетитель файла как свой собственный поток в doIt()
:
public static void doIt(){
try {
new OverriddenFileVisitor().startTh();
//^^^^^^^^^^
//(moved) Files.walkFileTree(SearchyGUI.p , visitor);
...
Я добавил новый метод в предыдущей строке в класс OverriddenFileVisitor: (Это основная часть кода MatLab/Java, которая имела смысл для меня, поэтому я использовал и изменил его.)
public void startTh() {
Thread t = new Thread(this);
t.start();
}
И я вставил переопределенный run()
метод для класса:
public void run() {
try {
Files.walkFileTree(SearchyGUI.p , this); // Used to be in doIt().
}
catch (IOException ex) { }
}
Он запускался и давал правильные результаты и останавливался, когда я нажимал кнопку "Выход", которая "стала" включенной после пересмотра посетителя файла для запуска в своем собственном потоке, о чем говорил @Robin Green. Я почти чувствую, что знаю, что я сделал.
PS Обратите внимание, что я уже смог получить свой вывод через invokeLater()
- Несколько строк оригинального вопроса.
Это не закончено, но это намного более удовлетворительно.