如何在 Swing 中制作剧透(以及为什么简单的解决方案不起作用)

问题描述 投票:0回答:1

我试图在 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;

}
java swing layout height boxlayout
1个回答
0
投票

看来,正如评论中的人们所说,我应该定义自己的大小调整方法(

getPreferredSize/getMinimumSize/getMaximumSize
)实现。我为我的一些面板实现了它们,问题得到了解决。

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.