Eigene "Checkbox" erstellen


#1

Hallo nochmals,
Ich weiß nicht recht wonach ich suchen soll bzw. was möglich ist, deshalb frage ich einfach mal im Forum:

Ich möchte gerne eine indivuelle Komponente erstellen, die ähnlich funktionieren sollte wie so etwas:

Es sollen von der Mitte aus mehrere Pfeile ansteigender Größe nach links und rechts angeordnet sein. Davon soll immer genau einer ausgewählt werden können, die Farbe des ausgewählten Pfeils ist eine andere als die der übrigen.

Im Großen und Ganzen muss ich also (eigentlich) diese Komponenten zeichnen, sie so anordnen und sich wie Checkboxen verhalten lassen. Die Frage ist nur: WIE? Kann ich in Java soetwas selbst zeichnen? Oder setze ich sie einfach als Bilder(Icons) ein? Oder gibt es sowas vielleicht schon?

Wenn ihr da was wisst würde ich mich freuen, mir fehlt da jeglicher Ansatz.
Grüße,
TheAceOfSpades


#2

Mir ist nicht klar, wie diese Komponente funktionieren soll. Sucht der Benutzer einen der Pfeile aus? Und was macht er dann? Reichen keine Buttons mit Pfeilen drauf?

Beschreib bitte etwas genauer, was da passieren soll.


#3

Okay:

Der Benutzer kann einen der Pfeile anwählen (wie einen Button klicken). Dann ändert sich die Farbe des Pfeils, damit der Benutzer sieht welcher Pfeil ausgewählt ist. Das macht er mit mehreren Komponenten. Mehr passiert dann auch nicht, wenn der Nutzer meint er ist fertig dann schaut das System welche Pfeile ausgewählt sind und arbeitet damit weiter.

Mit geht es hauptsächlich darum, dass das Element grafisch sehr gut aufgearbeitet ist. Buttons mit Pfeilen drauf sind da halt nicht so schön. Oder täusche ich mich da und man kann das Aussehen der Buttons so stark beeinflussen?


#4

Du kannst das Aussehen von Buttons stark beeinflussen. Hier bräuchtest du so eine Art von Buttongroup. Ich weiß nicht, wie stark sich das Aussehen von Radiobuttons ändern lässt, ansonsten musst du die Funktionalität, mit dem Druck eines Buttons alle anderen Buttons der Gruppe auszuschalten, irgendwie selbst realisieren. Schwer ist das nicht. Aber vorgefertigt fällt mir in der Richtung nichts ein.

Gut wäre es, wenn du das “Pfeil-Selektions-Dingsbums” als eigene Klasse erstellst, die lässt sich dann leicht wiederverwenden. Hätte ich gerade mehr Zeit, würde es mich ja reizen, mich daran zu versuchen…


#5

Das was du mir erklärst ist mir alles klar und so werde ich es auch machen. Wie gesagt, es geht mir mehr um das Design der einzelnen Schaltflächen. Ich kann mich nicht erinnern, dass solche Pfeile (oder ähnliches) in Swing enthalten ist :stuck_out_tongue:


#6

Die Funktionsweise entspricht der eines JSliders. Am einfachsten wäre es da wohl einen JSlider zu nehmen und ein Bild darunter oder darüber anzuzeigen, evtl. kann man auch den Bereich der Ticks übermalen.
Die Symbole musst Du entweder aus einem Bild laden oder direkt selbst zeichnen.

Wenn es aber genau so funktionieren bzw. dargestellt werden soll wie oben beschrieben, ist vermutlich der Weg über Radiobuttons in einer Buttongroup der einfachste Weg. Die Darstellung lässt sich ja mit setIcon() bzw. setSelectedIcon() entsprechen manipulieren. Die Icons kann man dann bequem aus einem Bild laden.


#7

Hallo TheAceOfSpades,

im JDK gibt es unter \demo\jfc\SwingSet2 die SwingSet2.jar.
Dort gibt es ein Beispiel für Image Radio Buttons. (Unter ButtonDemo -> Radio Buttons) plus Source Code.
Man kann den einzelnen RadioButtons ganz individuell unterschiedliche Bilder zuordnen, die je nach Zustand dargestellt werden.
(Die Bilder wirst du dir aber selber machen müssen. So was gibt es in Java nicht von Haus aus.)
Das ganze noch in eine ButtonGroup und dann solltest du schon ungefähr das haben, was du willst.

MfG
hansmueller


#8

oder du baust ordinär ein JPanel mit beliebiger Zeichnung und einen MouseListener, der pixelgenau reagiert,

ob man alle Zeichnungen wie Farbverlauf am Rand hinbekommt ist eine Frage,
aber wenn, dann hat man volle Kontrolle gegenüber Bildern, kann jederzeit Farbe ändern, Anzahl Dreiecke, Darstellung bei Auswahl, womöglich Skalierung usw.
gegenüber 40 Bildern (10 links, 10 rechts = 20, 20 blaue + 20 gelbe = 40) schöner, besonders wenn man ein winziges Detail ändern will und alle 40 für die Katz sind


#9

Bei mir werdens wahrscheinlich nur 1 Mitte + 4 links/rechts = 9 pro Panel sein. Zeichnung und MouseListener wird wahrscheinlich besser aussehen, weiß nur nicht ob ich das hinkrieg. Ich schau mal ob ich zum Zeichnen Tutorials finde :slight_smile:


#10

Wie schon angedeutet wurde, ist das “sowas ähnliches wie ein Slider”. Im speziellen könnte man es ganz abstrakt als "Visuelle Repräsentation (oder schlicht: View) für ein BoundedRangeModel" bezeichnen.

Dementsprechend könnte man das komplett um ein BoundedRangeModel herum aufziehen, mit ChangeListenern und dem ganzen Kram drumrum.

Natürlich kann man bei so einer eigenen Component beliebig weit gehen. Also, man könnte sich spontan hundert Sätze ausdenken, die anfangen mit “Man könnte…” oder “Es wäre cool, wenn…”. Aber ein Ansatz (keine “gute Lösung”, sondern wirklich NUR ein Ansatz!) könnte GROB so aussehen:

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import java.awt.geom.RoundRectangle2D;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import javax.swing.BoundedRangeModel;
import javax.swing.DefaultBoundedRangeModel;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;


public class ArrowSelectorTest
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                createAndShowGUI();
            }
        });
    }
    
    private static void createAndShowGUI()
    {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.getContentPane().setLayout(new GridLayout(3,1));
        
        BoundedRangeModel m0 = new DefaultBoundedRangeModel(0, 0, -10, 10);
        final ArrowSelector a0 = new ArrowSelector(m0);
        JPanel p0 = new JPanel();
        p0.add(a0);
        f.getContentPane().add(p0);
        
        BoundedRangeModel m1 = new DefaultBoundedRangeModel(3, 0, 2, 8);
        final ArrowSelector a1 = new ArrowSelector(m1);
        JPanel p1 = new JPanel();
        p1.add(a1);
        f.getContentPane().add(p1);

        BoundedRangeModel m2 = new DefaultBoundedRangeModel(-2, 0, -6, -1);
        final ArrowSelector a2 = new ArrowSelector(m2);
        JPanel p2 = new JPanel();
        p2.add(a2);
        f.getContentPane().add(p2);
        
        ChangeListener changeListener = new ChangeListener()
        {
            @Override
            public void stateChanged(ChangeEvent e)
            {
                System.out.println("Value changed: "+
                    a0.getModel().getValue()+", "+
                    a1.getModel().getValue()+", "+
                    a2.getModel().getValue());
            }
        };
        a0.addChangeListener(changeListener);
        a1.addChangeListener(changeListener);
        a2.addChangeListener(changeListener);
        
        f.setSize(800,600);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }
}


class ArrowSelector extends JComponent
{
    private BoundedRangeModel model;
    private final ChangeListener changeListener;
    private final List<ChangeListener> changeListeners;
    private Dimension previousSize;
    private final List<Shape> shapes;
    private int hoverValue = Integer.MAX_VALUE;

    ArrowSelector(final BoundedRangeModel model)
    {
        changeListeners = new CopyOnWriteArrayList<ChangeListener>();
        changeListener = new ChangeListener()
        {
            @Override
            public void stateChanged(ChangeEvent e)
            {
                repaint();
                fireStateChanged();
            }
        };
        addMouseListener(new MouseAdapter()
        {
            @Override
            public void mouseClicked(MouseEvent e)
            {
                for (int i=0; i<shapes.size(); i++)
                {
                    Shape shape = shapes.get(i);
                    if (shape.contains(e.getPoint()))
                    {
                        int value = i + model.getMinimum();
                        model.setValue(value);
                        repaint();
                    }
                }
            }
        });
        addMouseMotionListener(new MouseAdapter()
        {
            @Override
            public void mouseMoved(MouseEvent e)
            {
                hoverValue = Integer.MAX_VALUE;
                for (int i=0; i<shapes.size(); i++)
                {
                    Shape shape = shapes.get(i);
                    if (shape.contains(e.getPoint()))
                    {
                        int value = i + model.getMinimum();
                        hoverValue = value; 
                    }
                }
                repaint();
            }
        });
        setModel(model);
        shapes = new ArrayList<Shape>();
    }
    
    @Override
    public Dimension getPreferredSize()
    {
        if (super.isPreferredSizeSet())
        {
            return super.getPreferredSize();
        }
        if (model == null)
        {
            return new Dimension(100,20);
        }
        
        int min = model.getMinimum();
        int max = model.getMaximum();
        int n = (max-min+1);
        return new Dimension(n*20, 20);
    }

    public void addChangeListener(ChangeListener changeListener)
    {
        changeListeners.add(changeListener);
    }

    public void removeChangeListener(ChangeListener changeListener)
    {
        changeListeners.remove(changeListener);
    }

    protected final void fireStateChanged()
    {
        for (ChangeListener changeListener : changeListeners)
        {
            changeListener.stateChanged(new ChangeEvent(this));
        }
    }

    public BoundedRangeModel getModel()
    {
        return model;
    }

    public void setModel(BoundedRangeModel newModel)
    {
        BoundedRangeModel oldModel = getModel();
        if (oldModel != null)
        {
            oldModel.removeChangeListener(changeListener);
        }
        model = newModel;
        previousSize = null;
        if (newModel != null)
        {
            newModel.addChangeListener(changeListener);
        }
    }

    @Override
    protected void paintComponent(Graphics gr)
    {
        super.paintComponent(gr);
        Graphics2D g = (Graphics2D) gr;
        g.setColor(getBackground());
        g.fillRect(0, 0, getWidth(), getHeight());
        if (model == null)
        {
            return;
        }

        Dimension size = getSize();
        if (!size.equals(previousSize))
        {
            createShapes();
            previousSize = size;
        }
        
        g.setRenderingHint(
            RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
        g.setColor(Color.BLACK);
        for (int i=0; i<shapes.size(); i++)
        {
            Shape shape = shapes.get(i);
            int value = i + model.getMinimum();
            if (value == model.getValue())
            {
                g.setColor(Color.ORANGE);
            }
            else
            {
                g.setColor(Color.GREEN);
            }
            g.fill(shape);
            g.setColor(Color.BLACK);
            if (value == hoverValue)
            {
                g.setStroke(new BasicStroke(3.0f));
            }
            else
            {
                g.setStroke(new BasicStroke(1.0f));
            }
            g.draw(shape);
        }
    }
    
    private void createShapes()
    {
        shapes.clear();
        
        int min = model.getMinimum();
        int max = model.getMaximum();
        int scalingSteps = Math.max(Math.abs(min), Math.abs(max));
        double r = 0.4;
        double cw = (double)getWidth() / (max-min+1);
        double ch = getHeight();
        double size = Math.min(cw, ch);
        for (int cx=min; cx<=max; cx++)
        {
            double x0 = (cx - min) * cw;
            AffineTransform at = new AffineTransform();
            at.translate(x0, (ch-size)*0.5);
            at.scale(size, size);
            double scalingAmount = (double)Math.abs(cx) / scalingSteps;
            double scale = 0.75 + 0.25 * scalingAmount;
            if (cx < 0)
            {
                at.translate(0.5, 0.5);
                at.scale(scale, scale);
                at.translate(-0.5, -0.5);
                at.scale(-1, 1);
                at.translate(-1, 0);
                shapes.add(at.createTransformedShape(createArrowShape()));
            }
            else if (cx > 0)
            {
                at.translate(0.5, 0.5);
                at.scale(scale, scale);
                at.translate(-0.5, -0.5);
                shapes.add(at.createTransformedShape(createArrowShape()));
            }
            else 
            {
                Shape s = new RoundRectangle2D.Double(0, 0, 1, 1, r, r);
                at.translate(0.5, 0.5);
                at.scale(scale, scale);
                at.translate(-0.5, -0.5);
                shapes.add(at.createTransformedShape(s));
            }
        }
    }

    private Shape createArrowShape()
    {
        double r = 0.2;
        Path2D path = new Path2D.Double();
        path.moveTo(0, 0.5);
        path.lineTo(0, r);
        path.curveTo(0, r, 0, 0, r+r, r);
        path.lineTo(1-r, 0.5-r*0.5);
        path.curveTo(1-r, 0.5-r*0.5, 1, 0.5, 1-r, 0.5+r*0.5);
        path.lineTo(r+r, 1-r);
        path.curveTo(r+r, 1-r, 0, 1, 0, 1-r);
        path.closePath();
        return path;
    }

}


#11

Naja, wenn du die paintcomponent eines jbuttons ueberschreibst, und einfach das bild dahin malst, sollte das soweit du erklaert hast fuer dich reichen.
Allerdings waere das ganze nicht wirklich erweiterbar.


#12

hier ist mein Versuch aber ohne runde Ecken: [SPOILER]```package test.bytewelt;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.Shape;

import javax.swing.ButtonGroup;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JRadioButton;

public class SliderTest extends JPanel {

public SliderTest() {
    super(new GridLayout(1, 0));

    ButtonGroup buttonGroup = new ButtonGroup();
    for (int i = -9; i < 10; i++) {
        addButton(this, buttonGroup, i);
    }
    setPreferredSize(new Dimension(4 * 190, 4 * 10));
}

public static void main(String[] args) {
    JFrame jFrame = new JFrame("SliderTest");
    jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    jFrame.add(new SliderTest());
    jFrame.pack();
    jFrame.setVisible(true);
}

private static void addButton(final JPanel message, ButtonGroup buttonGroup, final int i) {
    JRadioButton jRadioButton = new JRadioButton() {

        @Override
        public void paint(Graphics g) {
            if (isShowing()) {
                Graphics2D graphics2d = (Graphics2D) g;
                Dimension size = getSize();
                int minHeight = size.height / 4;
                int heightStep = minHeight / 9;
                graphics2d.setBackground(message.getBackground());
                graphics2d.clearRect(0, 0, size.width, size.height);
                Shape shape = null;
                if (0 < i) {
                    Polygon polygon = new Polygon();
                    polygon.addPoint(2, minHeight
                            - (i * heightStep));
                    polygon.addPoint(2, 3
                            * minHeight
                            + (i * heightStep));
                    polygon.addPoint(size.width - 2, size.height / 2);
                    shape = polygon;
                }
                if (0 == i) {
                    shape = new Rectangle(size.width / 4, minHeight, size.width / 2, size.height / 2);
                }
                if (0 > i) {
                    Polygon polygon = new Polygon();
                    polygon.addPoint(size.width - 2, minHeight
                            + (i * heightStep));
                    polygon.addPoint(size.width - 2, size.height
                            - 2
                            - ((9 + i) * heightStep));
                    polygon.addPoint(2, size.height / 2);
                    shape = polygon;
                }
                graphics2d.setColor(isSelected() ? Color.YELLOW : Color.BLUE);
                graphics2d.fill(shape);
            }
        }

    };
    jRadioButton.setName(String.format("%s", i));
    jRadioButton.setRolloverEnabled(false);
    jRadioButton.setPressedIcon(null);
    jRadioButton.setSelectedIcon(null);
    jRadioButton.setPressedIcon(null);
    message.add(jRadioButton);
    buttonGroup.add(jRadioButton);
}

}```[/SPOILER]für die ist dann wahrscheinlich in den restlichen 250 Zeilen Platz… ;o)

bye
TT


#13

@Marco13 Das ist genau das was ich gesucht habe :D! Jetzt muss ich nur noch zusehen, dass die Farben etwas anders sind und dass ich es irgendwie größer skaliert kriege. Vielen vielen Dank, ich dachte schon ich muss an den Zeichnungen verzweifeln…


#14

Da wollte ich heute basteln, und ihr habt das schon gelöst! schmunzelt


#15

Skalieren muss man da eigentlich nichts. Es nimmt sich den Platz, der da ist bzw. ihm mit setPreferredSize gegeben wurde (man kann aber auch in der getPreferredSize z.B. 30 statt 20 verwenden). Und die Farben werden direkt in der paintComponent gesetzt.


#16

Naja ich suche trotzdem noch rum. Wenn ich die Komponenten weiter auseinander setzen landen sie “außerhalb” des Panels, die äußeren Pfeile werden dann nicht mehr angezeigt.


#17

Hmja… man müßte sich da wohl genauer Gedanken machen, welche Größe wovon abhängen soll und so. Im Moment versucht er, jedes “Kästchen” mindestens 20x20 groß zu machen, und gibt das auch als seine preferredSize zurück. Aber wenn weniger Platz ist, sollte er es auch kleiner zeichnen (wenn nicht muss ich nochmal schauen). Das ganze war ja kein ausgearbeiteter Universal-Slider-Ersatz, sondern nur ein Beispiel für einen möglichen Ansatz… :o


#18

ich komme mir gerade wie der BetaMax-Erfinder vor… ;o)

bye
TT


#19

Das ist Marketing. Egal wie gut es ist, du hast dein Produkt nur schlecht verkauft. Ein kleiner Screenshot, der gleich einen ersten Eindruck verschafft, kann wahre Wunder wirken :wink:


#20

Ich hab nun doch noch was gebastelt. Ausgehend von der Lösung von @Timothy_Truckle habe ich seine Lösung erweitert, unterteilt, parametrisierbar gemacht und die Möglichkeit hinzugefügt, über die Auswahl einer der Buttons informiert zu werden.

Insbesondere die Darstellung der Dreiecke habe ich - optisch gleichbleibend - kräftig überarbeitet, bis ich genau verstand, was da passiert. :o)

[SPOILER]Startklasse:


import java.awt.Color;
import java.awt.GridLayout;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;

/**
 * Diese Klasse testet ArrowSelector-Elemente.
 *
 * Siehe http://forum.byte-welt.net/threads/12503-Eigene-Checkbox-erstellen
 * den Post von Timothy_Truckle.
 *
 * @version 1.01     2014-07-24
 * @author Crian
 */

public class ArrowSelectorTest {

    /** Startpunkt des Programms, Parameter werden ignoriert. */
    public static void main(String[] args) {
        new ArrowSelectorTest();
    }

    /** Konstruktor. */
    public ArrowSelectorTest() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                createAndShowGUI();
            }
        });
    }

    /** Erstellt die grafische Oberfläche und zeigt sie an. */
    private void createAndShowGUI() {
        JFrame frame = new JFrame("SliderTest");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLayout(new GridLayout(0,  1));

        add(frame, "Standard 9 auf beiden Seiten:", new ArrowSelector());
        add(frame, "9 auf beiden Seiten andere Farben:",
                new ArrowSelector(Color.RED, Color.ORANGE));
        add(frame, "3 auf beiden Seiten:", new ArrowSelector(-3, 3));
        add(frame, "3 auf beiden Seiten mit anderen Farben:",
                new ArrowSelector(Color.RED, Color.GREEN, -3, 3));
        add(frame, "5 rechts mit Rechteck:",
                new ArrowSelector(Color.RED, Color.BLACK, 0, 5));
        add(frame, "5 rechts ohne Rechteck:",
                new ArrowSelector(Color.ORANGE, Color.RED, 1, 5));
        add(frame, "4 links ohne Rechteck:", new ArrowSelector(-9, -6));

        frame.pack();
        frame.setVisible(true);
    }

    /**
     * Fügt einen ArrowSelektor zum Frame hinzu.
     *
     * @param frame
     *            Frame, in den eingefügt wird.
     * @param message
     *            Titel des ArrowSelectors.
     * @param arrowSelector
     *            Anzuzeigender ArrowSelektor.
     */
    private void add(final JFrame frame, final String message,
            final ArrowSelector arrowSelector) {
        frame.add(new JLabel(message));
        frame.add(arrowSelector);
        arrowSelector.addArrowSelectorListener(
                new ArrowSelectorListener() {
            @Override
            public void reactOnButtonSelection(int place) {
                System.out.println("Änderung im ArrowSelektor '"
                        + message + "':
	Ausgewählt ist nun Platz "
                        + place + "!");
            }
        });
    }

}

Der eigentliche ArrowSelector:


import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.ButtonGroup;
import javax.swing.JPanel;

/**
 * Diese Klasse stellt eine Komponente dar, auf der man eine Pfeiltaste
 * auswählen kann. In der Mitte steht ein Rechteck zur Verfügung.
 *
 * Siehe http://forum.byte-welt.net/threads/12503-Eigene-Checkbox-erstellen
 * den Post von Timothy_Truckle.
 *
 * @version 1.01     2014-07-24
 * @author Crian
 */

public class ArrowSelector extends JPanel {

    private static final long serialVersionUID = -5111966252366706268L;

    /** Defaultfarbe für selektierte Pfeile. */
    static final Color SELECTED_COLOR = Color.YELLOW;

    /** Defaultfarbe für nicht selektierte Pfeile. */
    static final Color NOT_SELECTED_COLOR = Color.BLUE;

    /** Maximale Anzahl an Pfeilen pro Seite. */
    static final int MAXIMAL_NUMBER_OF_ARROWS_PER_SIDE = 9;

    /** Farbe für selektierte Pfeile. */
    private final Color selectedColor;

    /** Farbe für nicht selektierte Pfeile. */
    private final Color notSelectedColor;

    /**
     * Platz des Pfeils der am weitesten links ist (negative Zahl, wenn Pfeile
     * links da sein sollen).
     */
    private final int leftestArrowPlace;

    /**
     * Platz des Pfeils der am weitesten rechts ist (positive Zahl, wenn Pfeile
     * rechts da sein sollen).
     */
    private final int rightetsArrowPlace;

    /** Verwaltung der Listener. */
    private final List<ArrowSelectorListener> listeners;

    /** Konstruktor. */
    public ArrowSelector() {
        this(SELECTED_COLOR, NOT_SELECTED_COLOR,
                -MAXIMAL_NUMBER_OF_ARROWS_PER_SIDE,
                MAXIMAL_NUMBER_OF_ARROWS_PER_SIDE);
    }

    /**
     * Konstruktor.
     *
     * @param selectedColor
     *            Farbe für selektierte Pfeile.
     * @param notSelectedColor
     *            Farbe für nicht selektierte Pfeile.
     */
    public ArrowSelector(final Color selectedColor,
            final Color notSelectedColor) {
        this(selectedColor, notSelectedColor,
                -MAXIMAL_NUMBER_OF_ARROWS_PER_SIDE,
                MAXIMAL_NUMBER_OF_ARROWS_PER_SIDE);
    }

    /**
     * Konstruktor.
     *
     * @param leftestArrowPlace
     *            Platz des Pfeils der am weitesten links ist (negative Zahl,
     *            wenn Pfeile links da sein sollen).
     * @param rightetsArrowPlace
     *            Platz des Pfeils der am weitesten rechts ist (positive Zahl,
     *            wenn Pfeile rechts da sein sollen).
     */
    public ArrowSelector(final int leftestArrowPlace,
            final int rightetsArrowPlace) {
        this(SELECTED_COLOR, NOT_SELECTED_COLOR,
                leftestArrowPlace, rightetsArrowPlace);
    }

    /**
     * Konstruktor.
     *
     * @param selectedColor
     *            Farbe für selektierte Pfeile.
     * @param notSelectedColor
     *            Farbe für nicht selektierte Pfeile.
     * @param leftestArrowPlace
     *            Platz des Pfeils der am weitesten links ist (negative Zahl,
     *            wenn Pfeile links da sein sollen).
     * @param rightetsArrowPlace
     *            Platz des Pfeils der am weitesten rechts ist (positive Zahl,
     *            wenn Pfeile rechts da sein sollen).
     */
    public ArrowSelector(final Color selectedColor,
            final Color notSelectedColor, final int leftestArrowPlace,
            final int rightetsArrowPlace) {
        super(new GridLayout(1, 0));

        this.selectedColor = selectedColor;
        this.notSelectedColor = notSelectedColor;
        this.leftestArrowPlace = leftestArrowPlace;
        this.rightetsArrowPlace = rightetsArrowPlace;
        listeners = new ArrayList<>();

        if (-leftestArrowPlace > MAXIMAL_NUMBER_OF_ARROWS_PER_SIDE) {
            throw new IllegalArgumentException("Zu viele Pfeile links "
                    + "gewählt. Maximal sind "
                    + MAXIMAL_NUMBER_OF_ARROWS_PER_SIDE
                    + " erlaubt, gewählt wurden " + (-leftestArrowPlace) + ".");
        }
        if (rightetsArrowPlace > MAXIMAL_NUMBER_OF_ARROWS_PER_SIDE) {
            throw new IllegalArgumentException("Zu viele Pfeile rechts "
                    + "gewählt. "
                    + "Maximal sind " + MAXIMAL_NUMBER_OF_ARROWS_PER_SIDE
                    + " erlaubt, gewählt wurden " + rightetsArrowPlace + ".");
        }

        createArrows();
        setPreferredSize(new Dimension(4 * 190, 4 * 10));
    }

    /** Erstellt die anzuzeigenden Pfeile. */
    private void createArrows() {
        ButtonGroup buttonGroup = new ButtonGroup();
        for (int place = leftestArrowPlace;
                place <= rightetsArrowPlace; place++) {
            addButton(buttonGroup, place);
        }
    }

    /**
     * Erzeugt einen Pfeil.
     *
     * @param buttonGroup
     *            Gruppe, zu der der Pfeil-Button hinzugefügt wird.
     * @param place
     *            Platz des Pfeiles.
     */
    private void addButton(final ButtonGroup buttonGroup, final int place) {
        ArrowButton button = new ArrowButton(this, place,
                selectedColor, notSelectedColor);
        button.setName(String.format("%s", place));
        button.setRolloverEnabled(false);
        button.setPressedIcon(null);
        button.setSelectedIcon(null);
        button.setPressedIcon(null);
        add(button);
        buttonGroup.add(button);
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                for (ArrowSelectorListener listener : listeners) {
                    listener.reactOnButtonSelection(place);
                }
            }
        });
    }

    /**
     * Fügt einen Listener zu der Liste der Listener hinzu.
     *
     * @param listener
     *            Hinzuzufügender Listener.
     */
    public void addArrowSelectorListener(
             final ArrowSelectorListener listener) {
        listeners.add(listener);
    }

}

Der einzelne ArrowButton:


import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.Shape;

import javax.swing.JPanel;
import javax.swing.JRadioButton;

import static such.dir.eins.aus.ArrowSelector.MAXIMAL_NUMBER_OF_ARROWS_PER_SIDE;

/**
 * Diese Klasse stellt einen JRadioButton dar, der sich als Pfeildarstellung
 * präsentiert.
 *
 * Siehe http://forum.byte-welt.net/threads/12503-Eigene-Checkbox-erstellen
 * den Post von Timothy_Truckle.
 *
 * @version 1.01     2014-07-24
 * @author Crian
 */

public class ArrowButton extends JRadioButton {

    private static final long serialVersionUID = -8818232289745150208L;

    /** Unterteilung der Höhe für das Rechteck und die Dreiecke. */
    private static final int HEIGHT_DIVISOR = 4;

    /** Unterteilung der Breite für das Rechteck. */
    private static final int WIDTH_DIVISOR = 4;

    /** Abstand der Pfeildarstellung vom Rand in X-Richtung. */
    private static final int ARROW_DISTANCE = 2;

    /** Das Panel, in dem dieser ArrowButton platziert wird. */
    private final JPanel surroundingPanel;

    /**
     * Platz oder Position des ArrowButtons. 0 bezeichnet die Mitte, negative
     * Werte Plätze links der Mitte, positive Werte rechts der Mitte.
     */
    private final int place;

    /** Farbe für selektierte Pfeile. */
    private final Color selectedColor;

    /** Farbe für nicht selektierte Pfeile. */
    private final Color notSelectedColor;

    /**
     * Konstruktor.
     *
     * @param surroundingPanel
     *            Das Panel, in dem dieser ArrowButton platziert wird.
     * @param place
     *            Platz oder Position des ArrowButtons. 0 bezeichnet die Mitte,
     *            negative Werte Plätze links der Mitte, positive Werte rechts
     *            der Mitte.
     * @param selectedColor
     *            Farbe für ausgewählte Pfeile.
     * @param notSelectedColor
     *            Farbe für nicht ausgewählte Pfeile.
     */
    public ArrowButton(final JPanel surroundingPanel, final int place,
            final Color selectedColor, final Color notSelectedColor) {
        super();
        this.surroundingPanel = surroundingPanel;
        this.place = place;
        this.selectedColor = selectedColor;
        this.notSelectedColor = notSelectedColor;
    }

    /**
     * Stellt den ArrowButton dar, falls das Objekt sichtbar ist.
     *
     * @param graphics
     *            Grafikobjekt.
     */
    @Override
    public void paint(final Graphics graphics) {
        if (isShowing()) {
            paintTheArrow((Graphics2D) graphics);
        }
    }

    /**
     * Stellt den ArrowButton dar.
     *
     * @param graphics2d
     *            Grafikobjekt.
     */
    private void paintTheArrow(final Graphics2D graphics2d) {
        Dimension size = getSize();
        graphics2d.setBackground(surroundingPanel.getBackground());
        graphics2d.clearRect(0, 0, size.width, size.height);

        Shape shape = null;
        if (0 == place) {
            shape = createRectangle(size);
        }
        else {
            shape = createArrow(size);
        }
        graphics2d.setColor(isSelected()
                ? selectedColor
                : notSelectedColor);
        graphics2d.fill(shape);
    }

    /**
     * Erzeugt das Rechteck. Die Größe wird übergeben und nicht nochmal
     * abgefragt, damit während einer Größenveränderung nicht bei mehrfachen
     * Abfragen der Werte unterschiedliche Größen verwendet werden.
     *
     * @param size
     *            Größe des Buttons.
     * @return Erzeugtes Rechteck.
     */
    private Rectangle createRectangle(final Dimension size) {
        int x = size.width / WIDTH_DIVISOR;
        int y = size.height / HEIGHT_DIVISOR;
        int width = size.width / 2;
        int height = size.height / 2;
        return new Rectangle(x, y, width, height);
    }

    /**
     * Erzeugt einen Pfeil. Die Größe wird übergeben und nicht nochmal
     * abgefragt, damit während einer Größenveränderung nicht bei mehrfachen
     * Abfragen der Werte unterschiedliche Größen verwendet werden.
     *
     * Links zeigt die Spitze nach links, rechts zeigt sie nach rechts.
     *
     * @param size
     *            Größe des Buttons.
     * @return Erzeugter Pfeil.
     */
    private Polygon createArrow(final Dimension size) {
        /* Minimale Höhe, entspricht der des Rechtecks (1/4): */
        int minHeight = size.height / HEIGHT_DIVISOR;

        /* Maximale Höhe (3/4): */
        int maxHeight = minHeight * (HEIGHT_DIVISOR - 1);

        /* Höhendifferenz oben (oder unten, also 1/4): */
        int totalHeightDifference = (maxHeight - minHeight) / 2;

        /* Höhendifferenz für einen Schritt: */
        int heightDifferenceStep = totalHeightDifference /
                MAXIMAL_NUMBER_OF_ARROWS_PER_SIDE;

        /* Höhendifferenz für den Platz: */
        int heightDifference = (MAXIMAL_NUMBER_OF_ARROWS_PER_SIDE -
                Math.abs(place)) * heightDifferenceStep;

        /* Benötigte x-Koordinaten: */
        int leftX = ARROW_DISTANCE;
        int rightX = size.width - ARROW_DISTANCE;

        /* Benötigte y-Koordinaten: */
        int upperY = heightDifference;
        int middleY = size.height / 2;
        int lowerY = size.height - heightDifference;

        /* x-Koordinatentausch für die linken Dreiecke: */
        if (place < 0) {
            int temp = rightX;
            rightX = leftX;
            leftX = temp;
        }

        /* Dreieck erzeugen: */
        Polygon polygon = new Polygon();
        polygon.addPoint(leftX, upperY);
        polygon.addPoint(rightX, middleY);
        polygon.addPoint(leftX, lowerY);

        return polygon;
    }

}

und das Interface ArrowSelectorListener:


/**
 * Dieses Interface dient zum Reagieren auf eine Auswahl im ArrowSelektor.
 *
 * @version 1.01     2014-07-24
 * @author Crian
 */

public interface ArrowSelectorListener {

    void reactOnButtonSelection(int place);

}```[/SPOILER]