java - 带有复选框的 Swing JMenu
问题描述
我需要在 JMenu 中显示一个复选框(模仿 JCheckBoxMenuItem 的功能,但在父级别)。这个功能似乎不是 Swing API 的一部分,但我想我可以通过覆盖 JMenu API 来让它工作。
这就是我希望它通过我在 Paint 中拼凑的 Mockup 的样子:
但我没有成功让复选框出现,我不知道如何强制这种行为。
这是一个代码示例,显示了我正在尝试做的事情。右键单击框架中的任意位置:
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JPopupMenu;
import javax.swing.JToggleButton;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;
public class PopupMenuExample extends JFrame {
JPopupMenu popupMenu;
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
try {
UIManager.setLookAndFeel(info.getClassName());
} catch (Exception e) {
e.printStackTrace();
}
break;
}
}
PopupMenuExample frame = new PopupMenuExample ();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setSize(400, 400);
frame.setVisible(true);
}
});
}
public PopupMenuExample () {
popupMenu = new JPopupMenu();
popupMenu.add(createMenu("Displayed Items"));
this.addMouseListener(new MouseAdapter() {
public void mouseReleased(MouseEvent Me) {
if (Me.isPopupTrigger()) {
popupMenu.show(Me.getComponent(), Me.getX(), Me.getY());
}
}
});
}
public JMenu createMenu(String title) {
JCheckBoxMenu m = new JCheckBoxMenu(title, true);
JCheckBoxMenuItem tractorsMenuItem = new JCheckBoxMenuItem("Tractor", true);
JCheckBoxMenu carsMenu = new JCheckBoxMenu("Cars", true);
JCheckBoxMenuItem chevy = new JCheckBoxMenuItem("Chevy", true);
JCheckBoxMenuItem fusion = new JCheckBoxMenuItem("Ford", true);
JCheckBoxMenuItem tesla = new JCheckBoxMenuItem("Tesla", true);
carsMenu.add(chevy);
carsMenu.add(fusion);
carsMenu.add(tesla);
JCheckBoxMenuItem tankMenuItem = new JCheckBoxMenuItem("Tank", true);
m.add(tractorsMenuItem);
m.add(carsMenu);
m.add(tankMenuItem);
return m;
}
// Trying to mimic how JCheckBoxMenuItem works:
// https://github.com/Himansu-Nayak/java7-sourcecode/blob/master/javax/swing/JCheckBoxMenuItem.java
public static class JCheckBoxMenu extends JMenu {
public JCheckBoxMenu(String text, boolean selected) {
super(text);
setModel(new JToggleButton.ToggleButtonModel());
setSelected(selected);
setFocusable(false);
}
}
}
解决方案
我必须同意@Andrew Thompson 关于将 aJMenu
用于工作的原因,原因如下:
- 目前尚不清楚菜单旁边的复选标记的实际含义。(同样的问题实际上也适用于带有复选框的树)。
- 使用菜单结构的导航不是最佳的,例如,如果您不小心点击了错误的位置,菜单就会关闭。也与大多数 lafs 点击 a
JMenuItem
将实际上关闭整个JPopupMenu
。如果您想切换多个项目,则不理想。
使用 aJTree
来管理状态的建议解决方案是我采用的解决方案。因为JPopupMenu
s 无法聚焦,所以需要创建弹出窗口并显式处理。在解释过程中,我将引用我的答案末尾可以找到的每个实现类。
因为即使使用显示复选标记的 a ,它的确切含义也JTree
可能模棱两可值(叶子只允许有or状态)。SELECTED
DELSELECTED
INDETERMINATE
INDETERMINATE
SELECTED
DESELECTED
首先,我们需要树的模型。TristateNode
这是自动管理子节点和父节点状态的简单实现。如果具有子节点的节点被选中/取消选中,所有下降节点将自动被选中/取消选中(TristateNode
和TristateState
)。
现在我们需要为树提供渲染器和编辑器。渲染器使用一个图标来显示当前TristateState
节点。我目前使用纯色,但你可以做任何你喜欢的事情。编辑器实际上并没有做任何事情。每次发送编辑请求时,它都会迭代目标节点的状态,并告诉树不要编辑。这样我们就不必提供额外的编辑组件。( CheckboxTree
、CheckBoxTreeRenderer
和CheckBoxTreeEditor
) TristateIcon
。
现在对于我们显示弹出窗口以显示树的部分。因为让弹出窗口始终可见可能很方便,所以我将在弹出窗口中添加一个“Pin”按钮,将其迁移到完整的JDialog
. 如果弹出/对话框当前可见,我们只需将显示的组件切换为新组件(TreePopupManager
和TreePopupContent
)。
对于演示,我创建了一个简单的面板,其中包含 4 个可以使用鼠标选择的区域。通过使用鼠标右键,弹出窗口将显示在鼠标位置。单击其他任何地方将隐藏弹出窗口。如果弹出窗口当前被固定,则聚焦任何组件将自动更新弹出窗口内容。
这是它所需的所有代码:
import java.awt.*;
import java.awt.event.*;
import java.util.EventObject;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.event.CellEditorListener;
import javax.swing.tree.*;
public class Test {
public static void main(final String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(createContentPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
private static JComponent createContentPane() {
return new DemoContent();
}
private static void drawRect(final Graphics g, final int x, final int y, final int w, final int h) {
g.fillRect(x, y, w, 1);
g.fillRect(x, y, 1, h);
g.fillRect(x + w - 1, y, 1, h);
g.fillRect(x, y + h - 1, w, 1);
}
private static class TreePopupManager {
private static final TreePopupManager INSTANCE = new TreePopupManager();
private final TreePopupContent contentPanel = new TreePopupContent(this::pin);
private Popup popup;
private Component owner;
private Point lastPos;
private JDialog dialog;
public TreePopupManager() {
Toolkit.getDefaultToolkit().addAWTEventListener(e -> {
if (popup != null) {
if (e instanceof FocusEvent) {
Component oppositeComp = ((FocusEvent) e).getOppositeComponent();
Component comp = ((FocusEvent) e).getComponent();
int id = e.getID();
if (id == FocusEvent.FOCUS_LOST) {
if (oppositeComp == null
|| !SwingUtilities.isDescendingFrom(oppositeComp, contentPanel)) {
hidePopup();
}
} else if (id == FocusEvent.FOCUS_GAINED) {
if (comp == null
|| !SwingUtilities.isDescendingFrom(comp, contentPanel)) {
hidePopup();
}
}
} else if (e instanceof MouseEvent && e.getID() == MouseEvent.MOUSE_PRESSED) {
Component component = ((MouseEvent) e).getComponent();
if (!SwingUtilities.isDescendingFrom(component, contentPanel)) {
hidePopup();
}
}
}
}, AWTEvent.FOCUS_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK);
}
private void pin() {
hidePopup();
if (dialog == null) {
dialog = new JDialog(owner != null ? SwingUtilities.getWindowAncestor(owner) : null);
dialog.setContentPane(contentPanel);
contentPanel.setPinned(true);
dialog.pack();
dialog.setLocation(lastPos);
dialog.setVisible(true);
}
}
private void hidePopup() {
if (popup != null) {
popup.hide();
popup = null;
}
}
private void hideDialog() {
if (dialog != null) {
dialog.setVisible(false);
dialog.dispose();
dialog = null;
}
}
public static TreePopupManager getInstance() {
return INSTANCE;
}
public boolean isPopupVisible() {
return popup != null || dialog != null;
}
public void showPopup(final Component parent, final JTree content, final Point p, final String title) {
contentPanel.setTitle(title);
contentPanel.setContent(content);
if (dialog != null && !dialog.isVisible()) {
hideDialog();
}
if (popup == null && dialog == null) {
contentPanel.setPinned(false);
lastPos = p;
owner = parent;
popup = PopupFactory.getSharedInstance().getPopup(parent, contentPanel, p.x, p.y);
popup.show();
}
}
}
private static class TreePopupContent extends JPanel {
private final JPanel view = new JPanel(new BorderLayout());
private final JLabel label = new JLabel();
private final JButton pinButton = new JButton("Pin");
public TreePopupContent(final Runnable onPin) {
super(new BorderLayout());
JScrollPane scrollPane = new JScrollPane(view) {
@Override
public void setBorder(final Border border) {}
};
add(scrollPane);
Box box = Box.createHorizontalBox();
box.add(Box.createHorizontalStrut(5));
box.add(label);
box.add(Box.createHorizontalGlue());
pinButton.setMargin(new Insets(0, 0, 0, 0));
pinButton.setFocusable(false);
pinButton.setFocusPainted(false);
pinButton.addActionListener(e -> onPin.run());
box.add(pinButton);
box.add(Box.createHorizontalStrut(5));
add(box, BorderLayout.NORTH);
}
public void setPinned(final boolean pinned) {
pinButton.setVisible(!pinned);
}
@Override
public void updateUI() {
super.updateUI();
setBorder(new JPopupMenu().getBorder());
}
public void setTitle(final String title) {
label.setText(title);
}
public void setContent(final JComponent content) {
setBackground(content.getBackground());
view.removeAll();
view.add(content);
revalidate();
repaint();
Dimension pref = view.getPreferredSize();
setPreferredSize(new Dimension(Math.max(pref.width, 100) + 10,
Math.max(pref.height, 200) + 10));
}
}
private static class DemoContent extends JPanel {
private DemoContent() {
setPreferredSize(new Dimension(300, 300));
setLayout(new BorderLayout());
setLayout(new GridLayout(2, 2));
add(new DemoContentPanel(new CheckBoxTree(populate(new TristateNode("root1"), 1, 1))));
add(new DemoContentPanel(new CheckBoxTree(populate(new TristateNode("root2"), 2, 2))));
add(new DemoContentPanel(new CheckBoxTree(populate(new TristateNode("root3"), 3, 3))));
add(new DemoContentPanel(new CheckBoxTree(populate(new TristateNode("root4"), 4, 4))));
}
private TristateNode populate(final TristateNode node, final int count, final int depth) {
if (depth == 0) return node;
for (int i = 0; i < count; i++) {
node.add(populate(new TristateNode("Node-" + depth + "-" + i), count, depth - 1));
}
return node;
}
}
private static class DemoContentPanel extends JPanel {
private final CheckBoxTree tree;
private DemoContentPanel(final CheckBoxTree tree) {
this.tree = tree;
setFocusable(true);
addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
requestFocus();
onMousePressed(e);
}
});
addFocusListener(new FocusListener() {
@Override
public void focusGained(final FocusEvent e) {
repaint();
}
@Override
public void focusLost(final FocusEvent e) {
repaint();
}
});
}
@Override
protected void paintComponent(final Graphics g) {
super.paintComponent(g);
if (hasFocus()) {
g.setColor(Color.RED);
} else {
g.setColor(Color.BLACK);
}
drawRect(g, 0, 0, getWidth(), getHeight());
}
protected void onMousePressed(final MouseEvent e) {
SwingUtilities.invokeLater(() -> {
TreePopupManager manager = TreePopupManager.getInstance();
if (SwingUtilities.isRightMouseButton(e) || manager.isPopupVisible()) {
Point p = e.getPoint();
SwingUtilities.convertPointToScreen(p, this);
manager.showPopup(this, tree, p, "Tree");
}
});
}
}
private static class CheckBoxTree extends JTree {
public CheckBoxTree(final TristateNode root) {
super(root);
setShowsRootHandles(true);
setCellRenderer(new CheckBoxTreeRenderer());
setCellEditor(new CheckBoxTreeEditor(this));
setEditable(true);
}
}
private static class CheckBoxTreeRenderer extends DefaultTreeCellRenderer {
private final TristateIcon icon = new TristateIcon();
@Override
public Component getTreeCellRendererComponent(final JTree tree, final Object value, final boolean sel,
final boolean expanded, final boolean leaf,
final int row, final boolean hasFocus) {
Component component = super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
JLabel label = (JLabel) component; // DefaultTreeCellRenderer uses JLabel as its renderer;
TristateNode node = (TristateNode) value;
label.setText(node.getLabel());
label.setIcon(icon);
icon.setState(node.getState());
return label;
}
}
private static class CheckBoxTreeEditor implements TreeCellEditor {
private final JTree tree;
public CheckBoxTreeEditor(final JTree tree) {
this.tree = tree;
}
@Override
public Component getTreeCellEditorComponent(final JTree tree, final Object value,
final boolean isSelected,
final boolean expanded,
final boolean leaf, final int row) {
return null;
}
@Override
public Object getCellEditorValue() {
return ((TristateNode) tree.getEditingPath().getLastPathComponent()).getState();
}
@Override
public boolean isCellEditable(final EventObject anEvent) {
if (!(anEvent instanceof InputEvent)) return false;
InputEvent event = (InputEvent) anEvent;
Object source = event.getSource();
if (!(source instanceof JTree)) return false;
JTree tree = (JTree) source;
Object value = null;
if (event instanceof MouseEvent) {
Point p = ((MouseEvent) event).getPoint();
TreePath path = tree.getPathForLocation(p.x, p.y);
if (path != null) {
value = path.getLastPathComponent();
}
} else {
value = tree.getLeadSelectionPath().getLastPathComponent();
}
if (value instanceof TristateNode) {
((TristateNode) value).iterateState();
tree.repaint();
}
return false;
}
@Override
public boolean shouldSelectCell(final EventObject anEvent) {
return false;
}
@Override
public boolean stopCellEditing() {
return false;
}
@Override
public void cancelCellEditing() {}
@Override
public void addCellEditorListener(final CellEditorListener l) {}
@Override
public void removeCellEditorListener(final CellEditorListener l) {}
}
private static class TristateNode extends DefaultMutableTreeNode {
private String label;
public TristateNode() {
this(null);
}
public TristateNode(final String label) {
this(label, TristateState.DESELECTED);
}
public TristateNode(final String label, final TristateState state) {
this(label, state, true);
}
public TristateNode(final String label, final TristateState state, final boolean allowsChildren) {
super();
parent = null;
this.allowsChildren = allowsChildren;
this.userObject = state;
this.label = label;
}
@Override
public void add(final MutableTreeNode newChild) {
if (!(newChild instanceof TristateNode)) {
throw new IllegalArgumentException("Only children of type TristateTreeNode are allowed.");
}
super.add(newChild);
}
public TristateState getState() {
return getUserObject();
}
public void setState(final TristateState state) {
setState(state, false, false);
}
private void setState(final TristateState state, final boolean invokedByParent, final boolean invokedByChild) {
if (isLeaf() && state == TristateState.INDETERMINATE) {
throw new IllegalArgumentException("Leaf nodes cannot have an indeterminate state");
}
super.setUserObject(state);
if (!isLeaf() && !invokedByChild) {
if (state != TristateState.INDETERMINATE) {
for (TreeNode node : children) {
if (node instanceof TristateNode) {
((TristateNode) node).setState(state, true, false);
}
}
}
}
if (!invokedByParent) {
Object treeNode = getParent();
if (treeNode instanceof TristateNode) {
((TristateNode) treeNode).setState(((TristateNode) treeNode).getEffectiveState(), false, true);
}
}
}
public void setSelected(final boolean selected) {
this.userObject = selected;
}
@Override
public TristateState getUserObject() {
return (TristateState) super.getUserObject();
}
@Override
public void setUserObject(final Object userObject) {
if (!(userObject instanceof TristateState)) {
throw new IllegalArgumentException("Only values of type TristateState are allowed but got "
+ userObject);
}
setState((TristateState) userObject);
}
public TristateState getEffectiveState() {
if (isLeaf()) return getState();
TristateState state = null;
for (TreeNode node : children) {
if (node instanceof TristateNode) {
TristateState nodeState = ((TristateNode) node).getState();
if (state == null) state = nodeState;
if (state != nodeState) {
state = TristateState.INDETERMINATE;
break;
}
}
}
return state != null ? state : TristateState.DESELECTED;
}
public String getLabel() {
return label;
}
public void setLabel(final String label) {
this.label = label;
}
public void iterateState() {
TristateState state = getState();
switch (state) {
case DESELECTED :
setState(TristateState.SELECTED);
break;
case SELECTED :
setState(TristateState.DESELECTED);
break;
case INDETERMINATE :
setState(state.next());
break;
}
}
}
private enum TristateState {
DESELECTED() {
@Override
public TristateState next() {
return INDETERMINATE;
}
},
SELECTED() {
@Override
public TristateState next() {
return DESELECTED;
}
},
INDETERMINATE() {
@Override
public TristateState next() {
return SELECTED;
}
};
public abstract TristateState next();
}
private static class TristateIcon implements Icon {
private TristateState state = TristateState.DESELECTED;
@Override
public void paintIcon(final Component c, final Graphics g, final int x, final int y) {
switch (state) {
case DESELECTED :
g.setColor(Color.RED);
break;
case SELECTED :
g.setColor(Color.GREEN);
break;
case INDETERMINATE :
g.setColor(Color.ORANGE);
break;
default :
return;
}
g.fillRect(x, y, getIconWidth(), getIconHeight());
}
public void setState(final TristateState state) {
this.state = state;
}
@Override
public int getIconWidth() {
return 16;
}
@Override
public int getIconHeight() {
return 16;
}
}
}
结果如下:
推荐阅读
- python - “分数”类型与分数一起出现在我的打印列表中
- opensuse - 出于审计目的,需要提供通过 zypper patch 命令应用的所有补丁。如何列出最近 3 个月内安装的所有补丁
- c++ - 为什么我不能从函数返回的流的引用中读取?
- c# - 我正在使用 ASP.Net 实现 DocuSign API,但是在每次发送信封并重定向到 DocuSign 站点时需要登录到 DocuSign 站点?
- java - Liferay:执行索引操作时发生错误
- agora.io - 在 Android 中未收到来自 iOS 的 RTM 消息
- xgboost - xgboost.core.XGBoostError:不支持 Unicode
- batch-file - 批处理命令删除文件中的“
- c# - Nuget - “无法加载文件或程序集‘{类名},版本 = xxx,文化 = 中性,PublicKeyToken = null’或其依赖项之一”
- javascript - 如何用 split() 为表拆分字符串