Swing-Tips #
Bei der Arbeit mit Swing stößt man fast ständig an Besonderheiten und Eingenschaften, deren genaue Funktion man sich erst erarbeiten muss. Um solche Dinge für Andere Menschen und für mich zu erhalten und um vielleicht selber von den Tipps anderer Javaner zu profitieren, habe ich diese Seite begonnen, um sie hier zu erklären.
JTable wie ein Spreadsheet benutzen #
Der eigentliche Sinn einer editierbaren JTable ist meiner Meinung nach, dem Benutzer halbwegs das Gefühl zu geben, das er beim editieren eines Spreadsheets (also in MS Excel oder OpenOffice.org Calc) hat. Leider ist das "Feeling" etwas anders, was meiner Meinung nach unnötig die Benutzer verstört. Ich möchte jeden Unterschied, der mich stört, hierbei einzeln behandeln.
sofortiges löschen beim editieren #
Wenn man eine Textzelle (z.B. mit der Maus) aktiviert hat und dann eine alphanumerische Taste drückt, wird diese Taste an das Ende des bestehenden Textes angefügt. Das gleiche passiert beim Drücken von F2. In OOo Calc ist es so, daß beim einfachen Drücken der bestehende Wert immer überschrieben wird und man F2 drücken muss, um den Wert editieren zu können.
Dieses Problem ist aufgrund der Struktur der JTable-Klasse gar nicht so trivial, wie ich dachte. Deshalb dokumentiere ich hier einen Teil des Weges, den ich zu meiner Lösung genommen habe, um ggf. später beim nachbessern nicht alles neu suchen zu müssen:
Zuerstmal steht im Swingwiki
, daß die eine Lösung hätten. Diese Lösung basiert darauf, daß ein DefaultCellEditor gesetzt wird, der sich beim Aufruf automatisch komplett selektiert. Das hilft zwar, wenn ich eine alphanumerische Taste drücke, selektiert aber bei F2 ebenfalls den gesamten Text und verhindert somit, das man einen vorhandenen Zelleninhalt editieren kann.
In einem Sun-Forum
steht ein Quelltext, von dem ich überhaupt nicht verstehe, was der soll. Ich habe ihn nicht ausprobiert, nach der Beschreibung sollte er aber mit meinem Problem zu tun haben.
In einem Artikel
mit einem relativ einfachen Niveau fand ich den Hinweis, wie Funktionstasten und alphanumerische Tasten überhaupt behandelt werden und wo der Unterschied liegt: Scheinbar wird in BasicTableUI entschieden, ob es sich um eine einzufügende Taste oder eine Sondertaste (z.B. Pfeiltaste oder F2) handelt. F2 wird dann in BasicTableUI.actionPerformed als Action START_EDITING behandelt, normale Tasten in ~JTable.processKeyBinding().
Parallel fand ich in einem Forumsthread zum Thema
einen Beitrag von "oswald", der versucht hatte, ~processKeyBinding() zu überladen, aber mit dieser Idee nicht vorwärtsgekommen war. Nachdem ich das durchdacht hatte, merkte ich, daß dieser Gedanke der beste war, den ich gesehen hatte. Ich beschäftigte mit intensiver mit dieser Methode, fand das Problem, das oswald hatte, einen Workaround hierfür und habe folgende Lösung implementiert:
public class SpreadsheetJTable extends JTable {
protected boolean processKeyBinding(KeyStroke ks, KeyEvent e,
int condition, boolean pressed) {
/*
* Der Inhalt dieses if-Blocks ist aus der super-Methode entnommen. Ich
* erzeuge hier die Editor-Komponente genauso wie im Original. Dann
* selektiere ich allerdings den ganzen Text wie in
* Spreadsheet-Applikationen üblich.
*/
if (condition == WHEN_ANCESTOR_OF_FOCUSED_COMPONENT
&& isFocusOwner()
&& !Boolean.FALSE
.equals((Boolean) getClientProperty("JTable.autoStartsEdit"))) {
// We do not have a binding for the event.
Component editorComponent = getEditorComponent();
if (editorComponent == null) {
// Only attempt to install the editor on a KEY_PRESSED,
if (e == null || e.getID() != KeyEvent.KEY_PRESSED) {
return false;
}
// Don't start when just a modifier is pressed
int code = e.getKeyCode();
if (code == KeyEvent.VK_SHIFT || code == KeyEvent.VK_CONTROL
|| code == KeyEvent.VK_ALT) {
return false;
}
// Try to install the editor
int leadRow = getSelectionModel().getLeadSelectionIndex();
int leadColumn = getColumnModel().getSelectionModel()
.getLeadSelectionIndex();
if (leadRow != -1 && leadColumn != -1 && !isEditing()) {
if (!editCellAt(leadRow, leadColumn, e)) {
return false;
}
}
editorComponent = getEditorComponent();
if (editorComponent == null) {
return false;
}
// -TB-
// select everything (like Spreadsheet Apps do)
if (editorComponent instanceof JTextField) {
((JTextField) editorComponent).selectAll();
}
}
}
boolean retValue = super.processKeyBinding(ks, e, condition, pressed);
return retValue;
}
}
Das Ergebnis ist jetzt, das man einerseits mit einer normalen Taste den Zellinhalt überschreibt, aber andererseits mit F2 auch den vorhandenen Inhalt editieren kann! -- ThomasBayen
Lösung des löschen-beim-editieren Problems mit TableCellEditor #
Eine andere Lösung hat JensKapitza hier vorgestellt. Nach meinen Versuchen funktioniert dieses genauso gut wie meine Lösung. Mir fällt es auch schwer, eine der beiden Lösungen für "besser" zu erklären. (Lediglich habe ich bei Jens Lösung das Gefühl, daß man, wenn man dort Delegation durch Ableitung ersetzt, noch einige Zeilen Code sparen kann.) -- ThomasBayen
Man kann es auch mal ein wenig anders für jeden Type (hier geht es ja nur um String.class) machen es folg ein kleines Beispiel
// GPL v2 oder höher ;)
// (c) 2008 Jens Kapitza ;)
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.KeyEvent;
import java.util.EventObject;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.event.CellEditorListener;
import javax.swing.table.TableCellEditor;
import javax.swing.text.JTextComponent;
public class H {
public static void main(String[] args) {
// einfaches fenseter
JFrame f = new JFrame("a");
// ein paar test daten
String[][] d = new String[][] { { "A", "B", "C" }, { "A", "V", "2" } };
JTable t = new JTable(d, new String[] { "AAAA", "BBBBBBBBB",
"CCCCCCCCCCCCCCCC" });
// damit man was sieht
JScrollPane p = new JScrollPane(t);
// nun den editor holen
final TableCellEditor tce = t.getDefaultEditor(Object.class);
// und ein wenig ändern
// siehe flag select und getTableCellEditorComponent sowie isCellEditable
t.setDefaultEditor(Object.class, new TableCellEditor() {
// ich muss wissen ob ich diese componente nun überschreiben soll oder nicht wie bei oo calc wenn a getippt wird ist es überschreiben bei F2 z.b. nur ein anhängen!
private boolean select = false;
@Override
public Component getTableCellEditorComponent(JTable table,
Object value, boolean isSelected, int row, int column) {
// componente holen
Component cmp = tce.getTableCellEditorComponent(table, value,
isSelected, row, column);
// wenn !f2 dann alles selct
// ist es eine textcomponente nur sie kennt selectAll !!!!!
if (cmp instanceof JTextComponent) {
if (select) { // wenn es selectiert werden soll -> isCellEditable setzt den flag
// dann überschreiben ermöglichen!
((JTextComponent) cmp).selectAll();
select = false;
}
}
return cmp;
}
@Override
public void addCellEditorListener(CellEditorListener l) {
tce.addCellEditorListener(l);
}
@Override
public void cancelCellEditing() {
tce.cancelCellEditing();
}
@Override
public Object getCellEditorValue() {
return tce.getCellEditorValue();
}
@Override
public boolean isCellEditable(EventObject anEvent) {
if (anEvent instanceof KeyEvent) { // ist es ein Tasten event
// wer lust hat kann ja noch die tasten codes abfangen! --> f2 ist kein KeyEvent !!!
// hab aber auch nicht weiter nachgeguckt
select = true;
}
// ActionEvent kommt raus und dass ist ein AWT event und je nach dem welches kann es ein Keyevent, .... sein
System.out.println(anEvent);
// mehr will ich nicht machen
return tce.isCellEditable(anEvent);
}
@Override
public void removeCellEditorListener(CellEditorListener l) {
tce.removeCellEditorListener(l);
}
@Override
public boolean shouldSelectCell(EventObject anEvent) {
return tce.shouldSelectCell(anEvent);
}
@Override
public boolean stopCellEditing() {
return tce.stopCellEditing();
}
});
p.setPreferredSize(new Dimension(200, 400));
f.add(p);
f.pack();
// anzeigen und in der console gucken
f.setVisible(true);
}
}
Zuordnung von Tastenfunktionen #
In einer Tabelle hatte ich damit gekämpft, daß man mit TAB nicht in die nächste Tabellenzelle kam. Der Fehler lag ganz woanders, aber ich habe etwas über die Zuordnung von Tasten zu Aktionen gelernt. Wer also wissen will, wie besondere Events wie z.B. Cursortasten, TAB, oder auch Mausaktionen zugeordnet werden, sollte einfach im ganz normalen JavaTutorial
)fündig nachlesen. Dort ist sehr gut erklärt, was es mit InputMap und ActionMap auf sich hat.