# JComboBox mit eigenem Model vorhanden, wie selektieren



## Guest (14. Jan 2009)

Hallo,

ich habe eine JComboBox, welche mit Daten aus einem ComboBoxModel gefüllt wird. Soweit so gut. Kann man schon in dem Model selbst einen Listener erzeugen, welcher dann, sobald die entsprechende Auswahl selektiert wurde, diese in der ComboBox anzeigt? Momentan zeigt es mir zwar alle Werte an, wenn man die Box ausklappt, jedoch wenn ich etwas selektiere dann bleibt diese Auswahl nicht bestehen...


----------



## Gast (14. Jan 2009)

Nachtrag: Ich habe jetzt von DefaultComboBoxModel abgeleitet und das funktioniert. Jedoch trotzdem noch die Frage wie sieht der StandardListener aus, wo muss man da drehen um im Model herauszubekommen was selektiert wurde um das dann so anzuzeigen wie es Default macht. Wenn ich nun einen Eintrag auswähle wird das ja beim vererbten DefaultCombo... auch angezeigt


----------



## Verjigorm (14. Jan 2009)

Dann hast du irgendwas verkehrt gemacht und ohne Code kann dir da wohl keiner helfen

man braucht keinen Listener um die Selektion anzeigen zu lassen.


----------



## Gast (14. Jan 2009)

OK. Dann präzisiere ich mal meine Frage. Wenn ich ein eigenes ComboBoxModel erstelle (implements ComboBoxModel) und die Metzhode getSize und getElementAt(int index) überschreibe und bei getSize a.length zurückgebe und bei getElementAt a[index] zeigt mir die ComboBox brav die Elemente (a ist ein Array) an, wenn ich vorher setModel gemacht habe. Wenn ich ein Element selektiere bleibt dies jedoch nicht selektiert, d.h. wird in der ComboBox nicht als ausgewählt angezeigt. Welche Methode im Model muss man überschreiben, wenn man will das der Wert selektiert bleibt? Denn wenn ich ein DefaultComboBoxModel nutze, klappt das ja auch


----------



## SlaterB (14. Jan 2009)

im Interface ComboBoxModel gibts die vorgegebenen Methoden

void setSelectedItem(Object anItem);
+
  Object getSelectedItem();

die haben natürlich stark was damit zu tun

aber wenn du das Interface erfolgreich implementierst, dann doch auch diese Methoden?


----------



## Gast (14. Jan 2009)

getSelectedItem(); Ja. Das dachte ich mir schon. Aber woher soll ich denn in der Methode wissen welches Element der Benutzer (index) selektiert hat um dann data[index] zurückzuliefern.


----------



## SlaterB (14. Jan 2009)

das Model muss sich nur merken, was vorher über die Methode 
setSelectedItem(Object anItem); 
gesetzt wurde,

denn das wird die JComboBox zum Zeitpunkt der Selektion aufrufen bzw. ein automatischer Listener der JComboBox

dabei wird die JComboBox nicht sagen, welcher Index dazugehört,
entweder speicherst du dieses Objekt in einer separaten Variable,
oder du findest den zugehörigen Index raus und speicherst diesen Index


----------



## Ebenius (14. Jan 2009)

Eine einfache ComboBoxModel-Implementierung auf Basis von Arrays sieht in etwa so aus: 
	
	
	
	





```
/*
 * This file is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */

package com.ebenius;

import java.util.Arrays;

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

/**
 * TODO: Javadoc me!
 * 
 * @version $Revision$ as of $Date$
 * @author Ebenius
 */
public class SimpleComboBoxModel extends AbstractListModel
  implements ComboBoxModel {

  private final Object[] data;
  private int selectedIndex;

  /**
   * Creates a new <code>SimpleComboBoxModel</code>.
   * 
   * @param data the data
   */
  public SimpleComboBoxModel(Object[] data) {
    this.data = data;
    selectedIndex = data.length > 0 ? 0 : -1;
  }

  @Override
  public Object getSelectedItem() {
    return selectedIndex == -1 ? null : data[selectedIndex];
  }

  @Override
  public void setSelectedItem(Object anItem) {
    final int index = Arrays.asList(data).indexOf(anItem);
    final int oldIndex = selectedIndex;
    if (oldIndex != index) {
      selectedIndex = index;
      fireContentsChanged(this, -1, -1);
    }
  }

  @Override
  public Object getElementAt(int index) {
    return data[index];
  }

  @Override
  public int getSize() {
    return data.length;
  }
}
```

Grüße, Ebenius


----------



## Gast (14. Jan 2009)

Vielen Dank. Der o.g. Code erscheint sehr nützlich


----------



## Gast (14. Jan 2009)

Achso, wenn ich nur implements ComboBoxModel nutze und eine Änderung wie die Selektion detektieren will, muss ich einfach addListDataListener(ListDataListener arg0) überschreiben, oder?

Wieso wird da als Listener überhaupt was übergeben? Wenn ich mal arg0 ausgeben lasse erscheint:

javax.swing.JComboBox[,0,0,0x0,invalid,alignmentX=0.0,alignmentY=0.0,border=,flags=0,maximumSize=,minimumSize=,preferredSize=,isEditable=false,lightWeightPopupEnabled=true,maximumRowCount=8,selectedItemReminder=]
javax.swing.plaf.basic.BasicListUI$Handler@cd2e33
javax.swing.plaf.basic.BasicComboBoxUI$Handler@9446e4

Es wird also wohl eine Art StandardListener übergeben, oder?


----------



## SlaterB (14. Jan 2009)

addListDataListener wird nur beim Hinzufügen eines Listener berührt,
das hat nix mit dem Auftreten von Events zu tun,
abgesehen davon, dass es eine schlechte Idee wäre, wegen soetwas irgendwelche Methoden zu überschreiben,

um auf Events zu reagieren kannst du selber einen Listener adden?!

-----

JComboBox implementiert das ListDataListener-Interface,  das dürfte das arg0 gewesen sein,
reagiert auf Datenänderungen durch Neuzeichnen


----------



## Ebenius (14. Jan 2009)

Im allgemeinen: Die meisten Swing-Komponenten arbeiten so, dass die Komponente ein Model hat, das Model kennt die Daten. Die Komponente meldet sich über als Listener bei seinem Model an, damit sie von Änderungen im Model erfährt. Das Model hat die Aufgabe alle Änderungen an alle Listener weiter zu geben. In Deinem Fall oben ist der erste Listener also Deine Combo Box, die anderen beiden sind aus dem UI (die Darstellung, die vom Look and Feel übernommen wird).

Anmerkung am Rande: Wenn Du sagst, dass Du "ComboBoxModel nutz[t]", dann ist das keine gute Wortwahl. Du implementierst das Interface (oder die Schnittstelle auf deutsch) ComboBoxModel. Und Methoden die in einer Schnittstelle definiert sind werden von der implementierenden Klasse nicht "überschr[ie]ben", sondern implementiert.

Natürlich musst Du alle im Interface enthaltenen Methoden in Deiner Klasse implementieren, ansonsten schimpft ja der Compiler mit Dir.

Hilft das zur Erklärung? Hier noch ein Link zum Sun-Artikel: Swing Architecture. Der Aufwand lohnt!

Grüße, Ebenius


----------



## Gast (14. Jan 2009)

Hallo,

danke für die Antworten...

>In Deinem Fall oben ist der erste Listener also Deine Combo Box
Wieso/Wie registriert sich die ComboBox als Listener? Damit sie, wenn ich das Model jetzt modifizieren würde andere Werte anzeigt?

Wenn ich jetzt also bei einer Änderung des Wertes noch einen anderen Listener informieren wöllte, könnte ich also in meinem eigenen Modell, welches die ComboBoxModel Schnittstelle implementiert einen eigenen Listener über addListener im Konstruktor des eigenen Modells hinzufügen?


----------



## SlaterB (14. Jan 2009)

stimmt

stimmt

(habe ich beides zuvor auch schon geschrieben  )


----------



## Ebenius (14. Jan 2009)

Gast hat gesagt.:
			
		

> Wenn ich jetzt also bei einer Änderung des Wertes noch einen anderen Listener informieren wöllte, könnte ich also in meinem eigenen Modell, welches die ComboBoxModel Schnittstelle implementiert einen eigenen Listener über addListener im Konstruktor des eigenen Modells hinzufügen?



Nur um das klar zu machen: Nicht nur mit Deinem eigenen Model: Du kannst zu diesem Zweck natürlich Listener an jedes beliebige Swing-Model hängen.

Grüße, Ebenius


----------



## Gast (14. Jan 2009)

Hallo,

wenn ich die Listener dem Modell hinzugefügt habe, kann ich auf diese standardmässig zugreifen oder muss ich dann die Listener im Modell abspeichern um ein notify aufzurufen, wenn sich an meinem Modell was ändert?


----------



## Gast (14. Jan 2009)

Nachtrag: Ich mein natürlich um contentsChanged für die jeweiligen Listener aufzurufen. Denn so hab ich da ja keinen Zugriff.  Ich wollte die Methode aus setSelectedItem für die hinzugefügten Listener aufrufen. Mit scheint aber ich muss die Listener in meinem eigenen Modell doch erst irgendwo selbst abspeichern


----------



## SlaterB (14. Jan 2009)

schau dir doch das Beispiel von Ebenius an:

public void setSelectedItem(Object anItem) { 
    final int index = Arrays.asList(data).indexOf(anItem); 
    final int oldIndex = selectedIndex; 
    if (oldIndex != index) { 
      selectedIndex = index; 
      fireContentsChanged(this, -1, -1); 
    } 
  } 


fällt dir da nichts zu 'contentsChanged' auf? wenn du von AbstractListModel erbst, hast du alles wichtige dazu zur Verfügung,
kannst auch getListDataListeners() aufrufen,
bisschen ausprobieren?!


----------



## Gast (14. Jan 2009)

Nachtrag2: So werd ich das zumindest mal versuchen, es könnte doch aber sein, dass es dafür schon ne (geerbte) Methode gibt


----------



## Ebenius (14. Jan 2009)

Also Moment mal. Schau Dir mein Beispiel nochmal an! Ich erbe von einer Klasse AbstractListModel die schon ein paar Methoden des Interfaces implementiert. Deshalb muss ich auch die add und remove-Methoden nicht implementieren. Wenn Du aber nicht vererben möchtest, sondern *ComboBoxModel direkt implementieren* willst, dann kannst Du doch keine geerbte Methode für nix haben, oder?

Klingt logisch, ge? Sag, dass ich Recht habe! ;-)

Ebenius


----------



## Gast (14. Jan 2009)

du hast natürlich wie immer recht  also ich will es nur bei der ComboBoxImplementierung belassen. Ich speichere nun bei jedem add den ListDataListener in meinem Modell ab. Was mich aber stört, ich will die Listener ja nur informieren, wenn jemand ein item angeklickt hat in setSelectedItem. Das ListDataEvent bietet ja aber nur     CONTENTS_CHANGED,INTERVAL_ADDED, INTERVAL_REMOVED. Es hat sich ja am Modell nix geändert. Ich will ja nur ne Info an den Listener schicken welches Objekt selektiert wurde.


----------



## Ebenius (14. Jan 2009)

Für das selektierte Element bekommt die Combo Box den Index -1 geliefert. Implementierung könnte so aussehen: 
	
	
	
	





```
// -------------------------------------------------------------------------
// Event dispatching
// -------------------------------------------------------------------------

/** Listener list for all listeners handled */
private EventListenerList listeners;

/**
 * Adds the {@link ListDataListener} to <code>this</code> instance.
 * 
 * @param listener the listener to add
 */
public void addListDataListener(ListDataListener listener) {
  listeners = listeners == null ? new EventListenerList() : listeners;
  listeners.add(ListDataListener.class, listener);
}

/**
 * Removes the {@link ListDataListener} from <code>this</code> instance.
 * 
 * @param listener the listener to remove
 */
public void removeListDataListener(ListDataListener listener) {
  if (listeners != null) {
    listeners.remove(ListDataListener.class, listener);
  }
}

/** Fires a {@link ListDataEvent} to all listeners, notifying that the selection changed. */
protected void fireSelectionChanged() {
  if (listeners == null) {
    return;
  }

  ListDataEvent event = null;
  final Object[] list = listeners.getListenerList();
  for (int idx = 0; idx < list.length; idx += 2) {
    if (list[idx] == ListDataListener.class) {
      if (event == null) {
        event = new ListDataEvent(this, //
              ListDataEvent.CONTENTS_CHANGED, -1, -1);
      }
      ((ListDataListener) list[idx + 1]).contentsChanged(event);
    }
  }
}
```

Ebenius


----------



## Ebenius (14. Jan 2009)

Nachtrag: Ich würde allerdings von AbstractListModel erben und nicht vollständig das Interface implementieren. Diese abstrakte Klasse bietet Dir genau den add/remove-Listener-Code und die fire-Methoden die Du brauchst. Alles andere kannste selber implementieren.

Wenn Du keinen guten Grund dagegen hast, dann spar Dir diese Arbeit und das damit verbundene Fehlerpotential. Ein guter Grund kann natürlich sein, dass Du's soweit im Detail lernen möchtest; in dem Fall hab ich ma nüscht gesagt. 

Grüße, Ebenius


----------



## Gast (14. Jan 2009)

Hallo,

ja, ich wollt das mal im Detail machen  Was ich jedoch noch nicht ganz verstanden habe, wieso die indizes -1 sind/sein müssen, die da übergeben werden...

event = new ListDataEvent(this, //
              ListDataEvent.CONTENTS_CHANGED, -1, -1); 

Quelle ist mit this ja einfach mein Modell. Wieso kann ich nicht gleich das selektierte Objekt in das Event packen?


----------



## Gast (14. Jan 2009)

Nachtrag: Und wie gesagt, wieso schickt man ein "Model" changed? Ich meine das Modell mit den zugrundeliegenden Daten bleibt ja gleich. Es wurde lediglich ein bestimmtes Objekt im Modell selektiert. Der Wert selber hat sich ja nicht geändert. Wieso schickt man dann trotzdem ein "Contents_Changed"?


----------



## Ebenius (14. Jan 2009)

Der selektierte Wert ist Teil des Models einer Combo Box. Dieser Wert ändert sich. Ergo ändert sich das Model. Ergo schickt es einen Event _CONTENTS_CHANGED_.

Du nimmst natürlich den Index des sich verändernden Werts. Wenn in der Liste der dritte Eintrag von oben "Fisch" war und sich zu "Fleisch" ändert, dann schickst Du natürlich: 
	
	
	
	





```
new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, 2, 2);
```

Der selektierte Wert hat in der Combo Box im Event nun mal den Index *-1*. Wenn sich also der selektierte Wert ändert, dann so schicken wie in meinem Code-Beispiel.

Grüße, Ebenius

PS: Bitte code-Tags verwenden!


----------



## Gast (15. Jan 2009)

Hallo Ebenius,

vielen Dank. Das hat wirklich weitergeholfen! Wo steht übrigens, dass der selektierte Wert den Index -1 hat? Übrigens, wenn ich meine ComboBox mit Werten fülle ist initial auch ein "leerer" Eintrag zu sehen. Wenn ich jedoch eine Selektion getroffen habe, ist kein "Leerer" Wert danach selektiertbar. Woran liegt das?


----------



## Ebenius (15. Jan 2009)

Anonymous hat gesagt.:
			
		

> Wo steht übrigens, dass der selektierte Wert den Index -1 hat?



Hier hat Sun geschludert und es nirgends in der API-Doc vermerkt. Ich hab's vor langer Zeit im Quelltext von DefaultComboBoxModel nachgelesen: 
	
	
	
	





```
public void setSelectedItem(Object anItem) {
  selected = anItem;
  selectedIndex = indexOf(anItem);
  fireContentsChanged(this, -1, -1);
}
```



			
				Anonymous hat gesagt.:
			
		

> Übrigens, wenn ich meine ComboBox mit Werten fülle ist initial auch ein "leerer" Eintrag zu sehen. Wenn ich jedoch eine Selektion getroffen habe, ist kein "Leerer" Wert danach selektiertbar. Woran liegt das?



An Deiner Model-Implementierung; sprich daran, dass getSelectedItem() initial _null_ zurück liefert, dieser Wert _null_ aber nicht per getElementAt(int) verfügbar ist.

Ebenius


----------



## Gast (18. Jan 2009)

Hmm, d.h. ich kann keine Liste mit einem leeren Feld erstellen oder müsste mir mit einem leeren String o.ä. weiterhelfen, oder?


----------



## Ebenius (18. Jan 2009)

Nein, genau das heißt es nicht. Es heißt vielmehr: Wenn Du am Anfang keinen "leeren" Wert haben möchtest, muss das Modell bei _getSelectedIndex()_ schon einen Wert angeben. Wenn Du hingegen auch einen leeren Wert auswählen können möchtest, dann muss bei _getElementAt(i)_ für irgendein _i > -1 && i < getSize()_ auch _null_ zurück gegeben werden.

Ebenius


----------

