# JScrollPane scrollt nicht nach unten



## 0001001 (24. Aug 2008)

Folgendes Problem:

Ich habe eine JTable in einer JScrollPane. Mit der Anweisung

```
scrollpane.getVerticalScrollBar().setValue(scrollpane.getVerticalScrollBar().getMaximum());
```
kann man die JScrollPane anweisen, nach ganz unten zu scrollen. Das funktioniert auch in den meisten Fällen.

In folgendem Fall scrollt die JScrollPane aber nur ein wenig, aber nicht bis ganz unten.
Es muss irgendetwas mit dem CellRenderer zu tun haben, der die Aufgabe hat, in einer Zelle sowohl ein Bild als auch einen Text anzuzeigen. *Denn wenn nur Text angezeigt wird, dann scrollt die JScrollPane problemlos bis nach ganz unten.*

Das Ganze sieht dann so aus: 






Kann mir jemand sagen, wieso die JScrollPane nicht bis ganz unten scrollt?

Hab die Klasse mal hier hochgeladen:
http://timeline1.ti.funpic.de/src.zip

Hier auch nochmal der Quellcode:

*Test Klasse:*

```
public class TimeLineTest extends JFrame{
	TimeLine timeline;
	
	public TimeLineTest(){
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.setLayout(new BorderLayout());
		
		timeline = new TimeLine(5,10,true);
		JScrollPane scrollpane = new JScrollPane(timeline);
		
		this.add(scrollpane,BorderLayout.CENTER);
		
		addData();		

		this.pack();
		this.setSize(600,400);
		this.setVisible(true);

		// scroll down
		scrollpane.getVerticalScrollBar().setValue(scrollpane.getVerticalScrollBar().getMaximum());
	}
	
	public static void main(String[] args) {
		SwingUtilities.invokeLater (new Runnable(){	
			public void run ()	{
				new TimeLineTest();
			}
		});	
	}
	
	private void addData(){		
		for(int i=0;i<5;i++){
			ArrayList<Object[]> columndata = new ArrayList<Object[]>();
			int random = (int)(Math.random() * 40) + 2;
			for(int j=0;j<random;j++){				
				columndata.add(new Object[]{"Bildtext",new ImageIcon(this.getClass().getResource("image1.png"))});				
			}
			timeline.addColumn("Spalte", columndata);
		}
	}
}
```

*Modifizierte JTable:*

```
public class TimeLine extends JTable{
	private ImageTableModel itm;			// table model
	private int maxRowHeight = 55;			// maximum row height


	/**
	 * Generates a new TimeLine object
	 * @param rows	- number of rows of the default grid
	 * @param columns - number of columns of the default grid
	 * @param autoSizeRowHeight	- auto adjust row height to the max row height
	 * @param autoSizeColumnWidth - auto adjust column width
	 */
	public TimeLine(int rows, int columns, boolean autoSizeRowHeight){
		this.setTableHeader(null);
		this.setDefaultRenderer(Object.class, new MyCellRenderer(this)); 

		itm = new ImageTableModel();
		itm.initGrid(rows, columns);

		setModel(itm);	// set the table model
	}

	
	/**
	 * Adds a new column to the table
	 * @param columnName - The column name
	 * @param columndata - The row values for this column. 
	 * Object[0] contains the cell text, object[1] contains the ImageIcon
	 */
	public void addColumn(String columnName, ArrayList<Object[]> columndata){
		itm.addColumn(columnName, columndata);
	}

	
	/**
	 *	Cell renderer for this table
	 */
	class MyCellRenderer extends DefaultTableCellRenderer{
		private TimeLine theTimeLine;
		public MyCellRenderer(TimeLine timeline){
			this.theTimeLine = timeline;
			setHorizontalAlignment( CENTER );
			setVerticalTextPosition(JLabel.BOTTOM);
			setHorizontalTextPosition(JLabel.CENTER);			
		}

		public void setValue(Object value) {
			Object[] a = (Object[])value;
			if(value == null){			// empty cell
				setIcon(null);
				super.setValue(value);				
			}
			else if (a[1] instanceof Icon) {		// normal cell with text and image
				setIcon((Icon) a[1]);
				setText((String)a[0]);
				this.setAlignmentY(JLabel.BOTTOM_ALIGNMENT);
				theTimeLine.setRowHeight(maxRowHeight);
			} 
			else if(a[1] == null && a[0] instanceof String){
				setText((String)a[0]);
				setIcon(null);
			}
			else {
				setIcon(null);
				super.setValue(value);
			}
		}
	}



	/**
	 * The table model for this timeline	 *
	 */
	class ImageTableModel extends AbstractTableModel{
		private ArrayList<String> columnnames; // holds the column names
		private ArrayList<ArrayList<Object[]>> data; // holds the table data
		private int maxRowCount;	
		private int columnCursor; // points on the current column

		public ImageTableModel(){
			columnnames = new ArrayList<String>();
			data = new ArrayList<ArrayList<Object[]>>();
			maxRowCount = 0;
			columnCursor = 0;
		}

		public Object getValueAt(int row, int column){
			if (data.get(column).size()-1<row){
				return null;
			}
			else{
				return data.get(column).get(row);
			}			
		} 

		public int getRowCount(){
			return maxRowCount;
		} 

		public int getColumnCount(){
			return columnnames.size();
		}

		public String getColumnName( int columnIndex ){
			return columnnames.get(columnIndex);
		}

		/**
		 * Adds a new column to the table
		 * @param columnName - The column name
		 * @param columndata - The row values for this column.
		 */
		public void addColumn(String columnName, ArrayList<Object[]> columndata){
			if(columnCursor >= columnnames.size()){
				columnnames.add(columnName);
				data.add(rotateFillList(columnName,columndata));
			}
			else{
				columnnames.set(columnCursor, columnName);
				data.set(columnCursor, rotateFillList(columnName,columndata));
			}
			SwingUtilities.invokeLater (new Runnable(){		// fixes a nasty java vector bug
				public void run ()	{
					fireTableStructureChanged();
					fireTableDataChanged();	
				}
			});
			columnCursor++;
		}

		public void initGrid(int rows, int columns){
			for(int i=0;i<columns;i++){
				ArrayList<Object[]> newdata = new ArrayList<Object[]>();
				for(int j=0;j<rows;j++){
					newdata.add(null);
				}
				columnnames.add(String.valueOf(i));
				data.add(newdata);
				maxRowCount = rows;
			}
			SwingUtilities.invokeLater (new Runnable(){		// fixes a nasty java vector bug
				public void run ()	{
					fireTableStructureChanged();
					fireTableDataChanged();	
				}
			});
		}

		/**
		 * Rotates the list. If list.size() is smaller than
		 * maxRowCount the list if filled with null values
		 * This generates the bottom up effect
		 * @param columnName - The column name
		 * @param list
		 * @return list
		 */
		private ArrayList<Object[]> rotateFillList(String columnName, ArrayList<Object[]> list){
			list.add(0,new Object[]{columnName,null});	// set column name to be on the bottom 

			if(maxRowCount < list.size()){
				// adjust all rows to the new maxRowCount
				maxRowCount = list.size();
				for(int i=0;i<data.size();i++){
					int diff = maxRowCount - data.get(i).size();
					for(int j=0;j<diff;j++){
						data.get(i).add(0,null);
					}
				}				
			}
			else {	// fill with null values
				int diff = maxRowCount - list.size();
				for(int i=0;i<diff;i++){
					list.add(null);
				}
			}

			ArrayList<Object[]> rotatedList = new ArrayList<Object[]>();
			for(int i= list.size()-1;i>=0;i--){		// rotate list
				rotatedList.add(list.get(i));
			}

			return rotatedList;
		}
	}	
}
```


----------



## Ariol (24. Aug 2008)

```
import java.awt.BorderLayout;
import java.util.ArrayList;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;

public class TimeLineTest extends JFrame
{
	TimeLine	timeline;

	public TimeLineTest(){
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.setLayout(new BorderLayout());
		
		timeline = new TimeLine(5,10,true);
		final JScrollPane scrollpane = new JScrollPane(timeline);
		
		this.add(scrollpane,BorderLayout.CENTER);
		
		addData();		

		this.pack();
		this.setSize(600,400);
		this.setVisible(true);

		SwingUtilities.invokeLater (new Runnable(){	
			public void run ()	
			{
				scrollpane.getVerticalScrollBar().setValue(scrollpane.getVerticalScrollBar().getMaximum());
			}
		});
	}

	public static void main(String[] args)
	{

		new TimeLineTest();

	}

	private void addData()
	{
		for (int i = 0; i < 5; i++)
		{
			ArrayList<Object[]> columndata = new ArrayList<Object[]>();
			int random = (int) (Math.random() * 40) + 2;
			for (int j = 0; j < random; j++)
			{
				columndata.add(new Object[]
				{ "Bildtext", new ImageIcon(this.getClass().getResource("image1.png")) });
			}
			timeline.addColumn("Spalte", columndata);
		}
	}
}
```

Das invokeLater in der main-Methode macht meiner Ansicht nach enig Sinn...


----------



## Marco13 (24. Aug 2008)

Ne, das invokeLater zu verwenden, um sein Fenster zu bauen und anzuzeigen ist eigentlich schon richtig. Und dazu, dass die ScrollPane manchmal erst verzögert ihre wahre Größe kennt, auch: http://www.java-forum.org/de/viewtopic.php?p=434845 

Also, das invokeLater in der main sollte bleiben, und zusätzlich noch 

```
SwingUtilities.invokeLater (new Runnable(){   
         public void run ()   
         {
            scrollpane.getVerticalScrollBar().setValue(scrollpane.getVerticalScrollBar().getMaximum());
         }
      });
```
im Konstruktor - obwohl das IMHO nicht notwendig sein sollte - vielleicht weil das eigentliche "validaten" erst ausgeführt wird, nachdem alle Events abgearbeitet sind - das grenzt IMHO schon fast an einen bug ... naja  :?


----------



## 0001001 (25. Aug 2008)

Mit dem invokeLater hat das nichts zu tun.
Was ich bisher herausgefunden habe ist, dass es an der Zeile

```
heTimeLine.setRowHeight(maxRowHeight);
```
liegt.

Anscheinend mag Java es gar nicht, wenn man im CellRenderer die Höhe der Zeile festlegt.


----------



## Marco13 (25. Aug 2008)

Ohja, da bin ich auch drüber gestolpert. Hatte die setValue-Methode zwar durch die Methode ersetzt, wo das eigentlich drinstehen sollte (nämlich getTableCellRendererComponent), und auch die rowheight ist mir aufgefallen und ich hatte kurz geschaut, ob's vielleicht daran liegt, aber dass die in setValue verwendet wird hatte ich übersehen. Aber wenn man die row height im Konstruktor setzt, geht's doch?!


----------



## 0001001 (25. Aug 2008)

Genau das mache ich im Moment  Klappt wunderbar.


----------

