Программа отображает имена файлов в 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()- Несколько строк оригинального вопроса.

Это не закончено, но это намного более удовлетворительно.

Другие вопросы по тегам