演示代码包含在类的顶部。
编辑:
screenshot
概念:
使用不带文本的普通JCheckBox,这样外观和行为都是经典的,并且可以通过paintComponent代码来测量框的大小。为不确定状态绘制一个填充矩形。JCheckBox是以编程方式设置的,因为使用了ActionListener,所以不会触发事件。
布尔值用于表示状态。类本身不扩展组件,因此它只有基本方法。所有使用过的部件都暴露在外。
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
final public class ThreeStateCheckBox {
public static void main(final String[] args) {
SwingUtilities.invokeLater(() -> {
final String lookAndFeelName;
lookAndFeelName = "Nimbus";
final UIManager.LookAndFeelInfo[] lafis = UIManager.getInstalledLookAndFeels();
boolean foundLAFI = false;
for (UIManager.LookAndFeelInfo lafi : lafis) {
if (lafi.getName().equalsIgnoreCase(lookAndFeelName)) {
try {
UIManager.setLookAndFeel(lafi.getClassName());
} catch (Exception e) {
e.printStackTrace();
}
foundLAFI = true;
break;
}
}
if (!foundLAFI) {
throw new Error("Unknown LookAndFeel: " + lookAndFeelName);
}
final JFrame window = new JFrame("L&F: " + lookAndFeelName);
final JPanel contentPane = new JPanel(new GridLayout(0, 1));
final String cbDemoText = "bold";
final List<JComponent> cbList = new ArrayList<>();
ThreeStateCheckBox cbThreeState;
JCheckBox cbClassic;
cbThreeState = new ThreeStateCheckBox(cbDemoText, null, false, true, null, null, null, null, false, false, TSCBOrientation.WEST, true, null);
cbClassic = new JCheckBox(cbDemoText);
cbList.add(cbThreeState.getComponent());
cbList.add(cbClassic);
cbThreeState.setClientCode(b -> System.out.println("CURRENT STATE: " + b.getState() + "; PREVIOUS STATE: " + b.getPreviousState()));
cbClassic.addActionListener(e -> System.out.println("CURRENT STATE: " + cbClassic.isSelected()));
cbThreeState.setUserCanEffectIndeterminateState(true);
cbList.forEach(contentPane::add);
window.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
window.setContentPane(contentPane);
window.setSize(new Dimension(300, 300));
window.setResizable(false);
window.setLocationRelativeTo(null);
window.setVisible(true);
cbThreeState.setState(null);
cbThreeState.setState(false);
cbThreeState.setState(true);
cbThreeState.callClientCode();
});
}
public enum TSCBOrientation {
NORTHWEST, NORTH, NORTHEAST, WEST, CENTER, EAST, SOUTHWEST, SOUTH, SOUTHEAST
}
final public static Color DEFAULT_COLOR_OF_INDETERMINATE_STATE = new Color(0x222222);
final public static Color DEFAULT_COLOR_OF_INDETERMINATE_STATE_WHENDISABLED = new Color(0x7f222222, true);
final public static int DEFAULT_SIZE_OF_INDETERMINATERECTANGLE = 8;
final public static int MIN_SIZE_OF_INDETERMINATERECTANGLE = 2;
final private static Map<TSCBOrientation, Integer> GBC_ORIENTATION = new HashMap<>();
static {
GBC_ORIENTATION.put(TSCBOrientation.NORTHWEST, GridBagConstraints.NORTHWEST);
GBC_ORIENTATION.put(TSCBOrientation.NORTH, GridBagConstraints.NORTH);
GBC_ORIENTATION.put(TSCBOrientation.NORTHEAST, GridBagConstraints.NORTHEAST);
GBC_ORIENTATION.put(TSCBOrientation.WEST, GridBagConstraints.WEST);
GBC_ORIENTATION.put(TSCBOrientation.CENTER, GridBagConstraints.CENTER);
GBC_ORIENTATION.put(TSCBOrientation.EAST, GridBagConstraints.EAST);
GBC_ORIENTATION.put(TSCBOrientation.SOUTHWEST, GridBagConstraints.SOUTHWEST);
GBC_ORIENTATION.put(TSCBOrientation.SOUTH, GridBagConstraints.SOUTH);
GBC_ORIENTATION.put(TSCBOrientation.SOUTHEAST, GridBagConstraints.SOUTHEAST);
}
final private JPanel allContainer;
final private JPanel contentContainer;
final private JCheckBox checkBox;
final private JLabel label;
private String text = null;
private Boolean state;
private Boolean previousState;
private boolean userCanEffectIndeterminateState;
private boolean userCanAffectIndeterminateState;
private Consumer<ThreeStateCheckBox> clientCode = null;
final private Color colorOfIndeterminateRectangle;
final private Color colorOfIndeterminateRectangleWhenDisabled;
final private int widthOfIndeterminateRectangle;
final private int heightOfIndeterminateRectangle;
private boolean enabled = true;
public ThreeStateCheckBox() {
this(null, false, false, true, null, null, null, null, false, false, TSCBOrientation.WEST, false, null);
}
public ThreeStateCheckBox(final String text) {
this(text, false, false, true, null, null, null, null, false, false, TSCBOrientation.WEST, false, null);
}
public ThreeStateCheckBox(final String text, final Boolean initialState, final boolean userCanEffectIndeterminateState, final boolean userCanAffectIndeterminateState, final Color colorOfIndeterminateRectangle, final Color colorOfIndeterminateRectangleWhenDisabled, final Integer widthOfIndeterminateRectangle, final Integer heightOfIndeterminateRectangle, final boolean reactAcrossFullWidth, final boolean reactAcrossFullHeight, final TSCBOrientation orientationInAvailableSpace, final boolean leaveGapBetweenBoxAndLabel, final Consumer<ThreeStateCheckBox> clientCode) {
state = initialState;
previousState = state;
this.userCanEffectIndeterminateState = userCanEffectIndeterminateState;
this.userCanAffectIndeterminateState = userCanAffectIndeterminateState;
if (colorOfIndeterminateRectangle == null) {
this.colorOfIndeterminateRectangle = DEFAULT_COLOR_OF_INDETERMINATE_STATE;
} else {
this.colorOfIndeterminateRectangle = colorOfIndeterminateRectangle;
}
if (colorOfIndeterminateRectangleWhenDisabled == null) {
this.colorOfIndeterminateRectangleWhenDisabled = DEFAULT_COLOR_OF_INDETERMINATE_STATE_WHENDISABLED;
} else {
this.colorOfIndeterminateRectangleWhenDisabled = colorOfIndeterminateRectangleWhenDisabled;
}
if (widthOfIndeterminateRectangle == null) {
this.widthOfIndeterminateRectangle = DEFAULT_SIZE_OF_INDETERMINATERECTANGLE;
} else {
this.widthOfIndeterminateRectangle = Math.max(MIN_SIZE_OF_INDETERMINATERECTANGLE, widthOfIndeterminateRectangle);
}
if (heightOfIndeterminateRectangle == null) {
this.heightOfIndeterminateRectangle = DEFAULT_SIZE_OF_INDETERMINATERECTANGLE;
} else {
this.heightOfIndeterminateRectangle = Math.max(MIN_SIZE_OF_INDETERMINATERECTANGLE, heightOfIndeterminateRectangle);
}
Integer orientation = GBC_ORIENTATION.get(orientationInAvailableSpace);
if (orientation == null) {
orientation = GridBagConstraints.CENTER;
}
this.clientCode = clientCode;
label = new JLabel();
checkBox = new JCheckBox() {
@Override
protected void paintComponent(final Graphics g) {
super.paintComponent(g);
if (state == null) {
final int w = getWidth();
final int h = getHeight();
final int wOdd = w % 2;
final int hOdd = h % 2;
final int centerX = w / 2;
final int centerY = h / 2;
final int rwHalf = Math.max(1, ThreeStateCheckBox.this.widthOfIndeterminateRectangle / 2);
final int rhHalf = Math.max(1, ThreeStateCheckBox.this.heightOfIndeterminateRectangle / 2);
final int rw = rwHalf * 2 + wOdd;
final int rh = rhHalf * 2 + hOdd;
if (isEnabled()) {
g.setColor(ThreeStateCheckBox.this.colorOfIndeterminateRectangle);
} else {
g.setColor(ThreeStateCheckBox.this.colorOfIndeterminateRectangleWhenDisabled);
}
g.fillRect(centerX - rwHalf, centerY - rhHalf, rw, rh);
}
}
};
final int gap;
if (leaveGapBetweenBoxAndLabel) {
gap = checkBox.getIconTextGap();
} else {
gap = 0;
}
contentContainer = new JPanel(new BorderLayout(gap, 0));
allContainer = new JPanel(new GridBagLayout());
setOpaque(false);
if (state != null && state) {
checkBox.setSelected(true);
}
setText(text);
contentContainer.add(checkBox, BorderLayout.WEST);
contentContainer.add(label, BorderLayout.CENTER);
final GridBagConstraints gbc = new GridBagConstraints();
gbc.anchor = orientation;
if (reactAcrossFullWidth) {
gbc.fill = GridBagConstraints.HORIZONTAL;
}
gbc.weightx = 1;
gbc.weighty = 1;
allContainer.add(contentContainer, gbc);
final ActionListener checkBoxListener = e -> {
if (!enabled) {
return;
}
if (state == null) {
if (ThreeStateCheckBox.this.userCanAffectIndeterminateState) {
setState(false);
} else {
setState(null);
}
} else if (!state) {
setState(true);
} else {
if (ThreeStateCheckBox.this.userCanEffectIndeterminateState) {
setState(null);
} else {
setState(false);
}
}
callClientCode();
};
final MouseListener labelAndPanelListener = new MouseAdapter() {
final private ButtonModel model = checkBox.getModel();
private boolean lmbIsDown = false;
@Override
public void mousePressed(final MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) {
lmbIsDown = true;
model.setPressed(true);
model.setArmed(true);
}
}
@Override
public void mouseReleased(final MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) {
lmbIsDown = false;
model.setPressed(false);
model.setArmed(false);
}
}
@Override
public void mouseEntered(final MouseEvent e) {
model.setArmed(lmbIsDown);
}
@Override
public void mouseExited(final MouseEvent e) {
model.setArmed(false);
}
};
checkBox.addActionListener(checkBoxListener);
checkBox.addMouseListener(labelAndPanelListener);
label.addMouseListener(labelAndPanelListener);
contentContainer.addMouseListener(labelAndPanelListener);
if (reactAcrossFullHeight) {
allContainer.addMouseListener(labelAndPanelListener);
}
}
public void setEnabled(final boolean enabled) {
if (enabled == this.enabled) {
return;
}
this.enabled = enabled;
checkBox.setEnabled(enabled);
label.setEnabled(enabled);
contentContainer.setEnabled(enabled);
}
public boolean isEnabled() {
return enabled;
}
public Color getBackground() {
return allContainer.getBackground();
}
public void setBackground(final Color color) {
if (color == null) {
return;
}
checkBox.setBackground(color);
label.setBackground(color);
contentContainer.setBackground(color);
allContainer.setBackground(color);
}
public boolean isOpaque() {
return allContainer.isOpaque();
}
public void setOpaque(final boolean opaque) {
checkBox.setOpaque(opaque);
label.setOpaque(opaque);
contentContainer.setOpaque(opaque);
allContainer.setOpaque(opaque);
}
public JPanel getComponent() {
return allContainer;
}
public JPanel getCenterContainer() {
return contentContainer;
}
public JCheckBox getJCheckBox() {
return checkBox;
}
public JLabel getJLabel() {
return label;
}
public boolean canUserEffectIndeterminateState() {
return userCanEffectIndeterminateState;
}
public void setUserCanEffectIndeterminateState(final boolean userCanEffectIndeterminateState) {
this.userCanEffectIndeterminateState = userCanEffectIndeterminateState;
}
public boolean canUserAffectIndeterminateState() {
return userCanAffectIndeterminateState;
}
public void setUserCanAffectIndeterminateState(final boolean userCanAffectIndeterminateState) {
this.userCanAffectIndeterminateState = userCanAffectIndeterminateState;
}
public void setState(final Boolean newState) {
checkBox.setSelected(newState != null && newState);
if (newState == state) {
return;
}
previousState = state;
state = newState;
checkBox.repaint();
}
public Boolean getState() {
return state;
}
public Boolean getPreviousState() {
return previousState;
}
public void setClientCode(final Consumer<ThreeStateCheckBox> clientCode) {
this.clientCode = clientCode;
}
public Consumer<ThreeStateCheckBox> getClientCode() {
return clientCode;
}
public void callClientCode() {
if (clientCode != null) {
clientCode.accept(this);
}
}
public void setText(final String newText) {
if (newText == null || newText.trim().isEmpty()) {
text = null;
} else {
text = newText;
}
label.setText(text);
}
public String getText() {
return text;
}
}