Trädvy Permalänk
Medlem
Plats
Lund
Registrerad
Maj 2006

JComboBox gruppera val

Är det någon som vet hur man skapar en JComboBox i java som fungerar som denna
http://www.w3schools.com/tags/tryit.asp?filename=tryhtml_optg...

Vill alltså ha grupper som man inte kan klicka på.

Trädvy Permalänk
Medlem
Plats
Linköping
Registrerad
Mar 2008

Här är en hyfast enkel och lite småful lösning som kanske duger:

import java.awt.Component; import javax.swing.DefaultListCellRenderer; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.UIManager; class GroupMarker { private final String text; public GroupMarker(String text) { this.text = text; } @Override public String toString() { return text; } } class GroupComboRenderer extends DefaultListCellRenderer { @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { JLabel lbl = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); if (value instanceof GroupMarker) { lbl.setText("<html><b><i>" + value.toString() + "</i></b></html>"); } else { if (index == -1) { // Index är -1 om det är det valda elementet "högst upp" i combon. Då vill vi inte ha indraget. lbl.setText(value.toString()); } else { lbl.setText(" " + value.toString()); } } return lbl; } } public class GroupComboTest extends JFrame { public GroupComboTest() { setDefaultCloseOperation(EXIT_ON_CLOSE); Object[] comboData = { new GroupMarker("Frukter"), "Äpple", "Päron", "Banan", new GroupMarker("Grönsaker"), "Tomat", "Gurka", "Morot", new GroupMarker("Skillade snubbar"), "Donald Knuth", "Brian Goetz", "Mark Russinovich" }; JComboBox cb = new JComboBox(comboData); cb.setRenderer(new GroupComboRenderer()); add(cb); pack(); } public static void main(String[] args) throws Exception { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); new GroupComboTest().setVisible(true); } }

Principen är att vi skiljer på rena strängar i combo-listan och våra grupprubriker genom att göra grupprubrikerna till en annan klass. Vi sätter sedan en egen cellrenderare för combon, som i stort sett är densamma som defaulten en JComboBox får (en DefaultListCellRenderer), förutom att vi använder lite HTML för att styra upp utseendet på elementen och hjälpa oss skilja på grupprubrikerna och elementen.

För att veta om en användare har valt en grupprubrik eller inte kan du kolla om det valda elementet i combon har typen GroupMarker (genom instanceof GroupMarker). Skulle så vara fallet är valet felaktigt och du får styra upp det på det sätt du tycker är lämpligt (automatiskt välja ett giltigt alternativ uppåt eller neråt, eller strejka och spotta felmeddelanden i ansiktet på användaren).

EDIT: Den är inte lika snygg och bra som HTML-varianten du länkade, i och med att man här fortfarande kan välja grupprubrik-elementen. Det finns sätt runt det, men allt jag kommer på just nu är fulhack. Jag kanske återkommer om jag kommer på något snyggt.

Trädvy Permalänk
Medlem
Plats
Linköping
Registrerad
Mar 2008

Jag kom på ett sätt som är bara lite fulhackigt :), men som nog kan funka acceptabelt? Vi subklassar JComboBox som lyssnar efter händelsen att användaren väljer något - om vi inte gillar valet så återväljer vi det tidigare elementet som var valt.

Lösningen har sina begränsningar, men den kanske räcker för det du vill ha den till? Du får nog bryta ut GroupComboBox och GroupMarker till egna filer och göra dem publika för att kunna använda dem på ett smidigt sätt.

import java.awt.Component; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Vector; import javax.swing.DefaultListCellRenderer; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.UIManager; class GroupMarker { private final String text; public GroupMarker(String text) { this.text = text; } public String getText() { return text; } @Override public String toString() { return text; } } class GroupComboBox extends JComboBox { // Har renderarklassen som en privat medlemsklass, eftersom den är helt // ointressant för andra än GroupComboBox. private static class GroupComboRenderer extends DefaultListCellRenderer { @Override public Component getListCellRendererComponent( JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { JLabel lbl = (JLabel) super.getListCellRendererComponent( list, value, index, isSelected, cellHasFocus); if (value instanceof GroupMarker) { lbl = new JLabel(); // Fuling för att bli av med bakgrundsfärg etc lbl.setText("<html><b><i>" + value.toString() + "</i></b></html>"); } else { if (index == -1) { // Index är -1 om det är det valda elementet "högst upp" i combon. Då vill vi inte ha indraget. lbl.setText(value.toString()); } else { lbl.setText(" " + value.toString()); } } return lbl; } } private Object lastObject = null; public GroupComboBox() { this(new Object[]{" "}); } public GroupComboBox(Vector<?> data) { this(data.toArray()); } public GroupComboBox(Object[] data) { super(data); setRenderer(new GroupComboRenderer()); if (data != null && data.length > 0) { lastObject = data[0]; } // Se till att ingen GroupMarker är vald från början. for (Object o : data) { if (!(o instanceof GroupMarker)) { lastObject = o; break; } } addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Object o = getSelectedItem(); if (o instanceof GroupMarker) { setSelectedItem(lastObject); fireActionEvent(); } else { lastObject = o; } } }); } } public class GroupComboTest extends JFrame { public GroupComboTest() { setDefaultCloseOperation(EXIT_ON_CLOSE); Object[] comboData = { new GroupMarker("Frukter"), "Äpple", "Päron", "Banan", new GroupMarker("Grönsaker"), "Tomat", "Gurka", "Morot", new GroupMarker("Skillade snubbar"), "Donald Knuth", "Brian Goetz", "Mark Russinovich" }; GroupComboBox cb = new GroupComboBox(comboData); add(cb); pack(); } public static void main(String[] args) throws Exception { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); new GroupComboTest().setVisible(true); } }

Trädvy Permalänk
Medlem
Plats
Lund
Registrerad
Maj 2006

AHhhhhhhh!!!!

Helt lysande. Tusen tack. Har googlat hela jefvla dagen men inte hittat nån lösning som jag kunde förstå.

Hur påverkar html-koden prestandan tro? Och är det vänligt över olika plattformar?

Trädvy Permalänk
Medlem
Plats
Linköping
Registrerad
Mar 2008
Citat:

Ursprungligen inskrivet av stantemo
AHhhhhhhh!!!!

Helt lysande. Tusen tack. Har googlat hela jefvla dagen men inte hittat nån lösning som jag kunde förstå.

Hur påverkar html-koden prestandan tro? Och är det vänligt över olika plattformar?

Prestandan påverkas knappt alls. Du hade säkert märkt av det om du försökt rendera tusentals JLabels i sekunden, men det lär du aldrig vara i närheten av.

Det är inget här som är plattformsberoende heller. Dock bör man ha i åtanke att vissa look-and-feels har som standard att JLabels (som standardelementen i en JComboBox är) är fetstilta från början. T.ex. är standard-look-and-feel:en för Java (Java LAF även kallad Metal) gjord så. Detta skulle medföra att GroupMarker-elementen och de "riktiga" elementen, strängarna, alla skulle vara fetstilta. Och vad jag vet finns ingen html-tag som gör nånting "ofett"?

Man kan ju på olika sätt försöka tvinga comboboxar att alltid rendera "smalstilt", så att även Javas standard-LAF följer det. Trodde jag iaf... Jag bet i gräset när jag försökte på min sida här och vet inte riktigt vad jag gjort för fel. Jag kanske återkommer...

BTW, ändra for-loopen i konstruktorn till

// Se till att ingen GroupMarker är vald från början. for (Object o : data) { if (!(o instanceof GroupMarker)) { lastObject = o; setSelectedItem(o); fireActionEvent(); break; } }

...så funkar den som den ska också.

Trädvy Permalänk
Medlem
Plats
Lund
Registrerad
Maj 2006

Nu har jag en fråga till. I början av boxen har jag skapat en GroupMarker("") så att default är tom. problemet är att om jag från början klickar direkt på en annan groupmarker så väljs den. går detta att fixa på något sätt?

Trädvy Permalänk
Medlem
Plats
Linköping
Registrerad
Mar 2008

Jag hänger inte med.
1) Varför ha en GroupMarker("") alls? "så att default är tom" - menar du att hela comboboxen är tom som default? Isf behövs inga GroupMarkers alls.
2) "problemet är att om jag från början klickar direkt på en annan groupmarker så väljs den" - vilken väljs, den tomma eller den andra?

Trädvy Permalänk
Medlem
Plats
Lund
Registrerad
Maj 2006
Citat:

Ursprungligen inskrivet av badboll
Jag hänger inte med.
1) Varför ha en GroupMarker("") alls? "så att default är tom" - menar du att hela comboboxen är tom som default? Isf behövs inga GroupMarkers alls.
2) "problemet är att om jag från början klickar direkt på en annan groupmarker så väljs den" - vilken väljs, den tomma eller den andra?

hehe jag var ganska otydlig såg jag nu.

jo, jag har en box som från början är tom. den ska när programmet laddas fyllas med val (kategorier) som då ska hamna under rätt huvudrubrik.

typ så här:

Inkomster
-Lön
-Övrigt
Utgifter
-Hyra
-Garage
-Försäkring

Om exempelvis Inkomster inte har några kategorier i sig vill jag inte att denna grupp ska visas likaså om Utgifter inte har några kategorier.

När programmet laddas och det då finns kategorier att fylla boxen med vill jag att defaul ska vara att boxen inte är satt till något val utan att den visas som blank. Detta trodde jag att jag kunde lösa med att ha en GroupMarker(""). Problemet är egentligen att jag inte riktigt forstod hur ajg skulle använda din kod och blandade då in min gammla kod som fyller boxen. Detta kom jag på i morse innan jag stack till jobbet. ska kolla mer på det när jag kommer hem.

Trädvy Permalänk
Medlem
Plats
Lund
Registrerad
Maj 2006

Jag får verkligen inte rätsida på problemet. Så här ser min kod ut.

//Comboboxen instansieras av NetBeans men jag har ändrat den så att den ser ut så här: //jComboBoxCashBookCategory = new GroupComboBox(); private void setComboBoxCategories() { List incomeCategories = categoryManager.getCategories(false); List expenceCategories = categoryManager.getCategories(true); DefaultComboBoxModel comboBoxModel = new DefaultComboBoxModel(); if (incomeCategories.size() == 0 && expenceCategories.size() == 0) { comboBoxModel.addElement(new String("-")); } if (incomeCategories.size() > 0) { comboBoxModel.addElement(new GroupMarker("Inkomster")); for (int i=0; i<incomeCategories.size(); i++) { Category category = (Category)incomeCategories.get(i); comboBoxModel.addElement(new ComboBoxItem(category.getId(), category.getName())); } } if (expenceCategories.size() > 0) { comboBoxModel.addElement(new GroupMarker("Utgifter")); for (int i=0; i<expenceCategories.size(); i++) { Category category = (Category)expenceCategories.get(i); comboBoxModel.addElement(new ComboBoxItem(category.getId(), category.getName())); } } jComboBoxCashBookCategory.setModel(comboBoxModel); }

jag har även testat detta, men då funkar det inte alls.

private void setComboBoxCategories() { List incomeCategories = categoryManager.getCategories(false); List expenceCategories = categoryManager.getCategories(true); Vector v = new Vector(); if (incomeCategories.size() == 0 && expenceCategories.size() == 0) { v.add(new String("-")); } if (incomeCategories.size() > 0) { v.add(new GroupMarker("Inkomster")); for (int i=0; i<incomeCategories.size(); i++) { Category category = (Category)incomeCategories.get(i); v.add(new ComboBoxItem(category.getId(), category.getName())); } } if (expenceCategories.size() > 0) { v.add(new GroupMarker("Utgifter")); for (int i=0; i<expenceCategories.size(); i++) { Category category = (Category)expenceCategories.get(i); v.add(new ComboBoxItem(category.getId(), category.getName())); } } jComboBoxCashBookCategory = new GroupComboBox(v); }

Trädvy Permalänk
Medlem
Plats
Linköping
Registrerad
Mar 2008
Citat:

Ursprungligen inskrivet av stantemo
hehe jag var ganska otydlig såg jag nu.

jo, jag har en box som från början är tom. den ska när programmet laddas fyllas med val (kategorier) som då ska hamna under rätt huvudrubrik.

typ så här:

Inkomster
-Lön
-Övrigt
Utgifter
-Hyra
-Garage
-Försäkring

Om exempelvis Inkomster inte har några kategorier i sig vill jag inte att denna grupp ska visas likaså om Utgifter inte har några kategorier.

När programmet laddas och det då finns kategorier att fylla boxen med vill jag att defaul ska vara att boxen inte är satt till något val utan att den visas som blank.

Jag har vissa svårigheter att förstå ditt problem. Menar du att även om du har en klase kategorier att välja bland, så ska ingen vara vald från början? Det får du i så fall lösa med att ha ett blankt dummyelement i combon, och ha det förvalt. Du måste då också vara medveten om att användaren kan välja det elementet, och du måste ta ställning till om användaren får välja det blanka elementet eller inte, och agera därefter där kontroll av det valda elementet görs.

Jag skulle nog också skapa en egen liten klass för det blanka elementet, ungefär som så här:

final class DummyCategoryElement { @Override public void toString() { return " "; } }

...sedan kan man kolla om dummyelementet är valt på följande sätt...:

if (comboBox.getSelectedObject() instanceof DummyCategoryElement) { //... protestera? }

Om du vill ha ett blankt element förvalt som magiskt "försvinner" så fort användaren börjar bläddra i combon, så går det också att ordna, men det är ingen användarvänlig lösning och kräver lite lätt Java-voodoo som bara krånglar till saker i onödan.

Trädvy Permalänk
Medlem
Plats
Linköping
Registrerad
Mar 2008

List incomeCategories = categoryManager.getCategories(false); List expenceCategories = categoryManager.getCategories(true);

BTW, varför använder du inte generics, och varför använder du en skum boolean för att bestämma kategorityp? Varför kan det inte vara:

List<Category> incomeCategories = categoryManager.getIncomeCategories(); List<Category> expenseCategories = categoryManager.getExpenseCategories();

...?

Med den striktare typningen det här medför, kan du också skriva om

if (incomeCategories.size() > 0) { comboBoxModel.addElement(new GroupMarker("Inkomster")); for (int i=0; i<incomeCategories.size(); i++) { Category category = (Category)incomeCategories.get(i); comboBoxModel.addElement(new ComboBoxItem(category.getId(), category.getName())); } }

till:

if (!incomeCategories.isEmpty()) { comboBoxModel.addElement(new GroupMarker("Inkomster")); for (Category category : incomeCategories) { comboBoxModel.add(new ComboBoxItem(category.getId(), category.getName())); } }

Typade listor och for-each-loopar istället för indexerade loopar med typecasts från Object är en bättre lösning om du ändå "vet" att listan bara ska innehålla en typ...