PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Eventhandling bei Swing-Komponenten einfach abschaltbar?


Senior Sanchez
2005-04-20, 12:27:51
Hallo,

und zwar habe ich folgendes problem: An eine JComboBox ist ein ItemListener registriert, der bei einer Änderung des selektierten Items eine Nachricht über das Netzwerk an einen Server schickt. Beim Start der Applikation wird die JComboBox automatisch mit Serverdaten befüllt, nur ist die add()-Methode der JComboBox so dämlich implementiert, dass jedes mal das neueste hinzugefügte Item automatisch selektiert wird. Dies ruft nun den ItemListener auf den Plan, der demzufolge den Server zuspammed.

Nun ist die Frage: Lässt sich das Eventhandling der JComboBox/einer Swing-Komponente vollständig deaktivieren, sodass auf keine Events reagiert wird?

Die dirrrrrty-style Variante wäre, einfach alle EventListener vor dem hinzugefügen von Items zu deregistrieren und nachdem der Vorgang abgeschlossen ist, wieder zu registrieren. Doch die Lösung ist umständlich und natürlich wieder ne neue Fehlerquelle ;)


mfg Senior Sanchez

ethrandil
2005-04-20, 15:11:51
wa spricht gegen


box.setEnabled(false);
/*
Initialisieren
blabla
*/
box.setEnabled(true);


?

Senior Sanchez
2005-04-20, 16:24:03
Das es nicht funktioniert ;) Das spricht dagegen. *g* Ich habe das schon ausprobiert, und es ging net, leider.

Kannst es ausprobieren.


box.addItemListener(new ItemListener() {
/**
* itemStateChanged
*
* @param e ItemEvent
*/
public void itemStateChanged(ItemEvent e) {
System.out.println("bla");
}

});

box.setEnabled(false);

box.addItem("test1");
box.addItem("test2");
box.addItem("test3");

box.setEnabled(true);



mfg Senior Sanchez

ethrandil
2005-04-20, 17:11:35
http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/MutableComboBoxModel.html#addElement(java.lang.Object)

überladen? O_O

- Eth

EDIT: Bei mir macht das nur ein mal "bla" ist das richtig?

Senior Sanchez
2005-04-20, 17:15:46
Ich denke es wird aufs Überschreiben von bestimmten Klassen hinauslaufen wobei das ziemlich ätzend wird, weil ich mehrere "live-updating"-Komponenten habe und ich nicht das halbe Swing neuimplementieren will ;)

Ich werde das dann denke ich über nen Flag machen und die einzelnen event-Methoden überschreiben. Wenns flag gesetzt ist wird nix gemacht, wenns net gesetzt ist, wird geforwarded auf die event-Methoden der Superklasse.


mfg Senior Sanchez

ethrandil
2005-04-20, 17:21:09
Ja, so würde ich es wohl auch machen ;-)

Sauberer wäre es aber wohl, die entsprechenden Models umzuschreiben :)

- Eth

Gast
2005-04-20, 20:18:49
warum befüllst du nicht zuerst die combobox per add()
und rufst erst nachher addItemListener(..) auf.
nur mal so eine idee

lg
michl

HellHorse
2005-04-20, 21:02:58
Was spricht gegen die saubere Lösung und ComboBoxModel zu implementieren? MVC in Swing ist zum brauchen da.

Senior Sanchez
2005-04-20, 22:16:14
warum befüllst du nicht zuerst die combobox per add()
und rufst erst nachher addItemListener(..) auf.
nur mal so eine idee

lg
michl

Das geht nicht, weil diese Stadien nicht eindeutig abgrenzbar sind, d.h. es kann auch nachdem die ComboBox erzeugt wurde zu updates kommen, das Items hinzugefügt werden müssen was dann am ende wieder zu meiner dirrrty-Variante führt.


Was spricht gegen die saubere Lösung und ComboBoxModel zu implementieren? MVC in Swing ist zum brauchen da.

Das ist die Lösung die ethrandil schon angesprochen hat und die werde ich sicherlich auch machen, obwohl es ätzend werden kann, weil ich das eventuell für nen haufen Komponenten machen muss. Deshalb war mir ne kleine aber feine Lösung am Liebsten, aber die gibt es anscheinend nicht.


mfg Senior Sanchez

HellHorse
2005-04-21, 08:47:40
Das ist die Lösung die ethrandil schon angesprochen hat und die werde ich sicherlich auch machen, obwohl es ätzend werden kann, weil ich das eventuell für nen haufen Komponenten machen muss. Deshalb war mir ne kleine aber feine Lösung am Liebsten, aber die gibt es anscheinend nicht.
Sehe nicht ganz wie es mehr zu tun geben sollte, als all die Listener, die du schon jetzt hast.
1 Subklasse von AbstractListModel, die ComboBoxModel implentiert und auf einer Liste arbeitet. Das sind 2 Instanzvariablen und 4 Methoden. 3 davon Einzeiler und in der vierten hast du deinen alten Listener Code.

Senior Sanchez
2005-04-21, 09:20:32
Sehe nicht ganz wie es mehr zu tun geben sollte, als all die Listener, die du schon jetzt hast.
1 Subklasse von AbstractListModel, die ComboBoxModel implentiert und auf einer Liste arbeitet. Das sind 2 Instanzvariablen und 4 Methoden. 3 davon Einzeiler und in der vierten hast du deinen alten Listener Code.

Ich meinte mit den mehreren Komponenten dass mir das eventuell auch noch blühen kann bei anderen Swing-Komponenten, z.B. ne JTable oder auch eigene Klassen zur Datumsauswahl ;) Das war gemeint.
Die eigentlich Implementierung sollte kein Ding sein, geht mir nur um den Aufwand.

Was meinst du mit Listener Code?

mfg Senior Sanchez

HellHorse
2005-04-21, 10:46:12
z.B. ne JTable oder auch eigene Klassen zur Datumsauswahl ;) Das war gemeint.
Ja und, wo ist das Problem? Die Idee von MVC ist ja GUI und Model zu trennen. Du gibst ein Datum aus und bekommst eines zurück. Ist doch scheiss egal was für einen Komponenten Swing verwendet um das Datum darzustellen und zu editieren.

Das einzige was am TableModel komplizierter ist, ist dass du zwei Indices hast. Und du kannst ja im GUI angeben was für eine Komponente zum editieren verwendet werden soll.

Ich sehe immer noch nicht, wie das in mehr Aufwand ggü dem was du vorher hattest ausufern soll.

Was meinst du mit Listener Code?
Der pöse Code im Listener, der den Server zuspammt?

Senior Sanchez
2005-04-21, 11:47:44
Kann ich in so eine Datumsauswahlbox (ist ne eigenimplementierung, aber nicht von mir) einfach nen Subklasse packen die ein ComboBoxModel implementiert? Und wie steht es bei TextFelder? oder JSpinner? oder RadioButtons?


mfg Senior Sanchez

HellHorse
2005-04-21, 15:05:07
Kann ich in so eine Datumsauswahlbox (ist ne eigenimplementierung, aber nicht von mir) einfach nen Subklasse packen die ein ComboBoxModel implementiert?
Nein, das ist GUI nicht Model. Also ComboBoxEditor. Und dann der JComboBox sagen, sie soll diese Komponente als Editor verwenden.


Und wie steht es bei TextFelder? oder JSpinner? oder RadioButtons?
Was ist damit?
Ob du sie als ComboBoxEditor Komponenten werden kannst? Ja klar.
Ob sie alle ein Model haben? Ja natürlich.

Senior Sanchez
2005-04-21, 15:49:51
Nein, das ist GUI nicht Model. Also ComboBoxEditor. Und dann der JComboBox sagen, sie soll diese Komponente als Editor verwenden.


Was ist damit?
Ob du sie als ComboBoxEditor Komponenten werden kannst? Ja klar.
Ob sie alle ein Model haben? Ja natürlich.

Kannste mir mal kurz zeigen wie du die Implementierung meinst? Ich kann das im Moment irgendwie net nachvollziehen.


mfg Senior Sanchez

HellHorse
2005-04-21, 23:38:50
Mein Beispiel war eigentlich ganz nett, bis ich merke, dass es so nicht funktioniert, weil ich unverändlicher Objekte im Model habe, die ich nicht unterscheiden kann X-D
Da war der Schaden aber schon angerichtet und meine Kreativität reichte nicht mehr für ein neues Beispiel. :(

Das Ganze sieht wieder einmal nach extrem viel Code aus. Die wichtigen Punkte sind aber kurz und habe ich als Prosa hingeschrieben. Damit es anschaulich ist, sollte es aber IMHO schon ein funktionieredes Beispiel sein.
Und sonst probier es mal ohne MVC. ;)

Zuerst das ComboBoxModel. Die wichtige Methode hier ist #setSelectedItem die aufgeruft wird, wenn ein Item selektiert wird. Hier könntest du dann Daten an den Server schicken. Zudem habe ich eine Methode gemacht, die der Editor aufrufen wird, wenn er das selektierte Item geändert hat, um das GUI zu updaten. Sonst ist das ganze sehr allgemein und totsimpel.

Die #example methode ignorierst du am besten einfach und den Trick mit dem cast brauchst du nicht zu schnallen.
package gui.sanchez;

import java.util.ArrayList;
import java.util.List;

import javax.swing.AbstractListModel;
import javax.swing.ComboBoxModel;

public class ListComboModel<T> extends AbstractListModel implements ComboBoxModel {

private List<T> items;

private T selectedItem;

private Class<T> token;

public ListComboModel(Class<T> token, List<T> items) {
super();
this.token = token;
this.items = items;
}

public void setSelectedItem(Object anItem) {
this.selectedItem = this.token.cast(anItem);
}

public void changedAtSelection() {
// update UI
int selectionIndex = this.items.indexOf(this.selectedItem);
this.fireContentsChanged(this, selectionIndex, selectionIndex);
}

public T getSelectedItem() {
if (this.selectedItem == null && !this.items.isEmpty()) {
this.selectedItem = this.items.get(0);
}
return this.selectedItem;
}

public int getSize() {
return this.items.size();
}

public T getElementAt(int index) {
return this.items.get(index);
}


public static ListComboModel<ValueHolder> example() {
//XXX raw types
List<ValueHolder> chars = new ArrayList<ValueHolder>();
for (Character each : new Character[] {'S' , 'W', 'I' , 'N' , 'G'}) {
chars.add(new ValueHolder<Character>(each));
}
ListComboModel<ValueHolder> model = new ListComboModel<ValueHolder>(ValueHolder.class, chars);
return model;
}
}

Hier ist der eigene Editor. Eigentlich bloss ein JSpinner mit einem JTextField und einem eigenen Modell. Es gibt zwei Listener, einer updated das das Model und der andere JTextField. Der letztere ist nicht ganz sauber, ich weiss. Das JTextField ist eigentlich nur zu Layout-Zwecken da. Du kannst es und den entsprechenden Listener auch weglassen.
package gui.sanchez;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import javax.swing.ComboBoxEditor;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class CharacterEditor implements ComboBoxEditor {

private JSpinner editor;

protected JTextField textField;

private List<ActionListener> listeners;

private SpinnerCharacterModel spinnerModel;

private ValueHolder<Object> item;

protected ListComboModel<ValueHolder<Character>> model;

public CharacterEditor(char start, char end, ListComboModel<ValueHolder<Character>> model) {
this.model = model;
this.spinnerModel = new SpinnerCharacterModel(start, end);
this.editor = new JSpinner(this.spinnerModel);
this.editor.addChangeListener(new CharacterListener());
this.textField = new JTextField(8);
this.textField.setEditable(false);
this.editor.setEditor(this.textField);

CharacterPropagator listener = new CharacterPropagator();
this.editor.addChangeListener(listener);

this.listeners = new CopyOnWriteArrayList<ActionListener>();
}

public void addChangeListener(ChangeListener l) {
this.editor.addChangeListener(l);
}

public Component getEditorComponent() {
return this.editor;
}

public void setItem(Object anObject) {
// XXX unsafe cast
this.item = (ValueHolder<Object>) anObject;
this.spinnerModel.setValue(this.item.getContents());
this.notifyListeners();
}

public Object getItem() {
this.item.setContents(this.editor.getValue());
return this.item;
}

public void selectAll() {
//nothing
}

public synchronized void addActionListener(ActionListener l) {
this.listeners.add(l);
}

public synchronized void removeActionListener(ActionListener l) {
this.listeners.remove(l);
}

private void notifyListeners() {
ActionEvent e = new ActionEvent(this, 0, "edit");
for (ActionListener each : this.listeners) {
each.actionPerformed(e);
}
}

private class CharacterListener implements ChangeListener {
public void stateChanged(ChangeEvent e) {
JSpinner source = (JSpinner) e.getSource();
String value = source.getValue().toString();
CharacterEditor.this.textField.setText(value);
}
}

private class CharacterPropagator implements ChangeListener {
public void stateChanged(ChangeEvent e) {
JSpinner source = (JSpinner) e.getSource();
Character newValue = (Character) source.getValue();
ValueHolder<Character> holder = CharacterEditor.this.model.getSelectedItem();
Character oldValue = holder.getContents();
if (!newValue.equals(oldValue)) {
holder.setContents(newValue);
CharacterEditor.this.model.changedAtSelection();
}
}
}
}

Das Model für den Spinner, auch hier gibt es eigentlich nichts zu sehen. Eine obere Grenze, eine untere, das wars.

package gui.sanchez;

import javax.swing.AbstractSpinnerModel;

public class SpinnerCharacterModel extends AbstractSpinnerModel {

private char start, end, selected;

public SpinnerCharacterModel(char start, char end) {
this.start = start;
this.end = end;
}

public Character getValue() {
return this.selected;
}

public void setValue(Object value) {
this.selected = (Character) value;
this.fireStateChanged();
}

public Character getNextValue() {
if (this.selected >= this.end) {
return null;
}
return (char) (this.selected + 1);
}

public Character getPreviousValue() {
if (this.selected <= this.start) {
return null;
}
return (char) (this.selected - 1);
}
}

Und der Vollständigkeit halber. Das ist ein spezieller hack für mein Beispiel und braucht dich nicht zu interessieren.
Das Problem ist wenn ich nur Characters im ComboBoxModel habe, kann ich nicht genau sagen, welcher ausgewählt wurde, wenn mehrmals der gleiche vorkommt. Das wäre kein Problem wenn Characters mutable wären und der Editor den Wert des ausgewählten ändern kann.
Ok, ich hätte jetzt einen Identitätsvergleich machen können aber dann wäre das Modell weniger allgemein. Dafür wäre das Beispiel einfacher.

package gui.sanchez;

public class ValueHolder<T> {
private T contents;

public ValueHolder(T contents) {
this.contents = contents;
}

public T getContents() {
return this.contents;
}

public void setContents(T contents) {
this.contents = contents;
}

public String toString() {
return this.contents.toString();
}
}


Und noch etwas Code um das Ganze laufen zu lassen
Der wichtige Punkt ist combo.setEditor(editor); bei dem ich der ComboBox einen eigenen Editor angebe, der das Model updatet und natürlich new JComboBox(model); das die JComboBox auf dem Model erstellt.
package gui.sanchez;

import static javax.swing.SwingUtilities.invokeLater;
import static javax.swing.WindowConstants.EXIT_ON_CLOSE;

import javax.swing.BorderFactory;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class CharacterComboDemo {

private static JComponent createPanel() {
JPanel panel = new JPanel();

// XXX unsafe cast
ListComboModel<ValueHolder<Character>> model = (ListComboModel<ValueHolder<Character>>) ListComboModel.example();
CharacterEditor editor = new CharacterEditor('A', 'Z', model);

JComboBox combo = new JComboBox(model);
combo.setEditor(editor);
combo.setEditable(true);
panel.add(combo);

return panel;
}

protected static void createAndShowGUI() {
JFrame.setDefaultLookAndFeelDecorated(true);

JFrame frame = new JFrame("CharacterComboDemo");
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);

JComponent newContentPane = createPanel();
newContentPane.setOpaque(true);
newContentPane.setBorder(BorderFactory.createEmptyBorder(12, 12, 11, 11));
frame.setContentPane(newContentPane);

frame.pack();
frame.setVisible(true);
}

public static void main(String[] args) {
System.setProperty("swing.aatext", "true");
invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}