# JTable -> Suche passenden Listener



## hdi (23. Nov 2008)

Hi,

ich hab eine gewisse Vorstellung davon wie das Arbeiten für den User mit meinem JTable sein soll, aber
finde einfach nicht die passenden Methoden/Listener, wie ich das realisieren kann.

Ich hab einige Einträge in der Table, mit jeweils 4 Spalten, wobei nur 1 editierbar ist. Wenn man da Doppel-Klick
drauf macht, wird sie angewählt und man kann den Text löschen und ändern.

Das wär schon mal der Listener, den ich hier brauch. Was is das für einer? Muss ich das irgendwo kompliziert über nen MouseListener machen? 

Ich möchte nämlich gerne, dass der Text wirklich selektiert ist, wenn man da Doppelklick drauf macht, das ist
er nämlich standard-mässig nicht. Ihr wisst schon, damit man ihn nicht selbst markieren und löschen muss, sondern
einfach drauflosschreiben kann, und der alte wird beim ersten Tastendruck überschrieben.

Dann möchte ich* während* der Eingabe diese überprüfen, auf Korrektheit. Also regex, aber wo? Auch hier such ich den Listener, der da reagiert während man in einer Zelle etwas tippt. DocumentListener? Wenn ja, wie krieg ich den in die Zelle?

Ich häng etwas in der Luft, einem JTable kann so so ungefähr jeden Listener adden, den es in Java gibt, und ich hab da nich den Überblick... 

Danke


----------



## hdi (23. Nov 2008)

Also ich hab jetz gesehen dass es den CellEditor gibt. Aber ich hab mir jetzt nen eigenen gemacht und dem Table geaddet, allerdings wird da nie ne Methode abgefeuert, irgendwie krieg ich den nicht zum laufen..

Das Bsp zu dem Editor bei Sun ist auch nicth hilfreich, denn die verwenden nur einen Default CellEditor und implementieren gar nicht das Interface.. Ich weiss echt nicht, wie ich das machen soll, pls helft mir


----------



## Marco13 (23. Nov 2008)

Könnte ziemlich fummelig werden. Der DefaultCellEditor hat eine "editorComponent". Das ist ein JFormattedTextField. Von dem kann man sich vmtl. ein Document holen. Aber wie und wann und wo man das am besten macht ...  ???:L hm - vermutlich am besten, wenn getTableCellEditorComponent aufgerufen wird...


----------



## hdi (23. Nov 2008)

Jo, und genau das is mein Problem, das reagiert irgendwie nicht. Hier der Code:


```
JTalbe() = new ....
...
table.setCellEditor(new MyCellEditor());
```


```
public class MyCellEditor implements TableCellEditor, CellEditorListener {

	public MyCellEditor() {
		this.addCellEditorListener(this);
	}

	@Override
	public Component getTableCellEditorComponent(JTable table, Object value,
			boolean isSelected, int row, int column) {

		System.out.println("getTableComponent wurde aufgerufen!");

		return null;
	}

	//... weitere methoden
}
```

Und egal was ich in der Liste veränder, die Print-Methode wird nie aufgerufen, d.h. der reagiert nicht...
Das ist das was ich oben meinte. Ich krieg keinen eigenne CellEditor hin.


----------



## Marco13 (23. Nov 2008)

Tja, Hab gerade mal das Beispiel von http://java.sun.com/docs/books/tutorial/uiswing/components/table.html#validtext getestet. Da erscheint die Meldung.


----------



## André Uhres (23. Nov 2008)

hdi hat gesagt.:
			
		

> Ich möchte nämlich gerne, dass der Text wirklich selektiert ist, wenn man da Doppelklick drauf macht
> Dann möchte ich* während* der Eingabe diese überprüfen, auf Korrektheit. Also regex, aber wo?


Hier ist ein Beispiel mit einem DocumentListener, den wir einfach im Konstruktor an das Textfeld hängen.
In "getTableCellEditorComponent" und "shouldSelectCell" rufen wir  textfield.selectAll() auf:

```
class MyEditor extends DefaultCellEditor implements DocumentListener {

    private JTextField textfield;

    public MyEditor() {
        super(new JTextField());
        textfield = ((JTextField) editorComponent);
        textfield.getDocument().addDocumentListener(this);
    }

    @Override
    public boolean stopCellEditing() {
        String value = ((JTextField) getComponent()).getText();
        if (!value.equals("")) {
            if (!isValidValue(value)) {
                ((JComponent) getComponent()).setBorder(new LineBorder(Color.red));
                return false;
            }
        }
        return super.stopCellEditing();
    }

    @Override
    public Component getTableCellEditorComponent(final JTable table, final Object value,
            final boolean isSelected, final int row, final int column) {
        textfield.setBorder(new LineBorder(Color.black));
        try {
            textfield.setText(value.toString());
        } catch (Exception e) {
            textfield.setText("");
        }
        textfield.selectAll();
        return textfield;
    }

    @Override
    public Object getCellEditorValue() {
        return textfield.getText();
    }

    private boolean isValidValue(String value) {
        return value.matches("\\d*");
    }

    @Override
    public boolean shouldSelectCell(EventObject anEvent) {
        textfield.selectAll();
        return true;
    }

    public void changedUpdate(DocumentEvent e) {
        update();
    }

    public void insertUpdate(DocumentEvent e) {
        update();
    }

    public void removeUpdate(DocumentEvent e) {
        update();
    }

    public void update() {
        // Verändert die Umrandung des Editors, jenachdem, ob eine gültige
        // oder eine ungültige Eingabe gemacht wurde
        Color color;
        if (isValidValue(textfield.getText())) {
            color = Color.GREEN;
        } else {
            color = Color.RED;
        }
        textfield.setBorder(BorderFactory.createLineBorder(color));
    }
}
```


----------



## hdi (23. Nov 2008)

Ah, tausend, tausend Dank! So verwendet man das! Super!

Aber es ist so, dass wenn man das editieren anfängt, dann sollen alle Buttons abgeschaltet werden,
und wieder verfügbar gemacht werden, sobald man aufgehört hat zu editieren.
In die stopCellEditing() Methode kann ich das gut einbauen, aber wenn man das Editieren abbricht, 
weil man woanders hin klickt oder ESC drückt, dann sind die Buttons ja noch immer deaktiviert.

Eig. dachte ich das diese Methode hier in dem Fall aufgerufen wird:

```
@Override
	public void cancelCellEditing() {
		
	}
```
Aber die wird nie aufgerufen... Welche ist es denn dann?


----------



## André Uhres (23. Nov 2008)

Die "cancel editing" Events werden nicht an die Cell Editor Listeners geschickt,
weil die JTable einfach den Cell Editor entfernt wenn man z.B. ESCAPE drückt.
Wir können JTable erweitern und die removeEditor() Methode überschreiben:

```
@Override
public void removeEditor() {
    TableCellEditor editor = getCellEditor();
    super.removeEditor();
    if (editor != null) {
        editor.cancelCellEditing();
    }
}
```


----------



## hdi (24. Nov 2008)

Super, dank dir!!

Jetz hab ich eig. alles genauso hinbekommen,wie ich wollte. Und mein Programm ist zu 99% fertig, eig. zu 100%, wäre das nicht dieser* üble Fehler, der mein komplettes Programm nutzlos macht.*  :cry: 

Es ist so: Mein dem JTable zugrunde liegendes Datenmodell speichert sein Daten in einem 2d-array:

Object[][], namens "data", wobei eben diese Dimensionen Zeilen und Spalten sind.

Wenn der User einen neuen Eintrag in der Liste macht, wird ein neues temporäres 2d-array erstellt, das ganz
einfach eine Kopie von "data" ist, mit dem Unterschied, dass seine Kapazität eine Zeile mehr hat.
Und in diese letzte Zeile wird dann der neue Eintrag gespeichert, danach wird dem "data" dieses temporäre 
Modell wieder zugewiesen, es ist jetzt also quasi erweitert worden (obwohl es eigentlich nun ein neues Object[][]-Array ist)

Das funzt auch soweit, der Table zeigt dann halt diese neue Liste an mit dem neuen Element drin.

Löschen geht analog dazu.

Jetzt ist das Problem, dass ich beim Editieren eines so neu hinzugefügten Listen-Eintrags einen Fehler krieg,
der sich durch hunderte von Methoden durchschlängelt. Das gilt für alle neu hinzugefügten Einträge, also nicht nur
für den letzten. 

Dass dieses Problem sowas banales ist wie zB dass ich das neue 2d-array falsch abspeicher etc, denke ich nicht,
denn dann würde der JTable die Liste ja nicht korrekt anzeigen, doch das tut er. Man kann sie auch weitehrin
korrekt sortieren etc. Alles passt.

Ich schilder den Fehler beim editieren genauer: Man kann die Zelle anklicken, man kann auch reinschreiben. Es werden auch die in meinem CellEditor prüfenden Methoden auf Korrektheit korrekt angewendet (siehe Code von André), das heisst der Eintrag ist dort korrekt vorhanden.

Aber sobald ich Enter drücke, und das abspeichern will kommt der Fehler. Wenn ich mir die Meldung ansehe,
gibt es ein paar Stellen die den Fehler beinhalten könnten, aber ich weiss nicht so Recht welche.

*UPDATE:* Ich hab den Fehler gefunden, fragt mich nich wie lange es
gedauert hat, damit hab ich echt nicht gerechnet. Es liegt an dieser Zeile hier:


```
table.setAutoCreateRowSorter(true);
```

Wenn ich das beim Instanziieren meines JTables verwende, muss ich es jedesmal neu setzen nachdem ich 
etwas geaddet oder removed habe. Oder ich lass es komplett weg. 

Ein Blick in die API sagt mir:

When setAutoCreateRowSorter(true) is invoked, a TableRowSorter is immediately created and installed on the table. While the autoCreateRowSorter property remains true, every time the model is changed, a new TableRowSorter is created and set as the table's row sorter. 

Das ist in meinem Fall wohl nicht der Fall weil ich es per Hand machen muss. Das heisst ich "change" mein Model wohl nicht so, wie man es tun sollte, weil diese MEthode von nix invoked wird wenn ich was adde etc.

Oben im Text hab ich ja beschrieben wie ich etwas adde und lösche. Wie macht man es "richtig", also so, dass zB diese Methode aufgerufen wird weil der Table checkt, dass sein Model geändert wurde? Wer weiss, ob sich da noch andere Fehler in mein Programm eingeschlichen haben, die ich noch nicht gefunden hab...
Help :autsch:


----------



## André Uhres (24. Nov 2008)

Wenn wir eine neue Zeile einfügen, müssen wir daran denken, den Zeilenindex
zu konvertieren, weil der Modelindex anders sein kann als der Viewindex.
Zum Beispiel:

```
private void insertNewRow() {
    int row = table.getSelectedRow();
    if (row < 0) {
        return;
    }
    //convert the row index and insert row data:
    int modelRow = table.convertRowIndexToModel(row);
    model.insertRow(modelRow, buildRowData(row));
    //select the inserted row:
    table.changeSelection(table.convertRowIndexToView(modelRow), 0, false, false);
}
```

Das TableModel könnte etwa so aussehen:

```
class MyTableModel extends AbstractTableModel {
    private Object[][] data;
    public int getRowCount() {
        if (data != null) {
            return data.length;
        }
        return 0;
    }
    public int getColumnCount() {
        return 4;
    }
    public Object getValueAt(int rowIndex, int columnIndex) {
        Object value = null;
        try {
            value = data[rowIndex][columnIndex];
        } catch (Exception e) {
        }
        return value;
    }
    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        return columnIndex == 0;
    }
    @Override
    public void setValueAt(Object aValue, int row, int column) {
        data[row][column] = aValue;
        fireTableCellUpdated(row, column);
    }
    public void addRow(Object[] rowData) {
        insertRow(getRowCount(), rowData);
    }
    public void insertRow(int row, Object[] rowData) {
        Object[][] newData = new Object[getRowCount() + 1][];
        multiArrayCopy(data, 0, newData, 0, row);
        multiArrayCopy(data, row, newData, row + 1, getRowCount() - row);
        newData[row] = rowData;
        data = newData;
        fireTableRowsInserted(row, row);
    }
    public void multiArrayCopy(Object[][] source, int srcPos,
            Object[][] destination, int destPos, int length) {
        for (int a = srcPos, b = destPos; a < srcPos + length; a++, b++) {
            destination[b] = new Object[getColumnCount()];
            System.arraycopy(source[a], 0, destination[b], 0, source[a].length);
        }
    }
    public void removeRow(int row) {
        Object[][] newData = new Object[getRowCount() - 1][];
        multiArrayCopy(data, 0, newData, 0, row);
        multiArrayCopy(data, row + 1, newData, row, getRowCount() - row - 1);
        data = newData;
        fireTableRowsDeleted(row, row);
    }
}
```


----------



## hdi (24. Nov 2008)

Ah, wieder paar neue Dinge gelernt. System.arraycopy kannte ich auch noch nicht... Dank dir!!


----------

