{"id":1001,"date":"2011-06-20T16:25:24","date_gmt":"2011-06-20T14:25:24","guid":{"rendered":"http:\/\/www.tutego.de\/blog\/javainsel\/2011\/06\/jcomboxbox-mit-separator-nutzen\/"},"modified":"2011-06-20T16:25:24","modified_gmt":"2011-06-20T14:25:24","slug":"jcomboxbox-mit-separator-nutzen","status":"publish","type":"post","link":"https:\/\/www.tutego.de\/blog\/javainsel\/2011\/06\/jcomboxbox-mit-separator-nutzen\/","title":{"rendered":"JComboxBox mit Separator nutzen"},"content":{"rendered":"<p>Standardm\u00e4\u00dfig unterst\u00fctzt die JList und auch JComboBox keine Separatoren, doch die Unterteilung in Segmente ist in der Praxis sehr n\u00fctzlich. Microsoft Word und PowerPoint benutzt sie zum Beispiel, um die zuletzt vom Benutzer ausgew\u00e4hlten Zeichens\u00e4tze prominent oben in der Liste zu haben (Excel dagegen nicht).<\/p>\n<p>Wir wollen diese M\u00f6glichkeit nachbilden, und dabei noch einiges \u00fcber Modelle und Renderer lernen.<\/p>\n<p><!--more--><\/p>\n<p>Bei der Umsetzung gibt es unterschiedliche Varianten, die sich neben der technischen Implementierung darin unterscheiden, ob das Modell eine Markierung f\u00fcr den Separator enth\u00e4lt oder nicht. Wir stellen beide Ans\u00e4tze vor und beginnen mit der ersten Variante, einem Zellen-Renderer Positionen mitzugeben, die sagen, wo ein Trennstrich zu zeichnen ist.<\/p>\n<p>JFrame frame = new JFrame();<\/p>\n<p>frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );<\/p>\n<p>String[] items = { &quot;Cambria&quot;, &quot;Arial&quot;, &quot;Verdana&quot;, &quot;Times&quot; };<\/p>\n<p>JComboBox&lt;String&gt; comboBox = new JComboBox&lt;String&gt;( items );<\/p>\n<p><b>ListCellRenderer&lt;String&gt; renderer = new SeparatorAwareListCellRenderer1&lt;String&gt;(<\/b><\/p>\n<p><b>comboBox.getRenderer(), 0 );<\/b><\/p>\n<p><b>comboBox.setRenderer( renderer );<\/b><\/p>\n<p>frame.add( comboBox );<\/p>\n<p>frame.pack();<\/p>\n<p>frame.setVisible( true );<\/p>\n<p>Die eigene Klasse SeparatorAwareListCellRenderer1 ist ein ListCellRenderer, den die JComboBox zur Darstellung der Komponenten nutzt. Im Konstruktor des Renderes geben wir den Original-Renderer mit \u2013 es kann ein bestimmter Renderer schon vorinstalliert sein, den wollen wir dekorieren \u2013 und ein variable Argumentliste von Positionen. Das Beispiel \u00fcbergibt nur 0, da nach dem ersten Element (Index = 0) ein Trennzeichen zu setzen sein soll, sodass Cambria und Arial abgetrennt sind.<\/p>\n<p>package com.tutego.insel.ui.list;<\/p>\n<p>import java.awt.*;<\/p>\n<p>import java.util.Arrays;<\/p>\n<p>import javax.swing.*;<\/p>\n<p>public class SeparatorAwareListCellRenderer1&lt;E&gt; implements ListCellRenderer&lt;E&gt;<\/p>\n<p>{<\/p>\n<p>private final ListCellRenderer&lt;? super E&gt; delegate;<\/p>\n<p>private final int[] indexes;<\/p>\n<p>private final JPanel panel = new JPanel( new BorderLayout() );<\/p>\n<p>public SeparatorAwareListCellRenderer1( ListCellRenderer&lt;? super E&gt; delegate, int&#8230; indexes )<\/p>\n<p>{<\/p>\n<p>Arrays.sort( indexes );<\/p>\n<p>this.delegate = delegate;<\/p>\n<p>this.indexes = indexes;<\/p>\n<p>}<\/p>\n<p>@SuppressWarnings({ &quot;rawtypes&quot;, &quot;unchecked&quot; })<\/p>\n<p>@Override<\/p>\n<p>public Component getListCellRendererComponent( JList list, E value,<\/p>\n<p>int index, boolean isSelected,<\/p>\n<p>boolean cellHasFocus )<\/p>\n<p>{<\/p>\n<p><b>panel.removeAll();<\/b><\/p>\n<p><b>panel.add( delegate.getListCellRendererComponent( list, value, index,<\/b><\/p>\n<p><b>isSelected, cellHasFocus ) );<\/b><\/p>\n<p><b>if ( Arrays.binarySearch( indexes, index ) &gt;= 0 )<\/b><\/p>\n<p><b>panel.add( new JSeparator(), BorderLayout.PAGE_END );<\/b><\/p>\n<p><b><\/b><\/p>\n<p><b>return panel;<\/b><\/p>\n<p>}<\/p>\n<p>}<\/p>\n<p>Die Implementierung basiert auf der Idee, jede Komponenten in einen Container (JPanel) zu setzen und in diesem Container dann je nach Bedarf ein JSeparator mit unten dran zu setzen. Statt dem JPanel mit einem JSeparator auszustatten kann ebenfalls auch ein Border unten gezeichnet werden. Die Anweisung Arrays.binarySearch(indexes, index) &gt;= 0 ist als \u201econtains\u201c zu verstehen, also ein Test, ob der Index im Feld ist \u2013 leider gibt es so eine Methode nicht in der Java-API. Wenn der Index im Feld ist, soll der Separator unter der Komponente erscheinen \u2013 dass sich eine Trennlinie auch am Anfang befinden kann ber\u00fccksichtigt die L\u00f6sung nicht, und bleibt als \u00dcbungsaufgabe f\u00fcr die Leser.<\/p>\n<p>Diese L\u00f6sung ist einfach, und funktioniert gut, denn vorhandene Renderer werden weiterverwendet, was sehr wichtig ist, denn gr\u00f6\u00dferen Swing-Anwendungen nutzen viele eigene Renderer, etwa um Icons und Text zusammenzufassen. Ein Nachteil ist, dass der Separator zu einen Element geh\u00f6rt, und wenn das Element etwa in der Liste ausgew\u00e4hlt wird, steht der Separator mit in der Selektion und ist nicht abgetrennt (das ist aber bei Word genauso).<\/p>\n<p>W\u00e4hrend die vorgestellte Variante in der Praxis gut funktioniert, wollen wir uns noch mit einer alternativen Umsetzung besch\u00e4ftigen. Sie ist deutlicher komplexer und auch nicht so flexibel. Die L\u00f6sung basiert auf der Idee, dass die Modelldaten eine Markierung f\u00fcr den Separator enthalten \u2013 die folgende L\u00f6sung nutzt null daf\u00fcr. Da JList oder JComboBox eine null problemlos vertr\u00e4gt, ist die Basis schnell umgesetzt:<\/p>\n<p>JFrame frame = new JFrame();<\/p>\n<p>frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );<\/p>\n<p>String[] items = { &quot;Cambria&quot;, null, &quot;Arial&quot;, &quot;Cambria&quot;, &quot;Verdana&quot;, &quot;Times&quot; };<\/p>\n<p>JComboBox&lt;String&gt; comboBox = new JComboBox&lt;String&gt;( items );<\/p>\n<p>frame.add( comboBox );<\/p>\n<p>frame.pack();<\/p>\n<p>frame.setVisible( true );<\/p>\n<p>Das Programm zum Start gebracht zeigt dort, wie das Model eine null liefert einfach nichts an. Die Selektion dieses null-Elements ist auch m\u00f6glich und f\u00fchrt zu keiner Ausnahme.<\/p>\n<p>Damit Swing das null-Element nicht als Leereintrag anzeigt, verpassen wir unserer JCombBox einen Zellen-Renderer. Der soll immer dann, wenn das Element null ist, ein Exemplar von JSeparator zeichnen. Vom Design ist es das beste, wenn der Zell-Renderer selbst die null-Elemente darstellt, aber das Zeichnen der nicht-null-Elemente an einen anderen Zell-Renderer abgibt, satt diese etwas durch einen BasicComboBoxRenderer (ein JLabel was ListCellRenderer implementiert) selbst zu renderen \u2013 das w\u00fcrde die Flexibilit\u00e4t der L\u00f6sung massiv einschr\u00e4nken.<\/p>\n<p>package com.tutego.insel.ui.list;<\/p>\n<p>import java.awt.Component;<\/p>\n<p>import javax.swing.*;<\/p>\n<p>public class SeparatorAwareListCellRenderer2&lt;E&gt; implements ListCellRenderer&lt;E&gt;<\/p>\n<p>{<\/p>\n<p>private final ListCellRenderer&lt;? super E&gt; delegate;<\/p>\n<p>public SeparatorAwareListCellRenderer2( ListCellRenderer&lt;? super E&gt; delegate )<\/p>\n<p>{<\/p>\n<p>this.delegate = delegate;<\/p>\n<p>}<\/p>\n<p>@SuppressWarnings({ &quot;rawtypes&quot;, &quot;unchecked&quot; })<\/p>\n<p>@Override<\/p>\n<p>public Component getListCellRendererComponent( JList list, E value,<\/p>\n<p>int index, boolean isSelected,<\/p>\n<p>boolean cellHasFocus )<\/p>\n<p>{<\/p>\n<p><b>if ( value == null )<\/b><\/p>\n<p><b>return new JSeparator();<\/b><\/p>\n<p><b><\/b><\/p>\n<p><b>return delegate.getListCellRendererComponent( list, value, index,<\/b><\/p>\n<p><b>isSelected, cellHasFocus );<\/b><\/p>\n<p>}<\/p>\n<p>}<\/p>\n<p>Die eigene Klasse SeparatorAwareListCellRenderer2 bekommt einen anderen ListCellRenderer \u00fcbergeben und liefert die Komponenten dieses Delegate-Renders immer dann, wenn das Element ungleich null ist. Ist es dagegen gleich null, liefert getListCellRendererComponent() ein Exemplar des JSeparators.<\/p>\n<p>Im Beispielprogramm muss der Renderer nun angemeldet werden und dazu ist zu erg\u00e4nzen:<\/p>\n<p>comboBox.setRenderer(<\/p>\n<p>new SeparatorAwareListCellRenderer2&lt;String&gt;(comboBox.getRenderer()) );<\/p>\n<p>Nach dem Start des Programms ist das Ergebnis schon viel Besser: dort wo vorher die JComboBox eine leere Zeile darstellte, ist nun ein Strich.<\/p>\n<p>Die L\u00f6sung ist jedoch nur ein Etappensieg denn die Navigation mit der Tastatur durch die Liste zeigt eine Schwachstelle: Das null-Element l\u00e4sst sich ausw\u00e4hlen und erscheint auch als Linie im Editor\/Textfeld. Beheben l\u00e4sst sich das Problem nicht mit dem Renderer, denn der ist nur f\u00fcr die Darstellung in der Liste beauftragt. Hier muss in die Interna der Swing-Komponente eingegriffen werden. Jede Swing-Komponente hat ein korrespondierende UI-Delegate, das f\u00fcr das Verhalten und Darstellung der Komponente verantwortlich ist. F\u00fcr die JComboBox sind das Unterklassen von ComboBoxUI und zwei Methoden sind besonders interessant: selectNextPossibleValue() und selectPreviousPossibleValue(). <\/p>\n<p>Da jede UI-Implementierung ihre eines Look &amp; Feel mitbringt, m\u00fcssen wir hier eigentlichen einen Dekorator bauen und jede Methode bis auf die beiden genannten an das Original weiterleiten, doch das ist jetzt zu viel Arbeit und so nehmen wir die Basisklasse WindowsComboBoxUI als Basisklasse, denn unter Beispiel nutzt das Windows LaF. In der Unterklasse implementieren wir eigene Versionen der Methoden selectNextPossibleValue() und selectPreviousPossibleValue(), die so lange die Liste nach oben\/unten laufen m\u00fcssen, bis sie ein Element ungleich null finden.<\/p>\n<p>Listing 1.34: com\/tutego\/insel\/ui\/list\/SeparatorAwareComboBoxUI.java<\/p>\n<p>package com.tutego.insel.ui.list;<\/p>\n<p>import com.sun.java.swing.plaf.windows.WindowsComboBoxUI;<\/p>\n<p>public class SeparatorAwareComboBoxUI extends WindowsComboBoxUI <\/p>\n<p>{<\/p>\n<p>@Override<\/p>\n<p>protected <b>void selectNextPossibleValue()<\/b><\/p>\n<p>{<\/p>\n<p>for ( int index = comboBox.getSelectedIndex() + 1;<\/p>\n<p>index &lt; comboBox.getItemCount();<\/p>\n<p>index++ )<\/p>\n<p>if ( comboBox.getItemAt( index ) != null )<\/p>\n<p>{<\/p>\n<p>comboBox.setSelectedIndex( index );<\/p>\n<p>break;<\/p>\n<p>}<\/p>\n<p>}<\/p>\n<p>@Override<\/p>\n<p>protected <b>void selectPreviousPossibleValue()<\/b><\/p>\n<p>{<\/p>\n<p>for ( int index = comboBox.getSelectedIndex() &#8211; 1;<\/p>\n<p>index &gt;= 0;<\/p>\n<p>index&#8211; )<\/p>\n<p>if ( comboBox.getItemAt( index ) != null )<\/p>\n<p>{<\/p>\n<p>comboBox.setSelectedIndex( index );<\/p>\n<p>break;<\/p>\n<p>}<\/p>\n<p>}<\/p>\n<p>}<\/p>\n<p>Das UI-Objekt muss ebenfalls angemeldet werden:<\/p>\n<p>comboBox.setUI( new SeparatorAwareComboBoxUI() );<\/p>\n<p>Enth\u00e4lt die Liste null-Elemente \u00fcberspringt die Tastennavigation \u00fcber die Cursor-Taste diese. Doch auch mit diesem Teilst\u00fcck fehlt ein weitere Detail: Mit feinem Klick l\u00e4sst sich die Linie doch noch ausw\u00e4hlen. Das ist keine Frage des Renderes und auch keine Frage der Tastaturnavigation \u2013 es muss untersagt werden, dass bei der Aktivierung ein null-Element in zum Editor kommen kann. Hier ist Methode setSelectedItem() von JComboBox entscheidend. Denn jedes Element was selektiert wird \u2013 und dadurch auch in der Textfeld kommt \u2013 geht durch die Methode durch. Wenn wir die Methode \u00fcberschreiben, und bei null-Elemente einfach nichts tun, wird auch das null-Element nicht selektiert und im Textfeld bleibt das letzte Element.<\/p>\n<p>Damit auch spezielle Implementierungen von JComboBox von diesem Verhalten profitieren k\u00f6nnten, m\u00fcssen wir wieder einen Dekorator schreiben, doch das kostet zu viel M\u00fche, und so \u00fcberschreibt eine einfache Unterklasse die setSelectedItem()-Methode. (Im Prinzip w\u00e4re auch eine \u00fcberschiebene Methoden von setSelectedIndex() sinnvoll, denn das k\u00f6nnte eine programmierte Aktivierung der null vermeiden.)<\/p>\n<p>package com.tutego.insel.ui.list;<\/p>\n<p>import javax.swing.*;<\/p>\n<p>public class SeparatorAwareJComboBox&lt;E&gt; extends JComboBox&lt;E&gt;<\/p>\n<p>{<\/p>\n<p>public SeparatorAwareJComboBox( E&#8230; items )<\/p>\n<p>{<\/p>\n<p>super( items );<\/p>\n<p>}<\/p>\n<p>@Override<\/p>\n<p><b>public void setSelectedItem( Object anObject )<\/b><\/p>\n<p><b>{<\/b><\/p>\n<p><b>if ( anObject != null )<\/b><\/p>\n<p><b>super.setSelectedItem( anObject );<\/b><\/p>\n<p><b>}<\/b><\/p>\n<p>}<\/p>\n<p>Im Hauptprogramm muss nur diese spezielle Klasse zu Einsatz kommen:<\/p>\n<p>JComboBox&lt;String&gt; comboBox = new SeparatorAwareJComboBox&lt;String&gt;( items );<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Standardm\u00e4\u00dfig unterst\u00fctzt die JList und auch JComboBox keine Separatoren, doch die Unterteilung in Segmente ist in der Praxis sehr n\u00fctzlich. Microsoft Word und PowerPoint benutzt sie zum Beispiel, um die zuletzt vom Benutzer ausgew\u00e4hlten Zeichens\u00e4tze prominent oben in der Liste zu haben (Excel dagegen nicht). Wir wollen diese M\u00f6glichkeit nachbilden, und dabei noch einiges \u00fcber [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":"","_links_to":"","_links_to_target":""},"categories":[11,10],"tags":[],"class_list":["post-1001","post","type-post","status-publish","format-standard","hentry","category-insel","category-swing"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts\/1001","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/comments?post=1001"}],"version-history":[{"count":0,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts\/1001\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/media?parent=1001"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/categories?post=1001"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/tags?post=1001"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}