JComboBox gruppera val
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.
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);
}
}
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?
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å.
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?
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?
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.
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);
}
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.
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...
- Bra 5G-router för bruk i fäbodstuga.12
- Stora feta metaltråden!5,9k
- Ingen respons från Webhallen140
- Metas nya användarpolicy: betala eller bli spårad43
- Rykte: AMD siktar på prestandaklass med nästa generations Radeon67
- Datorn kraschar varje gång försöker spela (problem med drivrutiner, Oblivion och annat)10
- Portabel AC inför sommaren [Samlingstråd]5,7k
- Roundup: Sex luftkylare utmanar kylarkungen Noctua70
- Vilken alkoholhaltig dryck dricker ni just nu?6,4k
- Tråden om Xbox Series X|S8,2k
- Säljes Intressekoll:Dell latitude 5410
- Bytes MSI GeForce RTX 5090 32G VENTUS 3X OC bytes
- Köpes Söker 7950x3d eller 9800x3d!
- Säljes Sound Blaster AE-9
- Köpes Speldator
- Säljes Uppgraderingspaket, GPU, Chassi, etc..
- Säljes Gaming PC & Ultra-wide monitor, AIO push/pull, RGB fans + controller & reflective chassi
- Säljes dell Poweredge T630
- Köpes Söker 280mm kopparradiator, Alphacool, EK...
- Köpes Lian Li internal rack mount.
- Metas nya användarpolicy: betala eller bli spårad43
- Möjlig Radeon RX 7950 XTX-prototyp letar sig ut på nätet12
- Asus lanserar arbetsstation med Nvidia GB300 Blackwell Ultra9
- Så ska Microsoft få bukt med slöhet i Windows 1150
- Microsoft-bugg hotar tusentals organisationer10
- Överklockad Threadripper Pro 9995WX når 947 watt25
- Rykte: AMD siktar på prestandaklass med nästa generations Radeon67
- Snabbkoll: Hjälper du nära och kära med teknikköp?47
- Nya Intel-grafikkort närmar sig19
- Handskriven assembler snabbar upp kod24
Externa nyheter
Spelnyheter från FZ
- Ubisoft bekräftar att ett nytt Ghost Recon är på väg idag
- The Chinese Room har köpt sig fria från Sumo Digital idag
- Avatar: Frontiers of Pandora får tredjepersonsvy i år idag
- Ghost of Yōtei blir ungefär lika långt som föregångaren igår
- Inofficiell FPS-remake av Necropolis från första Fallout får en trailer igår