我试图在 Swing 中制作剧透效果(就像 HTML 中的
<summary>/<details>
标签)。但是,如果我切换 setVisible()
方法,我的父容器的高度将无法正确计算。
我尝试显示和隐藏的面板的所有父容器都使用
BoxLayout
和页面轴。
这是我的代码:
public class Entry extends javax.swing.JPanel {
public Entry() {
initComponents();
}
public Entry(Node node) {
this.node = node;
initComponents();
initEvents();
}
private void initEvents() {
marker.addMouseListener(new MouseListener() {
@Override
public void mouseClicked(MouseEvent e) {
if (!opened) open();
else close();
}
@Override
public void mousePressed(MouseEvent e) {}
@Override
public void mouseReleased(MouseEvent e) {}
@Override
public void mouseEntered(MouseEvent e) {}
@Override
public void mouseExited(MouseEvent e) {}
});
addMouseListener(listener);
}
public void addChild(Entry child, int pos) {
content.add(child, pos);
//content.validate();
}
public void inflate(int width) {
if (node == null) return;
if (node.nodeType == 1) {
boolean isPaired = !TagLibrary.tags.containsKey(node.tagName.toLowerCase()) ||
TagLibrary.tags.get(node.tagName.toLowerCase());
if (!isPaired) {
headerTag.setText("<" + node.tagName.toLowerCase());
headerTag2.setText(" />");
threeDots.setText("");
headerTag3.setText("");
content.setVisible(false);
footer.setVisible(false);
marker.setVisible(false);
} else {
headerTag.setText("<" + node.tagName.toLowerCase());
headerTag2.setText(">");
headerTag3.setText("</" + node.tagName.toLowerCase() + ">");
footerTag.setText("</" + node.tagName.toLowerCase() + ">");
}
int w = Math.max(Math.max(header.getMinimumSize().width, min_width), width - margin);
content.removeAll();
//System.out.println(getWidth());
for (int i = 0; i < node.children.size(); i++) {
Entry e = new Entry(node.children.get(i));
content.add(e);
e.inflate(w);
}
content.doLayout();
if (node.children.size() > 0) {
open();
} else {
close();
}
} else if (node.nodeType == 3 && !node.nodeValue.matches("\\s*")) {
content.removeAll();
header.setVisible(false);
footer.setVisible(false);
JTextArea textarea = new JTextArea();
textarea.setText(node.nodeValue);
textarea.setEditable(false);
textarea.setOpaque(false);
textarea.setBackground(new Color(255, 255, 255, 0));
textarea.setColumns(180);
textarea.setFont(new Font("Tahoma", Font.PLAIN, 16));
int rows = node.nodeValue.split("\n").length;
textarea.setRows(rows);
textarea.addMouseListener(listener);
int height = getFontMetrics(textarea.getFont()).getHeight() * rows;
content.add(textarea);
content.setOpaque(false);
int w = Math.max(Math.max(header.getMinimumSize().width, min_width), width - margin);
header.setMinimumSize(new Dimension(w, line_height));
footer.setMinimumSize(new Dimension(w, line_height));
content.setPreferredSize(new Dimension(w, content.getPreferredSize().height));
((JPanel)getParent()).setMinimumSize(new Dimension(w, line_height * 2 + content.getPreferredSize().height));
opened = true;
content.validate();
} else {
setVisible(false);
content.removeAll();
opened = false;
return;
}
int w = Math.max(Math.max(Math.max(content.getMinimumSize().width, header.getMinimumSize().width), min_width), width - margin);
header.setMinimumSize(new Dimension(w, line_height));
footer.setMinimumSize(new Dimension(w, line_height));
content.setPreferredSize(new Dimension(w, content.getPreferredSize().height));
int height = line_height * 2 + content.getPreferredSize().height;
if (opened) {
setSize(w, height);
}
}
public void setWidth(int width) {
int w = Math.max(Math.max(header.getMinimumSize().width, min_width), width - margin);
setPreferredSize(new Dimension(w, getPreferredSize().height));
header.setMinimumSize(new Dimension(w, line_height));
footer.setMinimumSize(new Dimension(w, line_height));
content.setPreferredSize(new Dimension(w, content.getPreferredSize().height));
Component[] c = content.getComponents();
for (int i = 0; i < c.length; i++) {
if (c[i] instanceof Entry) {
((Entry)c[i]).setWidth(w);
} else {
c[i].setSize(w, c[i].getMaximumSize().height);
c[i].setMaximumSize(new Dimension(w, c[i].getMaximumSize().height));
}
}
}
public static final int min_width = 280;
public static final int line_height = 26;
public static final int margin = 30;
MouseListener listener = new MouseListener() {
@Override
public void mouseClicked(MouseEvent e) {}
@Override
public void mousePressed(MouseEvent e) {}
@Override
public void mouseReleased(MouseEvent e) {}
@Override
public void mouseEntered(MouseEvent e) {
updateChildren(true);
repaint();
}
@Override
public void mouseExited(MouseEvent e) {
updateChildren(false);
repaint();
}
};
private void updateChildren(boolean value) {
hovered = value;
Component[] c = content.getComponents();
for (int i = 0; i < c.length; i++) {
if (c[i] instanceof Entry) {
((Entry)c[i]).updateChildren(value);
}
}
}
@Override
public void paintComponent(Graphics g) {
if (hovered) {
g.clearRect(0, 0, getWidth(), getHeight());
g.setColor(new Color(190, 230, 255, 93));
g.fillRect(0, 0, getWidth(), getHeight());
} else {
g.clearRect(0, 0, getWidth(), getHeight());
g.setColor(new Color(255, 255, 255));
g.fillRect(0, 0, getWidth(), getHeight());
}
//super.paintComponent(g);
}
private boolean hovered = false;
public void open() {
marker.setIcon(new javax.swing.ImageIcon(getClass().getResource("/resources/triangle.png")));
threeDots.setVisible(false);
headerTag3.setVisible(false);
content.setVisible(true);
footer.setVisible(true);
opened = true;
//getParent().getParent().setPreferredSize(new Dimension(getParent().getPreferredSize().width, getParent().getPreferredSize().height + delta));
//getParent().getParent().setPreferredSize(new Dimension(getParent().getParent().getSize().width, getParent().getParent().getSize().height + delta));
//((JComponent)getParent()).validate();
}
public void close() {
marker.setIcon(new javax.swing.ImageIcon(getClass().getResource("/resources/triangle2.png")));
content.setVisible(false);
footer.setVisible(false);
threeDots.setVisible(has_children);
marker.setVisible(has_children);
headerTag3.setVisible(true);
opened = false;
//getParent().setPreferredSize(new Dimension(getParent().getPreferredSize().width, getParent().getPreferredSize().height - delta));
//getParent().getParent().setPreferredSize(new Dimension(getParent().getParent().getSize().width, getParent().getParent().getSize().height - delta));
//((JComponent)getParent().getParent()).revalidate();
}
public void openAll() {
open();
Component[] c = content.getComponents();
for (int i = 0; i < c.length; i++) {
if (c[i] instanceof Entry) {
((Entry) c[i]).openAll();
}
}
}
public void closeAll() {
close();
Component[] c = content.getComponents();
for (int i = 0; i < c.length; i++) {
if (c[i] instanceof Entry) {
((Entry) c[i]).closeAll();
}
}
}
public boolean opened = false;
public Node node;
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">
private void initComponents() {
header = new javax.swing.JPanel();
headerMargin = new javax.swing.JPanel();
marker = new javax.swing.JLabel();
headerTag = new javax.swing.JLabel();
attributes = new javax.swing.JPanel();
headerTag2 = new javax.swing.JLabel();
threeDots = new javax.swing.JLabel();
headerTag3 = new javax.swing.JLabel();
content = new javax.swing.JPanel();
footer = new javax.swing.JPanel();
footerMargin = new javax.swing.JPanel();
footerTag = new javax.swing.JLabel();
setBackground(new java.awt.Color(255, 255, 255));
setLayout(new javax.swing.BoxLayout(this, javax.swing.BoxLayout.PAGE_AXIS));
header.setBackground(new java.awt.Color(255, 255, 255));
header.setAlignmentX(0.0F);
header.setMaximumSize(new java.awt.Dimension(32767, 26));
header.setMinimumSize(new java.awt.Dimension(280, 26));
header.setOpaque(false);
header.setPreferredSize(new java.awt.Dimension(280, 26));
header.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.LEADING, 0, 2));
headerMargin.setBorder(javax.swing.BorderFactory.createEmptyBorder(2, 0, 0, 5));
headerMargin.setMaximumSize(new java.awt.Dimension(30, 26));
headerMargin.setOpaque(false);
headerMargin.setPreferredSize(new java.awt.Dimension(30, 26));
marker.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING);
marker.setIcon(new javax.swing.ImageIcon(getClass().getResource("/resources/triangle.png"))); // NOI18N
marker.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR));
marker.setPreferredSize(new java.awt.Dimension(22, 22));
javax.swing.GroupLayout headerMarginLayout = new javax.swing.GroupLayout(headerMargin);
headerMargin.setLayout(headerMarginLayout);
headerMarginLayout.setHorizontalGroup(
headerMarginLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(headerMarginLayout.createSequentialGroup()
.addComponent(marker, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
headerMarginLayout.setVerticalGroup(
headerMarginLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(headerMarginLayout.createSequentialGroup()
.addComponent(marker, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
header.add(headerMargin);
headerTag.setFont(new java.awt.Font("Arial", 1, 16)); // NOI18N
headerTag.setForeground(new java.awt.Color(102, 0, 153));
headerTag.setText("<body");
header.add(headerTag);
attributes.setMaximumSize(new java.awt.Dimension(32767, 26));
attributes.setOpaque(false);
attributes.setPreferredSize(new java.awt.Dimension(0, 26));
attributes.setLayout(new javax.swing.BoxLayout(attributes, javax.swing.BoxLayout.LINE_AXIS));
header.add(attributes);
headerTag2.setFont(new java.awt.Font("Arial", 1, 16)); // NOI18N
headerTag2.setForeground(new java.awt.Color(102, 0, 153));
headerTag2.setText(">");
header.add(headerTag2);
threeDots.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
threeDots.setText("...");
threeDots.setPreferredSize(new java.awt.Dimension(19, 20));
header.add(threeDots);
headerTag3.setFont(new java.awt.Font("Arial", 1, 16)); // NOI18N
headerTag3.setForeground(new java.awt.Color(102, 0, 153));
headerTag3.setText("</body>");
header.add(headerTag3);
add(header);
content.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 30, 0, 0));
content.setAlignmentX(0.0F);
content.setOpaque(false);
content.setLayout(new javax.swing.BoxLayout(content, javax.swing.BoxLayout.PAGE_AXIS));
add(content);
footer.setBackground(new java.awt.Color(255, 255, 255));
footer.setAlignmentX(0.0F);
footer.setMaximumSize(new java.awt.Dimension(32767, 26));
footer.setOpaque(false);
footer.setPreferredSize(new java.awt.Dimension(91, 26));
footer.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.LEADING, 0, 2));
footerMargin.setOpaque(false);
footerMargin.setPreferredSize(new java.awt.Dimension(30, 26));
javax.swing.GroupLayout footerMarginLayout = new javax.swing.GroupLayout(footerMargin);
footerMargin.setLayout(footerMarginLayout);
footerMarginLayout.setHorizontalGroup(
footerMarginLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 30, Short.MAX_VALUE)
);
footerMarginLayout.setVerticalGroup(
footerMarginLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 26, Short.MAX_VALUE)
);
footer.add(footerMargin);
footerTag.setFont(new java.awt.Font("Arial", 1, 16)); // NOI18N
footerTag.setForeground(new java.awt.Color(102, 0, 153));
footerTag.setText("</body>");
footer.add(footerTag);
add(footer);
}// </editor-fold>
// Variables declaration - do not modify
private javax.swing.JPanel attributes;
private javax.swing.JPanel content;
private javax.swing.JPanel footer;
private javax.swing.JPanel footerMargin;
private javax.swing.JLabel footerTag;
private javax.swing.JPanel header;
private javax.swing.JPanel headerMargin;
private javax.swing.JLabel headerTag;
private javax.swing.JLabel headerTag2;
private javax.swing.JLabel headerTag3;
private javax.swing.JLabel marker;
private javax.swing.JLabel threeDots;
// End of variables declaration
}
public class Node {
public Node() {}
public Node(Node parent_node) {
if (parent_node.nodeType == 1) {
parent = parent_node;
parent_node.addChild(this);
}
}
public Node(int node_type) {
nodeType = node_type;
}
public Node(Node parent_node, int node_type) {
if (parent_node.nodeType == 1) {
parent = parent_node;
parent_node.addChild(this);
}
nodeType = node_type;
}
public boolean addChild(Node node) {
if (nodeType == 1) {
children.add(node);
return true;
}
return false;
}
public Node parent;
public Vector<Node> children = new Vector<Node>();
public LinkedHashMap<String, String> attributes = new LinkedHashMap<String, String>();
public Node previousSibling;
public Node nextSibling;
public String tagName = "";
public int nodeType = 3;
public String nodeValue = "";
}
public class TagLibrary {
public static void init() {
if (init) return;
tags.put("br", false);
tags.put("hr", false);
tags.put("link", false);
tags.put("img", false);
tags.put("a", true);
tags.put("span", true);
tags.put("div", true);
tags.put("p", true);
tags.put("sub", true);
tags.put("sup", true);
tags.put("b", true);
tags.put("i", true);
tags.put("u", true);
tags.put("s", true);
tags.put("strong", true);
tags.put("em", true);
tags.put("quote", true);
tags.put("cite", true);
tags.put("table", true);
tags.put("thead", true);
tags.put("tbody", true);
tags.put("cite", true);
tags.put("head", true);
tags.put("body", true);
leaves.add("style");
leaves.add("script");
init = true;
}
private static boolean init = false;
public static Hashtable<String, Boolean> tags = new Hashtable<String, Boolean>();
public static Vector<String> leaves = new Vector<String>();
}
主要课程:
public class WebInspectorTest {
private static Node prepareTree() {
Node root = new Node(1);
root.tagName = "body";
Node p = new Node(root, 1);
p.tagName = "p";
Node text1 = new Node(p, 3);
text1.nodeValue = "This is a ";
Node i = new Node(p, 1);
i.tagName = "i";
Node text2 = new Node(i, 3);
text2.nodeValue = "paragraph";
return root;
}
public static void main(String[] args) {
try {
UIManager.setLookAndFeel(
UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {}
final Node root = prepareTree();
if (root == null) return;
final JFrame frame = new JFrame("Document Inspector");
JPanel cp = new JPanel();
cp.setBorder(BorderFactory.createEmptyBorder(9, 10, 9, 10));
frame.setContentPane(cp);
cp.setLayout(new BorderLayout());
final JPanel contentpane = new JPanel();
contentpane.setBackground(Color.WHITE);
contentpane.setOpaque(true);
final int width = 490, height = 418;
final JScrollPane scrollpane = new JScrollPane(contentpane);
scrollpane.setOpaque(false);
scrollpane.getInsets();
cp.add(scrollpane);
scrollpane.setBackground(Color.WHITE);
scrollpane.setOpaque(true);
scrollpane.setPreferredSize(new Dimension(width, height));
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
TagLibrary.init();
final Entry rootEntry = new Entry(root);
contentpane.add(rootEntry);
final JScrollPane sp = scrollpane;
int width = sp.getVerticalScrollBar().isVisible() ? sp.getWidth() - sp.getVerticalScrollBar().getPreferredSize().width - 12 : sp.getWidth() + sp.getVerticalScrollBar().getPreferredSize().width;
rootEntry.inflate(width);
contentpane.addComponentListener(new java.awt.event.ComponentAdapter() {
@Override
public void componentMoved(java.awt.event.ComponentEvent evt) {}
@Override
public void componentResized(java.awt.event.ComponentEvent evt) {
int width = sp.getVerticalScrollBar().isVisible() ? sp.getWidth() - sp.getVerticalScrollBar().getPreferredSize().width - 12 : sp.getWidth() - 12;
rootEntry.setWidth(width);
}
});
}
});
}
}
这里出了什么问题?我尝试直接设置直接父级的新大小,但这对这种情况没有帮助。
给父母打电话
revalidate()
也不会改变任何事情。
要查看效果,您可以单击任何文本行中第一个字母左侧的三角形(我无法在此处附加图像文件,
triangle
和triangle2
是小10x10实心填充蓝色三角形的两个副本分别向下和向右看)。
如果您尝试关闭根目录,它将无法再正确打开。另外,根关闭后,它会移动到父级 JScrollPane 的中心。
更新: 这是更新后的代码,似乎解决了“从根部跳到中心”问题并解决了一般的最小化/最大化行为。但是,
<i>
元素在切换时仍然会跳跃。
public void open() {
marker.setIcon(new javax.swing.ImageIcon(getClass().getResource("/resources/triangle.png")));
threeDots.setVisible(false);
headerTag3.setVisible(false);
content.setVisible(true);
footer.setVisible(true);
opened = true;
int w = Math.max(Math.max(content.getMinimumSize().width, header.getMinimumSize().width), min_width);
int height = opened ? line_height * 2 + content.getPreferredSize().height : line_height;
if (content.getMinimumSize().height > content.getPreferredSize().height) {
content.setPreferredSize(content.getMinimumSize());
}
setSize(w, height);
setPreferredSize(null);
}
public void close() {
int delta = line_height + content.getPreferredSize().height;
marker.setIcon(new javax.swing.ImageIcon(getClass().getResource("/resources/triangle2.png")));
content.setVisible(false);
footer.setVisible(false);
boolean has_children = node.children.size() > 0;
threeDots.setVisible(has_children);
marker.setVisible(has_children);
headerTag3.setVisible(true);
opened = false;
int w = Math.max(getParent().getSize().width, Math.max(Math.max(content.getMinimumSize().width, header.getMinimumSize().width), min_width));
int height = opened ? line_height * 2 + content.getPreferredSize().height : line_height;
setSize(w, height);
if (getParent().getParent() instanceof Entry) {
getParent().setSize(new Dimension(getParent().getPreferredSize().width, getParent().getPreferredSize().height - delta));
} else {
setPreferredSize(new Dimension(w, height));
}
}
更新2:似乎“根条目居中”问题可以通过另一种方式解决:我可以将JScrollPane内根面板的布局管理器设置为
null
。它还修复了在调整大小处理程序方法中进行奇怪操作的需要,在该方法中,我从新宽度中减去凭经验发现的 12
数字,以在不需要时保持滚动。
但是,中间的切换元素上的跳跃仍然存在。
更新3:我编写了自己的非常简单的布局管理器,但它仍然无法正常工作。事实上,它的效果甚至比
BoxLayout
还要糟糕。我不明白,为什么。
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager;
public class LinearLayout implements LayoutManager {
public LinearLayout() {
this(X_AXIS);
}
public LinearLayout(int direction) {
this.direction = direction;
}
public LinearLayout(int direction, int gap) {
this.direction = direction;
this.gap = gap;
}
@Override
public void addLayoutComponent(String name, Component comp) {}
@Override
public void removeLayoutComponent(Component comp) {}
@Override
public Dimension preferredLayoutSize(Container parent) {
int width = 0;
int height = 0;
Insets insets = parent.getInsets();
Component[] c = parent.getComponents();
if (direction == X_AXIS) {
for (int i = 0; i < c.length; i++) {
if (c[i].getSize().height > height) {
height = c[i].getSize().height;
}
width += c[i].getSize().width + gap;
}
} else {
for (int i = 0; i < c.length; i++) {
if (c[i].getSize().width > width) {
width = c[i].getSize().width;
}
height += c[i].getSize().height + gap;
}
}
width += insets.left + insets.right;
height += insets.top + insets.bottom;
return new Dimension(width, height);
}
@Override
public Dimension minimumLayoutSize(Container parent) {
return preferredLayoutSize(parent);
}
@Override
public void layoutContainer(Container parent) {
Dimension dim = preferredLayoutSize(parent);
Component[] c = parent.getComponents();
Insets insets = parent.getInsets();
int x = insets.left;
int y = insets.top;
for (int i = 0; i < c.length; i++) {
if (direction == X_AXIS) {
c[i].setBounds(x, y, c[i].getSize().width, dim.height);
x += c[i].getSize().width + gap;
} else {
c[i].setBounds(x, y, dim.width, c[i].getSize().height);
y += c[i].getSize().height + gap;
}
}
}
private int direction = 0;
private int gap = 0;
public static final int X_AXIS = 0;
public static final int Y_AXIS = 1;
}
看来,正如评论中的人们所说,我应该定义自己的大小调整方法(
getPreferredSize/getMinimumSize/getMaximumSize
)实现。我为我的一些面板实现了它们,问题得到了解决。