Swing question / JTree / пользовательская модель дерева

У меня проблема, и я надеюсь, что кто-то знает, что происходит не так и почему, и может дать мне объяснение того, что я упускаю прямо сейчас, чтобы заставить эту вещь работать, как было предложено.

У меня есть JTree, построенный на пользовательской TreeModel ("WRTreeModel", см. ниже). Структура данных, для которой должна использоваться эта модель, состоит из корневого объекта, который содержит несколько полей и, кроме того, список, поддерживаемый «ArrayListModel», показанным ниже. Дерево выглядит нормально, когда я строю его с помощью WRTreeModel. Я могу разворачивать и сворачивать узлы, представляющие списки и поля, содержащиеся в объектах. Я могу разворачивать и сворачивать эти списки, а также просматривать их содержимое и так далее.

Теперь я хочу удалить дочерний элемент одного из списков и, как я уже знаю, сделать это, удалив его из модели, вызвав метод удаления модели ArrayListModel. Чтобы сообщить WRTreeModel об этом удалении, первым делом нужно вызвать его метод fireIntervalRemoved, пока все хорошо.

Во внутреннем классе WRTreeModels ArrayModelListener метод intervalRemoved подготавливает вызов fireTreeNodesRemoved, который затем создает TreeEvent, который пересылается всем зарегистрированным TreeModelListeners (и, следовательно, JTree, который автоматически регистрируется при подключении к модели).

Теперь я ожидаю, что дерево отразит изменение и обновит свое внутреннее и визуальное представление, чтобы показать новое состояние. К сожалению, похоже, это так не работает. Что-то происходит. Но когда я нажимаю на узел, я только что изменил некоторые исключения EventHandler-Exceptions. Явно что-то сильно запуталось.

Я знаю, что нелегко ответить на такой вопрос на лету, но я был бы очень признателен за быстрый ответ. Также было бы полезно, если бы кто-то знал веб-сайты, объясняющие использование пользовательских моделей дерева (не в DefaultMutableTreeNode или любом данном классе, основанном на реализации) и как работает обработка событий и обновление JTree.

С наилучшими пожеланиями,

Томас Артс


public class ArrayListModel<E> extends ArrayList<E> implements ListModel {

...

public E remove(int index) {
    fireIntervalRemoved(index, index);
    E removedElement = super.remove(index);
    return removedElement;
  }

...

}

public class WRTreeModel extends LogAndMark implements TreeModel {


  class ArrayModelListener implements ListDataListener {

  ...

    @Override
    public void intervalRemoved(ListDataEvent e) {
      int[] indices = new int[e.getIndex1() - e.getIndex0() + 1];
      for (int i = e.getIndex0(); i < e.getIndex1(); i++)
        indices[i - e.getIndex0()] = i;
        fireTreeNodesRemoved(e.getSource(), getPathToRoot(e.getSource()), indices,     ((ArrayListModel<?>)e.getSource()).subList(e.getIndex0(), e.getIndex1()+1).toArray());
    }

  ...

  }

  public Object[] getPathToRoot(Object child) {
    ArrayList<Object> ret = new ArrayList<Object>();
    if (child == null)
      return ret.toArray();
    ret.add(root);
    if (child == root)
      return ret.toArray();
    int childType = 0;
    if (child instanceof List<?> && ((List) child).get(0) instanceof Einleitungsstelle) {
      childType = 1;
    }
    if (child instanceof Einleitungsstelle) {
      childType = 2;
    }
    if (child instanceof List<?> && ((List) child).get(0) instanceof Messstelle) {
      childType = 3;
    }
    if (child instanceof Messstelle) {
      childType = 4;
    }
    if (child instanceof List<?> && ((List) child).get(0) instanceof     Ueberwachungswert) {
      childType = 5;
    }
    if (child instanceof Ueberwachungswert) {
      childType = 6;
    }
    if (child instanceof List<?> && ((List) child).get(0) instanceof     Selbstueberwachungswert) {
      childType = 7;
    }
    if (child instanceof Selbstueberwachungswert) {
      childType = 8;
    }
    switch (childType) {
    // List of ESTs
    case 1: {
      ret.add(child);
      break;
    }
    // EST
    case 2: {
      List<Einleitungsstelle> listOfEST = ((Wasserrecht) (root)).getListOfEST();
      ret.add(listOfEST);
      ret.add(child);
      break;
    }
    // List of MSTs
    case 3: {
      List<Einleitungsstelle> listOfEST = ((Wasserrecht) (root)).getListOfEST();
      ret.add(listOfEST);
      // Find the EST containing the List of MSTs the child referes to
      for (Einleitungsstelle einleitungsstelle : listOfEST) {
        if (child == einleitungsstelle.getListOfMST()) {
          ret.add(einleitungsstelle);
          break;
        }
      }
      ret.add(child);
      break;
    }
    // MST
    case 4: {
       List<Einleitungsstelle> listOfEST = ((Wasserrecht) (root)).getListOfEST();
       ret.add(listOfEST);
       // Find the EST containing the List of MSTs the child referes to
       for (Einleitungsstelle einleitungsstelle : listOfEST) {
          if (child == einleitungsstelle.getListOfMST()) {
            ret.add(einleitungsstelle.getListOfMST());
            break;
          }
       }
       ret.add(child);
       break;
    }
    // List of UEWs
    case 5: {
        List<Einleitungsstelle> listOfEST = ((Wasserrecht) (root)).getListOfEST();
        ret.add(listOfEST);
        // Find the EST containing the List of MSTs the child referes to
       for (Einleitungsstelle einleitungsstelle : listOfEST) {
         ArrayListModel<Messstelle> listOfMST = einleitungsstelle.getListOfMST();
         if (child == listOfMST) {
           ret.add(listOfMST);
           for (Messstelle messstelle : listOfMST) {
             if (child == messstelle.getListOfUEW()) {
               ret.add(messstelle.getListOfUEW());
               break;
             }
           }
          break;
        }
      }
     break;
    }
    // UEW
    case 6: {
      List<Einleitungsstelle> listOfEST = ((Wasserrecht) (root)).getListOfEST();
      ret.add(listOfEST);
      // Find the EST containing the List of MSTs the child referes to
      for (Einleitungsstelle einleitungsstelle : listOfEST) {
        ArrayListModel<Messstelle> listOfMST = einleitungsstelle.getListOfMST();
        if (child == listOfMST) {
          ret.add(listOfMST);
          for (Messstelle messstelle : listOfMST) {
            if (child == messstelle.getListOfUEW()) {
              ret.add(messstelle.getListOfUEW());
              break;
            }
          }
          break;
        }
      }
      ret.add(child);
      break;
    }
    // List of SUEWs
    case 7: {
      List<Einleitungsstelle> listOfEST = ((Wasserrecht) (root)).getListOfEST();
      ret.add(listOfEST);
      // Find the EST containing the List of MSTs the child referes to
      for (Einleitungsstelle einleitungsstelle : listOfEST) {
        ArrayListModel<Messstelle> listOfMST = einleitungsstelle.getListOfMST();
        if (child == listOfMST) {
          ret.add(listOfMST);
          for (Messstelle messstelle : listOfMST) {
            if (child == messstelle.getListOfSUEW()) {
              ret.add(messstelle.getListOfSUEW());
              break;
            }
          }
          break;
        }
      }
      break;
    }
    // SUEW
    case 8: {
       List<Einleitungsstelle> listOfEST = ((Wasserrecht) (root)).getListOfEST();
       ret.add(listOfEST);
       // Find the EST containing the List of MSTs the child referes to
       for (Einleitungsstelle einleitungsstelle : listOfEST) {
          ArrayListModel<Messstelle> listOfMST = einleitungsstelle.getListOfMST();
          if (child == listOfMST) {
          ret.add(listOfMST);
          for (Messstelle messstelle : listOfMST) {
             if (child == messstelle.getListOfSUEW()) {
               ret.add(messstelle.getListOfSUEW());
               break;
             }
           }
           break;
         }
       }
       ret.add(child);
       break;
      }
      default:
      ret = null;
    }
    return ret.toArray();
    }
  }

...

    protected void fireTreeNodesRemoved(Object changed, Object path[], int     childIndecies[], Object children[]) {
      TreeModelEvent event = new TreeModelEvent(this, path, childIndecies, children);
      synchronized (listeners) {
      for (Enumeration e = listeners.elements(); e.hasMoreElements();) {
        TreeModelListener tml = (TreeModelListener) e.nextElement();
        tml.treeNodesRemoved(event);
      }
      }
    }

...

}

person Community    schedule 10.12.2009    source источник
comment
Не знаю, поможет ли это в дальнейшем, но когда я пытаюсь свернуть, а затем развернуть родительский узел удаленного узла, я получаю следующее исключение: Exception in thread AWT-EventQueue-0 java.lang.NullPointerException at javax.swing.plaf .basic.BasicTreeUI.ensureRowsAreVisible(BasicTreeUI.java:1881) в javax.swing.plaf.basic.BasicTreeUI.toggleExpandState(BasicTreeUI.java:2208) в javax.swing.plaf.basic.BasicTreeUI.handleExpandControlClick(BasicTreeUI.java:2191) ) в javax.swing.plaf.basic.BasicTreeUI.checkForClickInExpandControl(BasicTreeUI.java:2149) в ... и так далее   -  person    schedule 10.12.2009
comment
Все проблемы срочные по определению :-) Я бы не упоминал в вопросе о цейтноте. И ваш вопрос предназначен для того, чтобы остаться и помочь другим, когда вы уже давно забыли об этом. Делает его более каноничным.   -  person raoulsson    schedule 10.12.2009


Ответы (4)


Вам необходимо выполнить удаление узла и последующее срабатывание события TreeModelListener.treeNodesRemoved в потоке отправки событий.

Для этого используйте:

SwingUtilities.invokeLater(
  new Runnable() 
  {
    public void run() 
    {
      //Delete and event firing logic goes here.
      ...
    }
  }
);

Это предотвращает использование Swing EDT для обновления дерева в середине вашего удаления, а запуск события сообщает элементу управления JTree (который добавил слушателей), что модель изменилась.

person Nick Holt    schedule 10.12.2009
comment
Это и, кроме того, Tree.updateUI() со следующим expandToPath(path) с путем к удаленному родительскому элементу выполняет именно ту работу, которую я хотел. Большое спасибо за эту подсказку! Кстати: кажется, использование SwingUtilities.invokeLater настолько распространено, что никто никогда не думает, что другие забывают поместить внутрь свои события и элементы пользовательского интерфейса. - person ; 10.12.2009

Поскольку мы торопимся, я еще не просмотрел ваш код. Ваше описание звучит так, как будто вы все делаете правильно и продумали, что нужно.

Мое предположение о том, что вы, возможно, не рассмотрели: происходит ли это изменение модели дерева в потоке отправки событий (псевдоним рабочего потока Swing)? Если изменение поступает из другого потока, скорее всего, оно не будет обработано должным образом.

Просто удар в темноте, конечно.

person Carl Smotricz    schedule 10.12.2009
comment
Я думаю, что это происходит в правильном треде. Когда я отлаживаю наличие точки останова в методе fireTreeNodesRemoved, он останавливается в потоке AWT-EventQueue. что кажется правильным. - person ; 10.12.2009
comment
Ты прав. См. ответ выше, который тоже предложил это, и на самом деле это работает как шарм :-) - person ; 10.12.2009
comment
Отлично! Извините, я тоже торопился (пришлось бежать на встречу) и не успел порекомендовать исправление. - person Carl Smotricz; 10.12.2009

Похоже, у вас есть ошибка в intervalRemoved.

Вы не инициализируете последнее значение в массиве индексов. Он будет автоматически инициализирован до 0.

@Override
public void intervalRemoved(ListDataEvent e) {
  int[] indices = new int[e.getIndex1() - e.getIndex0() + 1];
  for (int i = e.getIndex0(); i < e.getIndex1(); i++)
    indices[i - e.getIndex0()] = i;
    fireTreeNodesRemoved(e.getSource(), getPathToRoot(e.getSource()), indices,     ((ArrayListModel<?>)e.getSource()).subList(e.getIndex0(), e.getIndex1()+1).toArray());
}

Вместо этого попробуйте "i ‹= e.getIndex1()":

for (int i = e.getIndex0(); i <= e.getIndex1(); i++) {
    indices[i - e.getIndex0()] = i;
}
person Alex Stoddard    schedule 10.12.2009
comment
Вы абсолютно правы в этом. То же самое указано в описании метода. Я исправил это, хотя это не повлияло на мою проблему, поскольку элемент списка, который я пытаюсь удалить, является первым, и поэтому индекс 0 в порядке. - person ; 10.12.2009

Имя метода fireIntervalRemoved, поэтому попробуйте вызвать его после удаления:

public E remove(int index) {
    E removedElement = super.remove(index);
    fireIntervalRemoved(index, index);
    return removedElement;
}

Так я делал и всегда работал (может быть, добавил некоторую проверку).
(извините, если я что-то пропустил, не было времени проанализировать/протестировать ваш код...)

person user85421    schedule 10.12.2009
comment
Разве мне не нужно знать удаленный элемент для создания моего TreeEvent? Если я вызову fireIntervalRemoved(index,index) после удаления элемента, я больше не смогу его получить. Поэтому я перенес призыв вверх перед реальным удалением из списка. - person ; 10.12.2009
comment
не уверен, не проверял весь код и не могу найти fireIntervalRemoved. Как я знаю, TreeModelEvent не нуждается в элементе... Я думаю, что если вы запустите событие и графический интерфейс обновится (достаточно быстро) до того, как вы удалите элемент, дерево НЕ будет отражать удаление (поскольку это еще не произошло ). Возможно, вы можете создать событие перед удалением элемента и запустить событие после удаления... ((Gruss aus Stuttgart)) - person user85421; 10.12.2009