Du verwendest einen veralteten Browser. Es ist möglich, dass diese oder andere Websites nicht korrekt angezeigt werden. Du solltest ein Upgrade durchführen oder ein alternativer Browser verwenden.
Hallo, weiss jemand von euch, ob es für die JComboBox auch so etwas ähnliches wie den RowFilter für die JTable gibt? Hab da leider nirgendwo was dazu gefunden.
Und zwar habe ich Bankleitzahlen und die dazugehörigen Bankinstitute. Diese stecken als Objekte der Klasse BLZ im DefaultComboBoxModel, welches in der ComboBox die Bankinstitute anzeigt und nach Auswahl daneben in einem JTextField die dazugehörige Bankleitzahl einträgt. Das klappt soweit erstmal. Jetzt möchte ich aber auch andersherum die Bankinstitute in der JComboBox filtern, wenn ich in dem Textfeld anfange, eine Bankleitzahl einzugeben. Also, wenn ich z.B. dort als erstes eine "4" eingebe, sollen in der JComboBox nur noch die Bankinstitute stehen, bei welchen die BLZ mit einer 4 beginnt usw. Bei einer JTable hatte ich das schon mit einem RowFilter hinbekommen. Ich weiss auch, dass ich beim Textfeld einen DocumentListener brauche, um die Eingaben zu erkennen und darauf zu reagieren. Aber ich weiss nicht, wie ich dann die Liste im ComboBoxModel filtern kann. Gibt es dazu so etwas wie einen Filter, oder muss ich das alles selbst per Hand machen? Also, immer nach jeder Eingabe die Liste mit den BLZ-Daten filtern und dem Model übergeben?
Für die ComboBox gibt es sowas nicht. Hier muss man wie früher auch bei der JTable das per Hand machen z.B. mit zwei Models - ein Gesamt- und ein gefiltertes Model - arbeiten.
Danke. Und schade. Na gut, dann werde ich das wohl mal so angehen. Ich überlege gerade nur, ob ich dann wirklich 2 Model brauche, oder z.B. 2 ArrayListen die dann immer in dem Model gesetzt werden könnten. Vielleicht kann man sich ja dann so etwas erstellen, was wie der RowFilter bei der JTable funktioniert, damit man das evtl. bei anderen solchen Vorkommnissen einsetzen kann. Aber werde ich dann ja sehen, wenn ich mich an die Arbeit mache...
Und zwar hab ich nun einen DocumentListener am Textfeld, welcher erkennt, wenn etwas in das Textfeld eingegeben wurde, damit dann die Werte in der Combobox gefiltert werden können. Das funktioneirt auch. Wenn ich nun aber gleichzeitig an der Combobox einen ItemListener hinzugefügt habe (ich möchte ja durch Auswahl eines Bank-Eintrags die BLZ in dem Textfeld angezeigt bekommen), erhalte ich eine
Code:
java.lang.IllegalStateException: Attempt to mutate in notification
. Ich bin mir nicht ganz sicher, was dies bedeutet, vermute aber, dass das Filtern in der Combobox den ItemListener auslöst und einen Wert in das Textfeld eintragen möchte, was wiederum den DocumentListener aufruft, welcher dann die Combobox filtert. Also evtl. eine Endlosschleife? Wenn ich den ItemListener deaktiviere, funktioniert es mit dem Filtern problemlos. Ich kann aber dann nur nicht aus den gefilterten Einträgen auswählen und im Textfeld anzeigen lassen. Vielleicht kann mir wer weiterhelfen, wie ich das hinkriegen kann. Ich poste mal ein paar relevante Codeschnipsel...
Zuerst den Auszug aus dem MainPanel, welches im Frame liegt und die beiden Komponenten Textfeld und Combobox enthält:
Java:
...
private ComboBoxBankinstitute comboBankinstitute;
private JTextField tfBLZ;
public MainPanel(){
comboBankinstitute = new ComboBoxBankinstitute();
comboBankinstitute.setPreferredSize(new Dimension(350, 24));
tfBLZ = new JTextField();
tfBLZ.setPreferredSize(new Dimension(100, 24));
// DocumentListener reagiert auf Änderungen im Textfeld
tfBLZ.getDocument().addDocumentListener(new BLZDocumentListener(this));
// DocumentFilter für Textfeld (max. 8 Zeichen, nur Zahlen von 0-9)
((AbstractDocument)tfBLZ.getDocument()).setDocumentFilter(new DocumentSizeFilter());
// ItemListener für Combobox (ausgewählte Einträge sollen in Textfeld angezeigt werden)
ItemListener listenerBLZ = new ItemListener() {
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
Bank bank = (Bank)comboBankinstitute.getSelectedItem();
tfBLZ.setText(bank.getBlz());
}
}
};
comboBankinstitute.addItemListener(listenerBLZ);
this.add(tfBLZ);
this.add(comboBankinstitute);
}
// Werte in Combobox filtern
public void filter() {
// Daten im Model filtern
comboBankinstitute.filterModel(tfBLZ.getText());
// Popup der Combobox anzeigen
comboBankinstitute.showPopup();
}
...
Und nun die Combobox für die Anzeige der Bankinstitute. Die Daten liegen in einer ArrayList<Bank> (Bank: bankID, blz und bankinstitut) und kommen aus einer Datenbank:
Java:
import java.util.ArrayList;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
@SuppressWarnings("serial")
public class ComboBoxBankinstitute
extends JComboBox {
private ArrayList<Bank> bankenListe;
private DefaultComboBoxModel modelBanken, tempModelBanken;
public ComboBoxBankinstitute() {
init();
}
private void init() {
DBHandlerBank dbHandlerBank = new DBHandlerBank();
// Holt die Liste der Bankinstitute und BLZ aus der Datenbank
bankenListe = dbHandlerBank.getBankinstitute();
// Model mit allen Daten anlegen
modelBanken = new DefaultComboBoxModel();
// Model nur für die gefilterten Daten
tempModelBanken = new DefaultComboBoxModel();
// 1. Element im Model ist ein leerer Eintrag mit der ID -1
modelBanken.addElement(new Bank(-1, "", "-----"));
for( Bank b : bankenListe )
modelBanken.addElement(b);
// Der Combobox wird das Model zugewiesen
setModel(modelBanken);
}
// Daten im Model filtern und dann der Combobox zuweisen
public void filterModel(String filtertext) {
// wenn Filtertext leer, nprmales Model mit allen Daten anzeigen
if(filtertext.equals(""))
setModel(modelBanken);
else {
// alle Elemente aus dem gefilterten Model entfernen
tempModelBanken.removeAllElements();
// durch BLZ-Liste gehen und nur Daten mit Anfangswerten in Model übernehmen
for(Bank b : bankenListe)
if(b.getBlz().startsWith(filtertext))
tempModelBanken.addElement(b);
// der Combobox wird das gefilterte Model zugewiesen
setModel(tempModelBanken);
}
}
}
In der init-Methode werden die Daten als ArrayList aus der Datenbank geholt, in ein DefaultComboBoxModel (modelBanken) geschrieben und der Combobox hinzugefügt.
Der Methode filterModel wird der String übergeben, welcher gefiltert werden soll. Ich gehe durch die ArrayList mit den Daten und schreibe nur die mit dem Filter übereinstimmenden Daten in ein temporäres DefaultComboBoxModel (tempModelBanken), welches nur die gefilterten Werte enthält und dann wieder der Comobox hinzugefügt wird.
Danke für diesen Tipp. Leider scheint es so, dass beim Filtern, wenn ich die Einträge aus dem Model entferne, um die gefilterten Werte reinzuschreiben, eine Action abgeschickt wird (javax.swing.JComboBox.fireActionEvent). Zuerst bekam ich eine NPE im MainPanel, weil der selektierte Eintrag null war. Hab den Null-Wert mal abgefangen. Ich hatte jetzt mal die Zahlen 1 und 2 in das Textfeld reingeschrieben. Die 1 ging noch, nach der 2 gab es die Exception
Code:
java.lang.IllegalStateException: Attempt to mutate in notification
:
Java:
Bank: null
Bank: 12020300 :: BkmU Berlin
Exception in thread "AWT-EventQueue-0" java.lang.IllegalStateException: Attempt to mutate in notification
at javax.swing.text.AbstractDocument.writeLock(Unknown Source)
at javax.swing.text.AbstractDocument.replace(Unknown Source)
at javax.swing.text.JTextComponent.setText(Unknown Source)
at MainPanel$1.actionPerformed(MainPanel.java:46)
at javax.swing.JComboBox.fireActionEvent(Unknown Source)
at javax.swing.JComboBox.contentsChanged(Unknown Source)
at javax.swing.AbstractListModel.fireContentsChanged(Unknown Source)
at javax.swing.DefaultComboBoxModel.setSelectedItem(Unknown Source)
at javax.swing.DefaultComboBoxModel.addElement(Unknown Source)
at ComboBoxBankinstitute.filterModel(ComboBoxBankinstitute.java:64)
at MainPanel.filter(MainPanel.java:63)
at BLZDocumentListener.insertUpdate(BLZDocumentListener.java:25)
Hier nochmal das MainPanel, in welchem der ItemListener durch den ActionListener ausgetauscht wurde (steht noch auskommentiert drin):
Java:
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.text.AbstractDocument;
@SuppressWarnings("serial")
public class MainPanel
extends JPanel{
private ComboBoxBankinstitute comboBankinstitute;
private JTextField tfBLZ;
public MainPanel(){
comboBankinstitute = new ComboBoxBankinstitute();
comboBankinstitute.setPreferredSize(new Dimension(350, 24));
tfBLZ = new JTextField();
tfBLZ.setPreferredSize(new Dimension(100, 24));
// DocumentListener reagiert auf Änderungen im Textfeld
tfBLZ.getDocument().addDocumentListener(new BLZDocumentListener(this));
// DocumentFilter für Textfeld (max. 8 Zeichen, nur Zahlen von 0-9)
((AbstractDocument)tfBLZ.getDocument()).setDocumentFilter(new DocumentSizeFilter());
// ItemListener für Combobox (ausgewählte Einträge sollen in Textfeld angezeigt werden)
/*ItemListener listenerBLZ = new ItemListener() {
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
Bank bank = (Bank)comboBankinstitute.getSelectedItem();
tfBLZ.setText(bank.getBlz());
}
}
};*/
ActionListener blzListener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
Bank bank = (Bank)comboBankinstitute.getSelectedItem();
System.out.println("Bank: " + bank);
if(bank != null)
tfBLZ.setText(bank.getBlz());
}
};
//comboBankinstitute.addItemListener(listenerBLZ);
comboBankinstitute.addActionListener(blzListener);
this.add(tfBLZ);
this.add(comboBankinstitute);
}
// Werte in Combobox filtern
public void filter() {
// Daten im Model filtern
comboBankinstitute.filterModel(tfBLZ.getText());
// Popup der Combobox anzeigen
comboBankinstitute.showPopup();
}
}
Wow, cool. So funktioniert es prima. Ich hab jetzt nur noch ein kleines Problem, was aber wohl wahrscheinlich mit dem Popup der ComboBox zusammenhängt. Und zwar möchte ich ja, dass wenn ich was in das Textfeld eintippe, dass dann die ComboBox aufklappt und die gefilterten Einträge angezeigt werden. Wenn ich dann einen Eintrag auswähle, klappt das Popup wieder zu und schreibt den Wert ins Textfeld. Soweit, so gut. Beim Hinzufügen von Zahlen in das Textfeld klappt es auch, nur wenn ich einen Eintrag in der ComboBox angeklickt hatte und dann wieder ins Textfeld gehe und Zahlen entferne, dann ist das Popup von der ComboBox zum Teil abgeschnitten und nicht ganz sichtbar. Vielleicht hab ich den Aufruf
[…]wenn ich einen Eintrag in der ComboBox angeklickt hatte und dann wieder ins Textfeld gehe und Zahlen entferne, dann ist das Popup von der ComboBox zum Teil abgeschnitten und nicht ganz sichtbar. Vielleicht hab ich den Aufruf
OK, hier mal ein Screenshot... Wie gesagt, ich hab einen Eintrag in der ComboBox (im Popup) angeklickt. Der Cursor war immer noch im Textfeld. Dann hab ich nochmal auf die ComboBox (mittlerweile wieder zusammengeklappt) geklickt, Cursor/Focus von Textfeld verschwindet. Dann wieder in Textfeld reingeklickt und von hinten Zahlen entfernt. Dann wird das Popup nicht wieder aufgeklappt.
Danke für den Tipp (was ist das denn für eine Sprache in dem verlinkten Artikel?). Aber AutoCompletion ist es ja eigentlich ja nicht, weil in der ComboBox nur die Bankinstitute stehen sollen (im Moment ist da nur zum Testen die BLZ noch mit eingetragen) und davon soll aus den Bank-Objekten im Model die BLZ genommen und in das Textfeld eingetragen werden.
Danke für den Tipp (was ist das denn für eine Sprache in dem verlinkten Artikel?). Aber AutoCompletion ist es ja eigentlich ja nicht, weil in der ComboBox nur die Bankinstitute stehen sollen (im Moment ist da nur zum Testen die BLZ noch mit eingetragen) und davon soll aus den Bank-Objekten im Model die BLZ genommen und in das Textfeld eingetragen werden.
Keine Ahnung hab mit nur den Code angeschaut und dachte könnte weiterhelfen
Ich kenne es halt von SWT/JFace da kannst ein Filter setzen und Autocompletion.
Das Autocompletion ist eigentlich ein sofort filtern. Du trägst den 1. Buchstaben ein und es geht eine Liste mit allen Möglichkeiten gefiltert zu diesem Buchstaben auf. Umso mehr Buchstaben man einträgt umso weniger Möglichkeiten gibt es. Dachte könnte für deinen Fall auch zutreffen.
Klar wenn du nur read only Combobox willst, ist vielleicht nicht so hilfreich
Na ja, wie schon geschrieben, ich hab in meinem ComboBoxModel Objekte der Klasse Bank (id, blz und bankinstitut). Diese Daten kommen aus einern Datenbank. Angezeigt werden sollen in der ComboBox nur die Bankinstitute, gefiltert durch die Eingabe der BLZ in das Textfeld. Bei Klick auf einen Bank-Eintrag in der ComboBox, soll die dazugehörige BLZ in das Textfeld geschrieben werden. Die ComboBox selbst ist nur readonly.
EDIT: Und das ganze soll dann in einem Formular für einen Personenverwaltung eingebunden sein. D.h. wenn ich abspeichere, wird dann die ID der Bank gespeichert. Deshalb die Bank-Objekte im Model.
Ja aber so ist deine Combox nur für diesen einen Fall anwendbar. Komponenten sollten wiederverwendbar sein, darum solltest du deine DB Sachen von außerhalb regeln.
Sowas: DBHandlerBank dbHandlerBank = new DBHandlerBank(); hat doch in der JCombox nichts zu suchen
Um mal unterschiedliche Plattformen und Java-Versionen als Problemursache auszuschließen… Dieses Programm läuft bei mir perfekt (Ubuntu auf KDE, Sun Java 1.6.0_26 64bit):
Java:
/* (@)JComboBoxFilterFun.java */
/* Copyright 2012 Sebastian Haufe
* Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
[url]http://www.apache.org/licenses/LICENSE-2.0[/url]
* Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. */
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
/**
* Test auto-popup with text field filter.
*
* @author Sebastian Haufe
*/
public class JComboBoxFilterFun {
/** Creates the GUI. Call on EDT, only! */
static void createAndShowGui() {
final List<String> data = new ArrayList<String>();
data.add("BBBBB");
data.add("ABBBB");
data.add("AABBB");
data.add("AAABB");
data.add("AAAAB");
data.add("AAAAA");
final JTextField tf = new JTextField(20);
final JComboBox cb = new JComboBox(data.toArray(new String[data.size()]));
tf.getDocument().addDocumentListener(new DocumentListener() {
private boolean processingEvent;
public void removeUpdate(DocumentEvent e) {
updateFilter(e.getDocument());
}
public void insertUpdate(DocumentEvent e) {
updateFilter(e.getDocument());
}
public void changedUpdate(DocumentEvent e) {
updateFilter(e.getDocument());
}
private void updateFilter(Document document) {
if (!processingEvent) {
try {
processingEvent = true;
final int len = document.getLength();
final String s = document.getText(0, len);
final List<String> filteredData = new ArrayList<String>();
for (String string : data) {
if (string.startsWith(s)) {
filteredData.add(string);
}
}
cb.setModel(new DefaultComboBoxModel(filteredData
.toArray(new String[filteredData.size()])));
cb.showPopup();
} catch (BadLocationException ex) {
ex.printStackTrace();
} finally {
processingEvent = false;
}
}
}
});
cb.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
tf.setText((String) ((JComboBox) e.getSource()).getSelectedItem());
}
});
final JPanel contentPane = new JPanel(new BorderLayout(6, 6));
contentPane.add(tf, BorderLayout.NORTH);
contentPane.add(cb, BorderLayout.CENTER);
final JFrame f = new JFrame("Test Frame: JComboBoxFilterFun"); //$NON-NLS-1$
f.setContentPane(contentPane);
f.pack();
f.setLocationRelativeTo(null);
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.setVisible(true);
}
/** @param args ignored */
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
Ja aber so ist deine Combox nur für diesen einen Fall anwendbar. Komponenten sollten wiederverwendbar sein, darum solltest du deine DB Sachen von außerhalb regeln.
Sowas: DBHandlerBank dbHandlerBank = new DBHandlerBank(); hat doch in der JCombox nichts zu suchen
Ja, da hast du wohl recht. Ich wollte auch erst mal nur diese ComboBox für diesen speziellen Fall erstellen. Deshalb hab ich das Füllen aus der Datenbank da einfach direkt reingeschrieben. Vielleicht entwickle ich das dann noch weiter, wenn es mal richtig läuft...
Um mal unterschiedliche Plattformen und Java-Versionen als Problemursache auszuschließen… Dieses Programm läuft bei mir perfekt (Ubuntu auf KDE, Sun Java 1.6.0_26 64bit).
Bei Dir auch?s
Ja würde das orginal Model nehmen wo die gefilterten Daten drin sind und noch ne ArrayListe wo alle Daten drin sind.
dann muss halt bei insertElement usw. die filter aufrufen und wenn true mitkommt die super methode aufrufen ansonsten nichts. Je nachdem musst halt wissen was du willst bezüglich event handling usw.
Es gibt von swingx auch schon ein filter mechanisum für eine JList eventuell kannst da was abschauen
OK, ich hab jetzt also in meinem MainPanel eine normale JComboBox, welche das FilterComboBoxModel verpasst bekommt. In diesem Model hole ich mir aus der Datenbank die Liste mit den Bankdaten. Diese befinden sich in einer ArrayList<Bank> mit Bank (id, blz, bankinstitut). Diese Elemente füge ich einfach dem Model hinzu und sie werden dann in der ComboBox angezeigt. Aber wie wende ich nun den Filter an? Ich hab ja an meinem Textfeld den DocumentListener mit den Methoden insertUpdate und removeUpdate. Vorher hatte ich im MainPanel die Filtermethode, welche den String aus dem Textfeld geholt und an die ComboBox zum Filtern weitergeleitet hat. Nun muss das ja über das FilterComboBoxModel laufen. Aber wie? Du hattest da ja die Methode addFilter und das Interface ComboboxFilter geschrieben. Kannst du mir erklären, wie ichdas da nun filtern kann?
Du das waren nur ein paar Grundideen wie du deinen Code strukturieren könntest, wie gesagt zum Filtern gibt es schon fertigen Code für die JXList ich würde mir einfach das mal anschauen und adaptieren. Ich denke die swingx Leute haben sich schon genügend Gedanken zu diesem Thema gemacht.
Du das waren nur ein paar Grundideen wie du deinen Code strukturieren könntest, wie gesagt zum Filtern gibt es schon fertigen Code für die JXList ich würde mir einfach das mal anschauen und adaptieren. Ich denke die swingx Leute haben sich schon genügend Gedanken zu diesem Thema gemacht.
OK, danke. Das mit dem Filtern klappt ja jetzt so einigermassen. Hab's jetzt mal noch ein bisschen verbessert, aber die Daten aus der Datenbank müssten evtl. noch in eine externe Model-Klasse...
Ebenius, ich hab jetzt mal meinen Code an dein Beispiel angepasst. Das vorherige grafische Problem mit der abgeschnittenen aufgeklappten Liste ist nun verschwunden. Mir ist aber ein weiteres Problemchen aufgefallen. Und zwar kommt es öfters vor, dass es in der Liste mehrere Banken mit den gleichen BLZ gibt (wahrscheinlich die gleiche Bank in unterschiedlichen Ortsteilen). Beim Filtern kein Problem. Es zeigt mir in der gefilterten Liste alle Bankinstitute an, welche diese BLZ haben. Nur wenn ich dann auf eine dieser Bankinstitute, die in der ComboBox angezeigt werden, klicke, wird nicht dieses bestimmte Bankinstitut in der in der ComboBox angezeigt, sondern immer das erste aus der gefilterten Liste. Vermutlich liegt es daran, dass die Auswahl des Bankinstituts die BLZ ins Textfeld reinschreibt und dies eine neue Filterung auslöst, also das Model neu erstellt und gesetzt wird und automatisch das erste Element ausgewählt wird. Ich dachte ja, dass dies mit dem processingEvent verhindert wird? Vielleicht weisst du, wie ich auch das Bankinstitut angezeigt bekomme, welches ich in der Liste auswähle.
Ich kopiere hier jetzt nochmal meine beiden Klassen MainPanel und den DocumentListener rein:
Java:
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import javax.swing.JComboBox;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.text.AbstractDocument;
@SuppressWarnings("serial")
public class MainPanel
extends JPanel{
private JComboBox comboBank;
private JTextField tfBLZ;
public MainPanel(){
// Textfeld für Eingabe der Bankleitzahlen
tfBLZ = new JTextField();
tfBLZ.setPreferredSize(new Dimension(100, 24));
// Holt die Liste der Bankinstitute und BLZ aus der Datenbank
List<Bank> bankenListe = new DBHandlerBank().getBankinstitute();
// ComboBox zur Anzeige der Bankinstitute
comboBank = new JComboBox(bankenListe.toArray(new Bank[bankenListe.size()]));
comboBank.setPreferredSize(new Dimension(350, 24));
// DocumentListener reagiert auf Änderungen im Textfeld
tfBLZ.getDocument().addDocumentListener(new BLZDocumentListener(comboBank, bankenListe));
// DocumentFilter für Textfeld (max. 8 Zeichen, nur Zahlen von 0-9)
((AbstractDocument)tfBLZ.getDocument()).setDocumentFilter(new DocumentSizeFilter());
comboBank.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
tfBLZ.setText(((Bank) ((JComboBox) e.getSource()).getSelectedItem()).getBlz());
}
});
this.add(tfBLZ);
this.add(comboBank);
}
}
Java:
import java.util.ArrayList;
import java.util.List;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
public class BLZDocumentListener
implements DocumentListener {
private boolean processingEvent;
private JComboBox comboBank;
private List<Bank> bankenListe;
public BLZDocumentListener(JComboBox comboBank, List<Bank> bankenListe) {
this.comboBank = comboBank;
this.bankenListe = bankenListe;
}
// Text wird zu einem Textfeld hinzugefügt.
public void insertUpdate(DocumentEvent e) {
filter(e.getDocument());
}
// Text wird von einem Textfeld entfernt.
public void removeUpdate(DocumentEvent e) {
filter(e.getDocument());
}
// Gives notification that an attribute or set of attributes changed.
public void changedUpdate(DocumentEvent e) {
filter(e.getDocument());
}
// gefiltertes Model wird zusammengestellt und gesetzt
public void filter(Document document) {
if (!processingEvent) {
try {
processingEvent = true;
final int len = document.getLength();
final String s = document.getText(0, len);
final List<Bank> filteredData = new ArrayList<Bank>();
for (Bank bank : bankenListe) {
if (bank.getBlz().startsWith(s)) {
filteredData.add(bank);
}
}
comboBank.setModel(new DefaultComboBoxModel(filteredData
.toArray(new Bank[filteredData.size()])));
comboBank.showPopup();
} catch (BadLocationException ex) {
ex.printStackTrace();
} finally {
processingEvent = false;
}
}
}
}
import java.util.ArrayList;
import java.util.List;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
public class BLZDocumentListener
implements DocumentListener {
private boolean processingEvent;
private JComboBox comboBank;
private List<Bank> bankenListe;
public BLZDocumentListener(JComboBox comboBank, List<Bank> bankenListe) {
this.comboBank = comboBank;
this.bankenListe = bankenListe;
}
// Text wird zu einem Textfeld hinzugefügt.
public void insertUpdate(DocumentEvent e) {
filter(e.getDocument());
}
// Text wird von einem Textfeld entfernt.
public void removeUpdate(DocumentEvent e) {
filter(e.getDocument());
}
// Gives notification that an attribute or set of attributes changed.
public void changedUpdate(DocumentEvent e) {
filter(e.getDocument());
}
// gefiltertes Model wird zusammengestellt und gesetzt
public void filter(Document document) {
if (!processingEvent) {
try {
processingEvent = true;
final Object selection = comboBank.getSelectedItem();
final int len = document.getLength();
final String s = document.getText(0, len);
final List<Bank> filteredData = new ArrayList<Bank>();
boolean selectionFound = false;
for (Bank bank : bankenListe) {
if (bank.getBlz().startsWith(s)) {
if (selection != null && selection.equals(bank.getBlz())) {
selectionFound = true;
}
filteredData.add(bank);
}
}
comboBank.setModel(new DefaultComboBoxModel(filteredData
.toArray(new Bank[filteredData.size()])));
if (selectionFound) {
comboBank.setSelectedItem(selected);
}
comboBank.showPopup();
} catch (BadLocationException ex) {
ex.printStackTrace();
} finally {
processingEvent = false;
}
}
}
}
[SIZE="-1"]Code im Browser getippt und ungetestet.[/SIZE]
Schade. Es klappt leider immer noch nicht. Es wird bei einer Liste mit gleichen BLZ immer noch das erste Bankinstitut in der LIste in der ComboBox angezeigt, auch wenn ich ein anderes aus der Liste auswähle.
Ich bin mir nicht sicher, ob das eine Möglichkeit wäre, aber vielleicht könnte man das Filtern im Textfeld abstellen, wenn die ComboBox den Focus bekommen hat, also wenn ich dort ein Bankinstitut auswähle. Und das Filtern wird nur aktiviert, wenn das Textfeld den Focus hat. Hmm, werde ich mal testen, ob das so gehen könnte. Weil ich denke, dass durch das Auswählen aus der ComboBox die BLZ des Bankinstitutes in das Textfeld eingetragen wird und das Filtern damit erneut ausgelöst wird. Der ComboBox wird ein neues Model hinzugefügt und bei mehreren Einträgen steht die Selektion automatisch immer ganz oben.
Mach Dir einfach mal ein paar Debug-Ausgaben in die Event-Handler. Dann wird's bestimmt klarer. Oder setz Dir Break Points und debugge selber, je nachdem was Dir einfacher erscheint.
OK, ich hab jetzt mal Testausgaben im ActionListener gemacht, der auf die ComboBox hört und dann den BLZ-String in das Textfeld einfügt. Dort wird die richtige (also angeklickte Bank) in der Konsole angezeigt. Aber die Anzeige in der wieder zusammengeklappten ComboBox bleibt trotzdem der erste Eintrag der gefilterten Liste, wenn es mehrere Banken mit der gleichen BLZ gibt. Wenn ich nun vor dem Setzen des BLZ-Strings in das Textfeld den DocumentListener vom Textfeld entferne (also das Filtern sozusagen ausschalte), dann klappt es und es wird auch in der ComboBox das ausgewählte Bankinstitut angezeigt. Also, hängt es wahrscheinlich doch damit zusammen, dass das Setzen des BLZ-Strings im Textfeld ein erneutes Filtern durch den DocumentListener auslöst, das Model gefiltert und neu gesetzt und der erste Eintrag (bei mehreren Einträgen) selektiert wird. Leider ist dann nach dem Klick auf einen Eintrag im Popup der ComboBox der Listener deaktiviert und es kann nicht mehr gefiltert werden. Aber zumindest weiss ich schonmal, woran es wohl liegt. Werde mich dann morgen mal dran machen, dazu eine Lösung zu finden.
Hier nochmal die abgeänderte Version der Klasse MainPanel:
Java:
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import javax.swing.JComboBox;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.text.AbstractDocument;
@SuppressWarnings("serial")
public class MainPanel
extends JPanel{
private JComboBox comboBank;
private JTextField tfBLZ;
public MainPanel(){
// Textfeld für Eingabe der Bankleitzahlen
tfBLZ = new JTextField();
tfBLZ.setPreferredSize(new Dimension(100, 24));
// Holt die Liste der Bankinstitute und BLZ aus der Datenbank
List<Bank> bankenListe = new DBHandlerBank().getBankinstitute();
// ComboBox zur Anzeige der Bankinstitute
comboBank = new JComboBox(bankenListe.toArray(new Bank[bankenListe.size()]));
comboBank.setPreferredSize(new Dimension(350, 24));
final BLZDocumentListener blzDocumentListener = new BLZDocumentListener(comboBank, bankenListe);
// DocumentListener reagiert auf Änderungen im Textfeld
tfBLZ.getDocument().addDocumentListener(blzDocumentListener);
// DocumentFilter für Textfeld (max. 8 Zeichen, nur Zahlen von 0-9)
((AbstractDocument)tfBLZ.getDocument()).setDocumentFilter(new DocumentSizeFilter());
comboBank.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Bank bank = ((Bank) ((JComboBox) e.getSource()).getSelectedItem());
System.out.println("ID: " + bank.getBankID() + " - BLZ: " + bank.getBlz() + " - Bankinstitut: " + bank.getInstitut());
tfBLZ.getDocument().removeDocumentListener(blzDocumentListener);
tfBLZ.setText(((Bank) ((JComboBox) e.getSource()).getSelectedItem()).getBlz());
}
});
this.add(tfBLZ);
this.add(comboBank);
}
}
Evtl. hab ich jetzt die Lösung gefunden. Scheint jedenfalls zu funktionieren. Und zwar setze ich in dem ActionListener in meinem MainPanel die boolean Variable processingEvent (DocumentListener) auf true, setze dann den angeklickten Wert in das Textfeld und danach gleich wieder den Wert auf false. So wird das Filtern ausgeschaltet, während die BLZ ins Textfeld geschreiben wird, und danach wieder eingeschaltet. Dazu hab ich in dem DocumentListener eine Methode setProcessingEvent eingefügt, welche diesen Wert ändern kann. Hier nochmal die beiden Klassen MainPanel (Textfeld und ComboBox) und BLZDocumentListener (reagiert auf Eingaben im Textfeld und filtert die Einträge in der ComboBox):
Java:
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import javax.swing.JComboBox;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.text.AbstractDocument;
@SuppressWarnings("serial")
public class MainPanel
extends JPanel{
private JComboBox comboBank;
private JTextField tfBLZ;
public MainPanel(){
// Textfeld für Eingabe der Bankleitzahlen
tfBLZ = new JTextField();
tfBLZ.setPreferredSize(new Dimension(100, 24));
// Holt die Liste der Bankinstitute und BLZ aus der Datenbank
List<Bank> bankenListe = new DBHandlerBank().getBankinstitute();
// ComboBox zur Anzeige der Bankinstitute
comboBank = new JComboBox(bankenListe.toArray(new Bank[bankenListe.size()]));
comboBank.setPreferredSize(new Dimension(350, 24));
// DocumentListener reagiert auf Änderungen im Textfeld
final BLZDocumentListener blzDocumentListener = new BLZDocumentListener(comboBank, bankenListe);
// DocumentListener dem Textfeld hinzufügen
tfBLZ.getDocument().addDocumentListener(blzDocumentListener);
// DocumentFilter für Textfeld (max. 8 Zeichen, nur Zahlen von 0-9)
((AbstractDocument)tfBLZ.getDocument()).setDocumentFilter(new DocumentSizeFilter());
comboBank.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Bank bank = ((Bank) ((JComboBox) e.getSource()).getSelectedItem());
// Filtern abschalten
blzDocumentListener.setProcessingEvent(true);
// angeklickter Eintrag: BLZ ins Textfeld schreiben
tfBLZ.setText(((Bank) ((JComboBox) e.getSource()).getSelectedItem()).getBlz());
// Filtern wieder aktivieren
blzDocumentListener.setProcessingEvent(false);
}
});
this.add(tfBLZ);
this.add(comboBank);
}
}
Java:
import java.util.ArrayList;
import java.util.List;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
public class BLZDocumentListener
implements DocumentListener {
private boolean processingEvent;
private JComboBox comboBank;
private List<Bank> bankenListe;
public BLZDocumentListener(JComboBox comboBank, List<Bank> bankenListe) {
this.comboBank = comboBank;
this.bankenListe = bankenListe;
}
// verändert Wert zum An- oder Abschalten des Filterns
public void setProcessingEvent(boolean pe) {
this.processingEvent = pe;
}
// Text wird zu einem Textfeld hinzugefügt.
public void insertUpdate(DocumentEvent e) {
filter(e.getDocument());
}
// Text wird von einem Textfeld entfernt.
public void removeUpdate(DocumentEvent e) {
filter(e.getDocument());
}
// Gives notification that an attribute or set of attributes changed.
public void changedUpdate(DocumentEvent e) {
filter(e.getDocument());
}
// gefiltertes Model wird zusammengestellt und gesetzt
public void filter(Document document) {
if (!processingEvent) {
try {
processingEvent = true;
final int len = document.getLength();
final String s = document.getText(0, len);
final List<Bank> filteredData = new ArrayList<Bank>();
for (Bank bank : bankenListe) {
if (bank.getBlz().startsWith(s)) {
filteredData.add(bank);
}
}
comboBank.setModel(new DefaultComboBoxModel(filteredData
.toArray(new Bank[filteredData.size()])));
comboBank.showPopup();
} catch (BadLocationException ex) {
ex.printStackTrace();
} finally {
processingEvent = false;
}
}
}
}