2

Tristate Checkbox for Swing

 2 years ago
source link: https://news.kynosarges.org/2022/06/09/tristate-checkbox-for-swing/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Tristate Checkbox for Swing

One modern GUI feature notably absent from Java Swing is the tristate checkbox, i.e. a checkbox that has a third or “null” state in addition to the checked and unchecked states. This is typically visualized as a square or dash in the checkbox, where the checked state would be a checkmark and the unchecked state an empty box.

A proper implementation would take a lot of work, as the existing Swing infrastructure based on JToggleButton simply does not support more than two states. You can find an experimental attempt in an old JavaSpecialists article, and that wall of code does not even include drawing a proper null state icon. (I did crib the trick of showing all installed Look & Feels from that article, for my test application shown below.)

However, I found a clever and frankly outrageous hack submitted by an anonymous user on Stack Overflow. Instead of designing a new checkbox class, it is possible to emulate tristate behavior by simply attaching a custom ActionListener to a regular JCheckBox! Building on that, here’s my complete TristateActionListener class, with Javadoc comments elided for brevity. Note how actionPerformed cycles through the three states, using a custom null state icon not only for display but also as a marker for the third state, returned by getState as a null Boolean.

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;

public class TristateActionListener implements ActionListener {
    final protected Icon _icon;

    public TristateActionListener(Icon icon) {
        if (icon == null)
            throw new NullPointerException("icon");

        _icon = icon;
    }

    public static Icon createIcon(UIDefaults defaults) {
        if (defaults == null) defaults = UIManager.getDefaults();

        final Icon icon = defaults.getIcon("CheckBox.icon");
        final int width = icon.getIconWidth();
        final int height = icon.getIconHeight();
        final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);

        final Graphics g = image.getGraphics();
        icon.paintIcon(new JCheckBox(), g, 0, 0);
        final int dx = width / 4, dy = (int) (height / 2.2f);
        g.setColor(defaults.getColor("CheckBox.foreground"));
        g.fillRect(dx, dy, width - 2 * dx, height - 2 * dy);
        g.dispose();

        return new ImageIcon(image);
    }

    public static Boolean getState(JCheckBox checkBox){
        if (checkBox.getIcon() != null) return null;
        return checkBox.isSelected();
    }

    public void actionPerformed(ActionEvent e) {
        final JCheckBox checkBox = (JCheckBox) e.getSource();
        if (!checkBox.isSelected())
            checkBox.setIcon(_icon);
        else if (checkBox.getIcon() != null) {
            checkBox.setSelected(false);
            checkBox.setIcon(null);
        }
    }
}

Compared to the Stack Overflow code, I fixed a bug and added the drawing of the null state icon, based on the supplied or current Look & Feel. The result is not aesthetically perfect but good enough for my purposes. Here is a screenshot of the test application that shows the null state for all installed Look & Feels on my Windows 10 system.

Spinner Demo

You can download the complete source code for TristateActionListener and the TristateTest application, both with extensive Javadoc comments, as TristateCheckBox.zip. This package also contains the corresponding pre-created Java class files (targeting JDK 8) and Javadoc pages, all organized as a project for IntelliJ Idea 2022.1.2 with JDK 10.0.2.

However elegant, this hack is not without its shortcomings. They are detailed in the Javadoc comments, but aside from various visual inconsistencies the most important is that TristateActionListener itself changes the state of the attached JCheckBox. This means that any additional ActionListener that wants to react to a state change must delay that action using SwingUtilities.invokeLater, or else it would see an outdated state. Also, since the custom icon is used both as image and marker of the null state, you cannot use any custom icons of your own.

Finally, the test application revealed a weird internal JDK bug on my system (Windows 10 with JDK 10.0.2). Using multiple Look & Feels in the same application evidently can get some of them confused. The standard JCheckBox icons of Nimbus – not my custom one! – appear with the wrong background color (a shade of green). When using Nimbus as the only Look & Feel, the bug disappears.

a11046dab0d098ea1f638a89e2253dc7?s=49&d=retro&r=pgAuthor Christoph NahrPosted on 9 June 2022Categories CodingTags Java Swing


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK