JComboBox mit eigenem Model vorhanden, wie selektieren

  • Themenstarter Themenstarter Guest
  • Beginndatum Beginndatum
Status
Nicht offen für weitere Antworten.
G

Guest

Gast
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...
 
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
 
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.
 
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
 
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?
 
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.
 
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
 
Eine einfache ComboBoxModel-Implementierung auf Basis von Arrays sieht in etwa so aus:
Code:
/*
 * 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
 
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?
 
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
 
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
 
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?
 
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
 
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?
 
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
 
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?!
 
Nachtrag2: So werd ich das zumindest mal versuchen, es könnte doch aber sein, dass es dafür schon ne (geerbte) Methode gibt
 
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
 
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.
 
Für das selektierte Element bekommt die Combo Box den Index -1 geliefert. Implementierung könnte so aussehen:
Code:
// -------------------------------------------------------------------------
// 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
 
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
 
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?
 
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"?
 
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:
Code:
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!
 
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?
 
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:
Code:
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
 
Hmm, d.h. ich kann keine Liste mit einem leeren Feld erstellen oder müsste mir mit einem leeren String o.ä. weiterhelfen, oder?
 
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
 
Status
Nicht offen für weitere Antworten.

Zurück
Oben