# toString() Methode eines Interfaces überladen.



## sylo (12. Mrz 2010)

Hi zusammen

ich versuche mal mein Problem zu erklären. Wahrscheinlich denk ich da etwas verkehrt. Aber ihr könnt mir sicher weiterhelfen.

Folgendes Szenario:

Ich versuche einen TreeTable zu erzeugen. Mit dem DOM parse ich eine XML Datei und versuche die Tags dann im Baum darzustellen. Das funktioniert auch soweit. 

Was ich nun möchte ist aber dass die Baumelemente anders dargestellt werden. Das heißt normalerweise mache ich sowas wie node.toString() und er liefert mir den Namen des Nodes. Aber dieser liefert mir z.B. "[Baum: null]. Ich möchte aber, dass da nur Baum steht.

Nun wollte ich to toString() Methode vom Interface Node überladen. Die Frage ist kann man das überhaupt. Node ist wie gesagt ein Interface Node (Java 2 Platform SE v1.4.2) und dieser benutzt, so wie ich das verstehe, die toString() Methode von Object.
Aber irgendwie komm ich an den nicht ran.

Ich hoffe dass mir da jemand weiterhelfen kann.

Grüße 
sylo


----------



## Sonecc (12. Mrz 2010)

Du kannst bei Interfaces keine Methoden überschreiben, nur implementieren.
Stattdessen würde ich eine Methode schreiben, die aus den Daten der Node entsprechende Werte entnimmt und diese als String zurückliefert, so dass du das hast, was möchtest

Ich sollte noch dazu sagen, dass Interfaces an sich keine direkte toString methode besitzen, da diese nur von den Implementierungen bereitgestellt wird.


----------



## Marco13 (12. Mrz 2010)

Daraus Strings zu machen ist evtl. nicht so sinnvoll. Wenn man dann z.B. in diese TreeTable klickt, und fragt, welches Element selektiert ist, kriegt man einen String. Eigentlich will man aber den Knoten, der da liegen sollte. 
Man kann das vermutlich mit einem TreeCellRenderer oder TableCellRenderer lösen. Dann kann man da die Node-Objekte reinlegen, und als Inhalt den String anzeigen, den man will. Sogar in grün.


----------



## Michael... (12. Mrz 2010)

Node definiert keine Methode toString().
Prinzipiell kann man von jedem Objekt die toString() überladen, um die Textdarstellung zu manipulieren.

Ich kenne die Klasse TreeTable nicht, aber vielleicht/wahrscheinlich verwendet die Klasse zur Darstellung der Elemente Renderer (wie z.B. JTree und JTable)
Wenn ja sollte man eben so einen Renderer schreiben um das Aussehen eines solchen Nodes zu definieren.


----------



## sylo (13. Mrz 2010)

Genau da habt ihr recht. Es gibt den TreeCellRenderer der für die Darstellung der Zellen verantwortlich ist. Ich hab auch schon einen eigenen geschrieben. Aber wie kann ich von hier aus das String vom Node ändern? Ich bekomme es ja in der Form in der ich es nicht haben möchte.


----------



## Marco13 (13. Mrz 2010)

Dem Renderer wird normalerweise das Object übergeben, das in dieser Cell liegt. In diesem Fall ist das vermutlich der TreeNode, man sollte also z.B. sowas machen können wie

```
public Component getCellRendererComponent(.... Object object ...)
{
    super.getCellRendererComponent(....);
    DefaultMutableTreeNode node = (DefaultMutableTreeNode)object;
    Node xmlNode = (Node)node.getUserObject();
    String text = createSomeStringFrom(xmlNode); // Wie man will
    setText(text);
    return this;
}
```
(ganz grob, nur zur Vedeutlichung...)

Wenn's nicht klappt, poste mal den Code des Renderers, oder am besten ein KSKB...


----------



## sylo (13. Mrz 2010)

Probier ich mal aus. Danke schon mal.

Was ist den KSKB?


----------



## Marco13 (13. Mrz 2010)

Das, was erscheint, wenn man mit der Maus über "KSKB" fährt


----------



## Sonecc (13. Mrz 2010)

Marco13 hat gesagt.:


> Das, was erscheint, wenn man mit der Maus über "KSKB" fährt



Das klappt nicht bei jedem Browser.

KSKB heißt Kurzes Selbständig kompilierbares Beispiel


----------



## sylo (15. Mrz 2010)

Hi 
hab nun meinen eigenen CellRenderer geschrieben. Der sieht so aus:


```
public class CellRenderer extends DefaultTableCellRenderer{

	public Component getTableCellRendererComponent(JTable table, Object value,
			boolean isSelected, boolean hasFocus, int row, int column) {
		
		super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);

		return this;
	}
```
Erst mal soll der nicht viel machen. Der ruft nur die Methode vom DefaultTableCellRenderer auf. Jetzt geht es auch dass die Namen in der TreeTable richtig angezeigt werden.
Aber die "Pluse" (ich weiß nicht wie man die Dinger richtig nennt  ), um den Baum zu expandieren fehlen nun. Woran könnte das denn liegen?

Grüße
sylo


----------



## sylo (15. Mrz 2010)

Hier mal etwas Code:


```
private void openFile(File myXMLFile){
	    try{
	      Parser parser = new Parser();
	      
	      TreeTableModelExample treeTableModel = parser.runExample(myXMLFile);
	      JTreeTable treeTable = new JTreeTable(treeTableModel);
	       
	      CellRenderer renderer = new CellRenderer();
	   
 
//	      treeTable.setDefaultRenderer(coulmnClass1, renderer);
	      treeTable.setDefaultRenderer(coulmnClass2, renderer);
	         
	      jScrollPaneTree = new JScrollPane(treeTable);
	      jSplitPaneMain.setLeftComponent(jScrollPaneTree);
	    }
	    catch(RuntimeException e){
	      e.printStackTrace();
	    }
	  }
```
Hier wird alles erstellt und aufgerufen.

Der CellRenderer

```
public class CellRenderer extends JTree implements TableCellRenderer{
	
	JLabel jLabel = new JLabel();
	public Component getTableCellRendererComponent(JTable table, Object value,
			boolean isSelected, boolean hasFocus, int row, int column) {
			
		jLabel.setText(value.toString());
		return jLabel;
	}
}
```

Mein TreeTableModel:

```
public class TreeTableModelExample extends AbstractTreeTableModel implements TreeTableModel{	
	private Node root;
	private int row;
	private String[] columNames;
	private String[] methodNames;
	private String[] setterMethodNames;
	private Class[] cTypes;
	
	public TreeTableModelExample(Node root, int row, String[] columnNames,String[] methodNames,String[] setterMethodNames, Class[] cTypes){
		super(root);
		this.root = root;
		this.row = row;
		this.columNames = columnNames;
		this.methodNames = methodNames;
		this.setterMethodNames = setterMethodNames;
		this.cTypes = cTypes;
	}
	
	 /**
     * TreeModel method to return the number of children of a particular
     * node. Since <code>node</code> is a TreeNode, this can be answered
     * via the TreeNode method <code>getChildCount</code>.
     */
	public int getChildCount(Object parent) {
		
		Node node = (Node)parent; 
        NamedNodeMap attrs = node.getAttributes(); 
        int attrCount = attrs!=null ? attrs.getLength() : 0; 
        NodeList children = node.getChildNodes(); 
        int childCount = children!=null ? children.getLength() : 0; 
        return (attrCount + childCount)/2;
    
	}
	
	/**
     * TreeModel method to locate a particular child of the specified
     * node. Since <code>node</code> is a TreeNode, this can be answered
     * via the TreeNode method <code>getChild</code>.
     */
	public Object getChild(Object parent, int index) {
		
		Node node = (Node)parent; 
        NamedNodeMap attrs = node.getAttributes(); 
        int attrCount = attrs!=null ? attrs.getLength() : 0; 
        if(index<attrCount) 
            return attrs.item(index); 
        NodeList children = node.getChildNodes(); 
        
        return children.item((index - attrCount)*2+1); 	
		
	}
	
	/**
     * TreeModel method to determine if a node is a leaf. 
     * Since <code>node</code> is a TreeNode, this can be answered
     * via the TreeNode method <code>isLeaf</code>.
     */
	public boolean isLeaf(Object node) {
		return getChildCount(node)==0; 
	}
	
	
	//
    //  The TreeTable interface. 
    //
    /**
     * Returns the number of column names passed into the constructor.
     */
	public int getColumnCount() {
		return row;
	}
	
	/**
     * Returns the column name passed into the constructor.
     */
	public String getColumnName(int column) {
		return columNames[column];
	}
	
	/**
     * Returns the column class for column <code>column</code>. This
     * is set in the constructor.
     */
	public Class getColumnClass(int column) {
			if (cTypes == null || column < 0 || column >= cTypes.length) {
			    return null;
			}
			return cTypes[column];
	}
	
	/**
     * Returns the value for the column <code>column</code> and object
     * <code>node</code>. The return value is determined by invoking
     * the method specified in constructor for the passed in column.
     */
	public Object getValueAt(Object node, int column) {

	Node myNode=(Node) node;
		   try {
		     switch (column) {
	       case 0:
		         return myNode.getNodeName();
		       case 1:
		         return myNode.getNodeValue();
		       case 2:
		         return myNode.getParentNode();
		      }
		   }
		   catch (SecurityException se) {}
		   return null;
		 }

	/**
     * Returns true if there is a setter method name for column
     * <code>column</code>. This is set in the constructor.
     */
    public boolean isCellEditable(Object node, int column) { 
    	return ((setterMethodNames != null && setterMethodNames[column] != null));
    	
    }
	
	/**
     * Sets the value to <code>aValue</code> for the object
     * <code>node</code> in column <code>column</code>. This is done
     * by using the setter method name, and coercing the passed in
     * value to the specified type.
     */
    // Note: This looks up the methods each time! This is rather inefficient;
    // it should really be changed to cache matching methods/constructors
    // based on <code>node</code>'s class, and <code>aValue</code>'s class.
    public void setValueAt(Object aValue, Object node, int column) {
    	if(column == 0){
    		return;
    	}
	boolean found = false;
	try {
	    // We have to search through all the methods since the
	    // types may not match up.
	    Method[] methods = node.getClass().getMethods();

	    for (int counter = methods.length - 1; counter >= 0; counter--) {
		if (methods[counter].getName().equals
		       (setterMethodNames[column]) && methods[counter].
		       getParameterTypes() != null && methods[counter].
		       getParameterTypes().length == 1) {
		    // We found a matching method
		    Class param = methods[counter].getParameterTypes()[0];
		    if (!param.isInstance(aValue)) {
			// Yes, we can use the value passed in directly,
			// no coercision is necessary!
			if (aValue instanceof String &&
			    ((String)aValue).length() == 0) {
			    // Assume an empty string is null, this is
			    // probably bogus for here.
			    aValue = null;
			}
			else {
			    // Have to attempt some sort of coercision.
			    // See if the expected parameter type has
			    // a constructor that takes a String.
			    Constructor cs = param.getConstructor
			                 (new Class[] { String.class });
			    if (cs != null) {
				aValue = cs.newInstance(new Object[]
							{ aValue });
			    }
			    else {
				aValue = null;
			    }
			}
		    }
		    // null either means it was an empty string, or there
		    // was no translation. Could potentially deal with these
		    // differently.
		    methods[counter].invoke(node, new Object[] { aValue });
		    found = true;
		    break;
		}
	    }
	} catch (Throwable th) {
	    System.out.println("exception: " + th);
	}
	if (found) {
	    // The value changed, fire an event to notify listeners.
	    TreeNode parent = ((TreeNode)node).getParent();
	    fireTreeNodesChanged(this, getPathToRoot(parent),
				 new int[] { getIndexOfChild(parent, node) },
				 new Object[] { node });
	}
    }
    
    /**
     * Builds the parents of the node up to and including the root node,
     * where the original node is the last element in the returned array.
     * The length of the returned array gives the node's depth in the
     * tree.
     * 
     * @param aNode the TreeNode to get the path for
     * @param an array of TreeNodes giving the path from the root to the
     *        specified node. 
     */
    public TreeNode[] getPathToRoot(TreeNode aNode) {
        return getPathToRoot(aNode, 0);
    }
    
    /**
     * Builds the parents of the node up to and including the root node,
     * where the original node is the last element in the returned array.
     * The length of the returned array gives the node's depth in the
     * tree.
     * 
     * @param aNode  the TreeNode to get the path for
     * @param depth  an int giving the number of steps already taken towards
     *        the root (on recursive calls), used to size the returned array
     * @return an array of TreeNodes giving the path from the root to the
     *         specified node 
     */
    private TreeNode[] getPathToRoot(TreeNode aNode, int depth) {
        TreeNode[]              retNodes;
	// This method recurses, traversing towards the root in order
	// size the array. On the way back, it fills in the nodes,
	// starting from the root and working back to the original node.

        /* Check for null, in case someone passed in a null node, or
           they passed in an element that isn't rooted at root. */
        if(aNode == null) {
            if(depth == 0)
                return null;
            else
                retNodes = new TreeNode[depth];
        }
        else {
            depth++;
            if(aNode == root)
                retNodes = new TreeNode[depth];
            else
                retNodes = getPathToRoot(aNode.getParent(), depth);
            retNodes[retNodes.length - depth] = aNode;
        }
        return retNodes;
    }
}
```

Alle anderen Klassen, Interfaces usw. die benötigt werden, habe ich von der Demo auf der Sun-Seite übernommen.


----------



## SlaterB (15. Mrz 2010)

ob Plus oder nicht müsste in erste Linie von getChildCount() abhängen,
baue dort doch System.out.println-Logging ein, welcher Node hat wieviele Kinder?


----------



## sylo (15. Mrz 2010)

Sorry hatte mich falsch ausgedrückt.

Die Pluse werden nur nicht angezeigt. getChildCount liefert schon das Richtige, aber es wird sozusagen kein Baum angezeigt. Wenn ich auf einen Knoten mit der Maus klicke, dann erscheinen mehr Spalten, aber diese BaumStruktur ist nicht zu sehen.

Im Bild sieht man, dass Vorname und Nachname unter dem Knoten Name sind. Wenn ich auf das N von Name klicke, erscheinen bzw. verschwinden die zwei Einträge. Also der Baum ist schon da, wird aber nicht angezeigt


----------



## SlaterB (15. Mrz 2010)

hmm. ein TreeTable, kenne ich gar nicht,
wird schon seinen Sinn haben warum der Renderer von JTree erbt und dann this zurückgibt als CellRendererComponent,
du gibts ein JLabel zurück, sicher ohne Icon

warum machst du das so? steht vielleicht schon im Thread, ich versuche noch bisschen mehr zu lesen,
siehe auch 
Creating TreeTables in Swing
falls noch nicht bekannt,
was genau reicht davon nicht, wo musst du warum abweichen?


----------



## sylo (15. Mrz 2010)

Also eigentlich wollte ich garkeinen CellRenderer schreiben. ich habe da nur rumprobiert weil der DefaultRenderer z.B nicht "Name" sondern "[Name: null]" zurückgegeben hat. 
Aber das mit dem Label sehe ich ein. Wenn ich ein Label zurückgebe dann überlagert er ja den Baum. Wenn ich this zutückgebe, wird jeder Knoten mit dem Wert "JTree" gefüllt. 
Naja ich probier mal noch ein wenig. Falls jemand einen Tipp hat, bitt melden.


----------



## SlaterB (15. Mrz 2010)

ich denke du solltest an der Stelle getValueAt() im TreeTableModel eingreifen,

return myNode.getNodeValue();
usw.,
per System.out.println() anschauen was da zurückgegebenen wird und gegebenenfalls beeinflussen


----------



## sylo (15. Mrz 2010)

in der Methode getValueAt() werden, wie gewollt, einfach nur die Bezeichnungen ohne die nachfolgende ":null" ausgegeben. 
Mir ist aber aufgefallen dass wenn ich in der Methode getValueAt() nicht in der Spalte 0 sondern in der Spalte 1 die Bezeichnung, also myNode.getName(), ausgebe, gibt er mir die Richtig aus. 
Mir kommt es so vor als würde die Spalte 0 irgendwo anderster initialisiert werden. Sie ändert sich nicht wenn ich für die Spalte 0 z.b myNode.getValue() oder myNode.getParent() mache.


----------



## sylo (16. Mrz 2010)

Hi zusammen

Hab das Problem gelöst in dem ich über den eigenen CellRenderer gegangen bin. 
Mein Fehler war das ich den DefaultTreeTableRender der TreeTable mit meinem eigenen CellRenderer ersetzt habe. Um nur die Bezeichungen im Baum, und nicht z.B. die Pluse, zu ändern muss man den DefaultTreeRenderer des Jtrees ändern. Im CellRenderer dann mit this.setText() die Bezeichnung ändern.

Darauf muss man aber auch erst mal kommen 

Danke trotzdem für eure Hilfe

Grüße
sylo


----------

