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...
- Nvidia nu högre värderat än Apple och Microsoft11
- Microsoft konto kräver e-post adress10
- RTX 5080/5090 lagerstatus - Diskussionstråd115
- Krönika: Windows lever på lånad tid375
- Vilken serie såg du senast?2,5k
- Dagens fynd — Diskussionstråden54k
- Wordle på svenska - ordlig.se9,3k
- Rabbel.se - Ett dagligt ordspel2,3k
- Nu har Microsoft allt tabbat sig: Förstör Alt+Tab i ny uppdatering33
- Dagens fynd (bara tips, ingen diskussion) — Läs första inlägget först!20k
- Säljes Garderobsrensning! Nytt/Begagnat/Defekt - Massa blandat!
- Säljes Ubiquiti UniFi Security Gateway Pro
- Köpes Söker 3st SATA SSD och 2st NVME SSD
- Säljes Nothing Phone (3) - Svart
- Säljes Inno3D GeForce RTX 5090 X3
- Säljes Doom: The Dark Ages
- Säljes (PC) DOOM: The Dark Ages (Gamesplanet Steam key)
- Säljes Samsung 49" C49HG90 Curved QLED 144Hz
- Säljes RØDE NT-USB Studio/Streaming mikrofon
- Säljes Beyerdynamic DT1770 Pro
- Nvidia nu högre värderat än Apple och Microsoft11
- Upplev Doom: The Dark Ages – nu med path tracing och benchmarkverktyg för PC4
- Corsair lanserar Frame 5000D med fokus på luftflöde1
- Nu byter Max tillbaka till HBO Max113
- Nya Galaxy Fold 7 är företagets tunnaste hittills26
- Nya Ikea-högtalare får Bluetooth och Spotify Tap15
- Veckans fråga: Vilken webbläsare använder du?123
- Microsoft skruvar upp tempot i Edge22
- Grant Sinclair lanserar en nätt handhållen speldator12
- Bahnhofs vd: Hyresgäster betalar orimligt mycket för bredband170
Externa nyheter
Spelnyheter från FZ
- Nästa vecka – FZ streamar Eriksholm tillsammans med utvecklaren idag
- Subnautica 2 försenas till 2026 – för att undvika att betala ut en enorm bonus? idag
- Flertalet Fallout-spel kan vara på gång – enligt obekräftade uppgifter igår
- Snabbkollen – Spelar du mer eller mindre under sommarens ledighet? igår
- 20 minuter Ghost of Yōtei i kväll igår