Swing question / JTree / custom tree model
Asked Answered
V

4

5

I'm having a problem and hope, someone knows what's going wrong and why and is able to give me the explanation of what I'm missing out right now to make that thing work as suggested.

I have a JTree which is build upon a custom TreeModel ("WRTreeModel", see below). The data structure this model shall be used for is build of an root object which contains some fields and furthermore a list which is backed by the "ArrayListModel" shown below. The tree looks fine when I build it using the WRTreeModel. I'm able to expand and collapse the nodes which represent the lists and fields contained in the objects. I can expand and collapse these lists and see their contents as well and so on.

Now I want to remove a child of one of the lists and - as I already know - do it by removing it from the model calling the remove method of the ArrayListModel. To make the WRTreeModel aware of that remove, the first thing is to call its fireIntervalRemoved method is called, so far so good.

In the WRTreeModels inner class ArrayModelListener the intervalRemoved method prepares the call of fireTreeNodesRemoved which then builds a TreeEvent which is forwarded to all registered TreeModelListeners (and therefore the JTree which registers itself automaticall when it's connected to the model).

Now I would expect that the tree reflects the change and updates it's internal and visual representation to show the new state. Unfortunately this doesn't seem to work that way. Something happens. But when I click on the node I just have changed some EventHandler-Exceptions are thrown. Obviously something got really confused.

I know it's not easy to answer such a question on the fly but I would really appreciate a fast answer. It would also be of help, if someone knew websites explaining the use of custom tree models (not on DefaultMutableTreeNode or any given implementation based class) and how the event handling and updating of the JTree works.

With best regards,

Thomas Arts


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);
      }
      }
    }

...

}
Vasques answered 10/12, 2009 at 13:42 Comment(2)
Don't know if it's of further help but when I try to collapse and then expand the parent node of the removed node I get the following exception: Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException at javax.swing.plaf.basic.BasicTreeUI.ensureRowsAreVisible(BasicTreeUI.java:1881) at javax.swing.plaf.basic.BasicTreeUI.toggleExpandState(BasicTreeUI.java:2208) at javax.swing.plaf.basic.BasicTreeUI.handleExpandControlClick(BasicTreeUI.java:2191) at javax.swing.plaf.basic.BasicTreeUI.checkForClickInExpandControl(BasicTreeUI.java:2149) at ... and so onVasques
All problems are urgent by definition :-) I would not mention "time pressure" in a question. And your question is meant to stay and help others when you have long forgotten about it. Makes it more canonical.Toothlike
H
2

You need to perform the node delete and the subsequent TreeModelListener.treeNodesRemoved event firing on the Event Dispatch Thread.

To do this use:

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

Doing this prevents Swing using the EDT to update the tree in the middle of your delete and the event firing tells the JTree control (which has added listeners) that the model has changed.

Homerhomere answered 10/12, 2009 at 17:2 Comment(1)
That and in addition a Tree.updateUI() with a following expandToPath(path) with the path to the removed elements parent does exactly the job I wanted. Thanks a lot for that hint! BTW: Seems using SwingUtilities.invokeLater is so common that no one ever thinks others forget to put their event and UI stuff inside.Vasques
N
0

Because we're in a hurry I haven't looked at your code yet. Your description sounds as if you're doing everything correctly and have thought of what's needed.

My guess at something you may not have considered: Is this tree model change happening in the Event Dispatch Thread (alias Swing worker thread)? If the change comes from another thread then chances are it won't be properly processed.

Just a stab in the dark, of course.

Novotny answered 10/12, 2009 at 13:54 Comment(3)
I think it is happening in the right thread. When I debug having a breakpoint in the fireTreeNodesRemoved method it halts in in the AWT-EventQueue thread. which seems to be right.Vasques
You're right. See above answer which suggested to that, too and in fact it works like a charm :-)Vasques
Excellent! I'm sorry I was in a hurry too (had to bail for a meeting) and didn't have a chance to recommend the fix.Novotny
B
0

Looks like you have an off-by-one bug in intervalRemoved.

You aren't initializing the last value in indices array. It will autoinitialized to 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());
}

Try instead "i <= e.getIndex1()":

for (int i = e.getIndex0(); i <= e.getIndex1(); i++) {
    indices[i - e.getIndex0()] = i;
}
Bowens answered 10/12, 2009 at 14:31 Comment(1)
You're absolutely right with that. Same is stated in the at the method description. I fixed that though it had no influence on my problem since the list element I try to remove is the first and therefore index 0 is fine.Vasques
D
0

The method name is fireIntervalRemoved, so try calling it after removing:

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

It's the way I've done, and always worked (maybe added some checking).
(sorry if I missed something, did'nt get time to analise/test your code...)

Dagnah answered 10/12, 2009 at 14:53 Comment(2)
Don't I need to know the removed element for building my TreeEvent? If I call fireIntervalRemoved(index,index) after removing the element then I can't get it anymore. That's why I moved the call up before the real remove from the list.Vasques
not sure, haven't checked all the code and can't find fireIntervalRemoved. The TreeModelEvent does not need the element, as I know... I think if you fire the event and the GUI get's updated (fast enough) before you remove the element, the tree will NOT reflect the remove (since it has not yet happened). Maybe you may construct the event before removing the element and fire the event after removing... ((Gruss aus Stuttgart))Dagnah

© 2022 - 2024 — McMap. All rights reserved.