JCheckbox
以下に示すように、3つの状態を持つが必要です。
- 部分的に選択
- 未選択
- フルセレクション
Q1。JCheckbox
上記の目的でを使用できますか、それともカスタムスイングコンポーネントを使用する必要がありますか?
java apiがpaintIcon(..)メソッドをオーバーライドするための優れた機能を提供しているのに、追加のアイコンpngファイルを使用してソリューションを提供する理由がわかりません。
トライステート CheckBox スタンスを決定する最適な軽量ソリューションは、IMO ClientProperty属性です。
/*
* Tri-state checkbox example
* @s1w_
*/
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
class TCheckBox extends JCheckBox implements Icon, ActionListener {
final static boolean MIDasSELECTED = true; //consider mid-state as selected ?
public TCheckBox() { this(""); }
public TCheckBox(String text) {
super(text);
putClientProperty("SelectionState", 0);
setIcon(this);
addActionListener(this);
}
public TCheckBox(String text, int sel) {
/* tri-state checkbox has 3 selection states:
* 0 unselected
* 1 mid-state selection
* 2 fully selected
*/
super(text, sel > 1 ? true : false);
switch (sel) {
case 2: setSelected(true);
case 1:
case 0:
putClientProperty("SelectionState", sel);
break;
default:
throw new IllegalArgumentException();
}
addActionListener(this);
setIcon(this);
}
@Override
public boolean isSelected() {
if (MIDasSELECTED && (getSelectionState() > 0)) return true;
else return super.isSelected();
}
public int getSelectionState() {
return (getClientProperty("SelectionState") != null ? (int)getClientProperty("SelectionState") :
super.isSelected() ? 2 :
0);
}
public void setSelectionState(int sel) {
switch (sel) {
case 2: setSelected(true);
break;
case 1:
case 0: setSelected(false);
break;
default: throw new IllegalArgumentException();
}
putClientProperty("SelectionState", sel);
}
final static Icon icon = UIManager.getIcon("CheckBox.icon");
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
icon.paintIcon(c, g, x, y);
if (getSelectionState() != 1) return;
int w = getIconWidth();
int h = getIconHeight();
g.setColor(c.isEnabled() ? new Color(51, 51, 51) : new Color(122, 138, 153));
g.fillRect(x+4, y+4, w-8, h-8);
if (!c.isEnabled()) return;
g.setColor(new Color(81, 81, 81));
g.drawRect(x+4, y+4, w-9, h-9);
}
@Override
public int getIconWidth() {
return icon.getIconWidth();
}
@Override
public int getIconHeight() {
return icon.getIconHeight();
}
public void actionPerformed(ActionEvent e) {
TCheckBox tcb = (TCheckBox)e.getSource();
if (tcb.getSelectionState() == 0)
tcb.setSelected(false);
tcb.putClientProperty("SelectionState", tcb.getSelectionState() == 2 ? 0 :
tcb.getSelectionState() + 1);
// test
System.out.println(">>>>IS SELECTED: "+tcb.isSelected());
System.out.println(">>>>IN MID STATE: "+(tcb.getSelectionState()==1));
}
}
利用方法:TCheckBox tcb = new TCheckBox("My CheckBox");
UI 要素 JCheckBox は直接サポートしていませんが、だますことができます。
//selected
checkbox.setSelected(true);
//partially selected
checkbox.getModel().setPressed(true);
checkbox.getModel().setArmed(true);
//not selected
checkbox.setSelected(false);
もちろん、これは UI 部分にすぎません。カスタム モデルを使用してステート マシンを実装する必要があります。
チェックボックスのツリーを作成しようとしている場合 (トライステート チェックボックスが必要になる一般的な理由です)、Jide Commonsを確認してください。
チェックボックスの状態を保持するものが必要になります。カスタムモデルを使用しています。
@Override
protected void paintComponent(Graphics g) {
getModel().setArmedForPaint(isPartial());
getModel().setPressedForPaint(isPartial());
super.paintComponent(g);
getModel().setArmedForPaint(null);
getModel().setPressedForPaint(null);
}
カスタム ToggleButtonModel で
@Override
public boolean isArmed() {
if (armedForPaint != null) {
return armedForPaint.booleanValue();
}
return super.isArmed();
}
@Override
public boolean isPressed() {
if (pressedForPaint != null) {
return pressedForPaint.booleanValue();
}
return super.isPressed();
}
Java 8 で ThreeStateCheckBox クラスを作成しました。これは、JCheckBox を採用/模倣し、調整可能な動作を備えています (たとえば、コンポーネントがレイアウトによって引き伸ばされた場合、全幅、全高で反応し、どこに配置する必要がありますか?ユーザーは不確定な状態に影響を与えることができますか? ユーザーはそれに影響を与えることができますか?)、すべての標準の LookAndFeels で適切に見えます (ただし、Nimbus を使用する場合は、追加の引数を指定する必要があります)。最後の5時間かかりました。
デモ コードは、クラスの先頭に含まれています。編集: スクリーンショット
概念:
テキストのない通常の 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;
/**
* ThreeStateCheckBox by dreamspacepresident.com
* <p>
* v[1, 2015-11-20 13!00 UTC]
*/
final public class ThreeStateCheckBox {
//////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
// MAIN METHOD FOR TESTS/DEMONSTRATION
//////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Two checkboxes are shown. The upper one is TreeStateCheckBox, the lower one is the classic JCheckBox.
*/
public static void main(final String[] args) {
SwingUtilities.invokeLater(() -> {
// PICK A LOOKANDFEEL
final String lookAndFeelName;
lookAndFeelName = "Nimbus";
// lookAndFeelName = "Metal";
// lookAndFeelName = "CDE/Motif";
// lookAndFeelName = "Windows";
// lookAndFeelName = "Windows Classic";
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);
}
// CREATE AND INITIALIZE COMPONENTS
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<>(); // Originally, I had more demos planned. I'll leave this here in case you need it.
ThreeStateCheckBox cbThreeState;
JCheckBox cbClassic;
// contentPane.setBackground(Color.MAGENTA);
cbThreeState = new ThreeStateCheckBox(cbDemoText, null, false, true, null, null, null, null, false, false, TSCBOrientation.WEST, true, null); // NIMBUS DEMO (because of the gap)
// cbThreeState = new ThreeStateCheckBox(cbDemoText);
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.setEnabled(false);
// cbClassic.setEnabled(false);
// cbClassic.setBackground(Color.ORANGE);
// cbThreeState.setBackground(Color.ORANGE);
// cbClassic.setOpaque(true);
// cbThreeState.setOpaque(true);
// cbThreeState.setText("italic");
// cbClassic.setText("italian");
cbThreeState.setUserCanEffectIndeterminateState(true);
// LAYOUT COMPONENTS
cbList.forEach(contentPane::add);
// INITIALIZE AND SHOW WINDOW
window.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
window.setContentPane(contentPane);
// window.pack();
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();
});
}
//////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
// THE ACTUAL CLASS BEGINS HERE
//////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
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; // Extra step to ensure JCheckBox will have its minimum size.
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);
}
/**
* The all-constructor, called by the others.
*
* @param text checkbox text
* @param initialState whether the checkbox initially is not selelected (false),
* selected (true), or in indeterminate state (null)
* @param userCanEffectIndeterminateState Seriously, it's not that hard.
* @param userCanAffectIndeterminateState So learn it already.
* @param colorOfIndeterminateRectangle When the state is indeterminate, a filled rectangle (usually
* square) will be drawn. The default color (if value here is null)
* is a very dark gray which visually works with all LookAndFeels.
* (This setting can not be changed later.)
* @param colorOfIndeterminateRectangleWhenDisabled Same, except for when the component is not enabled. The color is
* the same, except it's only half opaque.
* @param widthOfIndeterminateRectangle null = default of 8, otherwise the size of the
* indeterminate-rectangle can be defined here. (This setting can
* not be changed later.)
* @param heightOfIndeterminateRectangle Same.
* @param reactAcrossFullWidth The default behavior for standard JCheckBoxes is true - no
* matter how far to the right of the box you click, even if there
* is no text at all, the box will be clicked. The default behavior
* (as per the other constructors) for ThreeStateCheckBox in this
* regard is FALSE, however. (This setting can not be changed
* later.)
* @param reactAcrossFullHeight The default behavior for standard JCheckBoxes is true - no
* matter how empty the space above and below is, the box will be
* clicked. The default behavior (as per the other constructors)
* for ThreeStateCheckBox in this regard is FALSE, however. If this
* setting is true, the same setting for width is effectively true,
* too, nothing you can do about that. (This setting can not be
* changed later.)
* @param orientationInAvailableSpace If the component is stretched via its placement in the layout
* hierarchy, as opposed to the JCheckBox, the ThreeStateCheckBox
* can be positioned in 9 places. (This setting can not be changed
* later.)
* @param leaveGapBetweenBoxAndLabel Even though JCheckBox's getIconTextGap() is queried to effect a
* proper distance, this works only correctly for Nimbus. In all
* other cases, the gap is too big compared to the original
* JCheckBox. Setting this to false fixes the behavior for all
* other LookAndFeels, because it just leave *no* gap at all.
* @param clientCode The code to be executed if THE USER clicks the checkbox. The
* code will receive a reference to the ThreeStateCheckBox, so they
* can query the new (=current) and previous state of it.
*/
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) {
// INITIALIZE CONSTRUCTOR VALUES
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;
// CREATE COMPONENTS
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());
// INITIALIZE COMPONENTS
setOpaque(false);
if (state != null && state) {
checkBox.setSelected(true);
}
setText(text);
// LAYOUT COMPONENTS
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);
// CREATE LISTENERS
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);
}
};
// ASSIGN LISTENERS
checkBox.addActionListener(checkBoxListener);
checkBox.addMouseListener(labelAndPanelListener); // Necessary or the behavior will look different when the mouse moves while button is down.
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;
}
/**
* Sets the state to the new state IF IT IS DIFFERENT, which is relevant because of the previousState variable.
* <p>
* Does NOT call the client code! You would have to do this yourself.
*
* @param newState null = indeterminate, false = not selected, true = selected
*/
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;
}
/**
* Convenience method so that you don't have to keep the reference around in case you need an extra call.
*/
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;
}
}