java - 如何让JTextArea在Java中占据GridBagLayout最大宽度的80%

标签 java gridbaglayout

我正在尝试制作一个聊天窗口,但我似乎无法弄清楚如何使 JTextArea 最多占据 GridBagLayout 中可用宽度空间的 80%。换句话说,如果文本看起来占据了窗口的整个宽度,那么它应该只能占据宽度的 80% 并且只能换行。另外,如果文本小于 80%,比如 40%,那么 JTextArea 应该只换行 40%,而不是整个 80%。
我正在使用 GridBagLayout,并将 GridBagConstraints Weightx 设置为 1.0,并将填充设置为 HORIZONTAL,向左或向右插入 40,具体取决于用户。但我似乎无法让 JTextArea 环绕文本并允许它在需要时使用窗口上 80% 的宽度,否则环绕文本。

这是我当前的聊天窗口的样子:
请注意 David 的第一个条目不应占据整个宽度。它应该只包裹文本。
enter image description here



这是我想要实现的目标:
enter image description here



以下是我当前编写的代码:

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import java.util.LinkedList;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.Scrollable;
import javax.swing.SwingUtilities;


public class ChatWindow {

    private JFrame mMainFrame;

    public ChatWindow(List<ChatEntry> chatContentList) {
        // Create JFrame, set window size and center on screen.
        mMainFrame = new JFrame();
        mMainFrame.setTitle("Chat Window");
        mMainFrame.setSize(360, 600);
        mMainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        mMainFrame.setLocationRelativeTo(null);

        // Create parent container JPanel for all other JComponents.
        JPanel mainPanel = new JPanel();
        mainPanel.setLayout(new GridBagLayout());

        GridBagConstraints gbc = new GridBagConstraints();
        gbc.weightx = 1.0;
        gbc.gridwidth = GridBagConstraints.REMAINDER;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        mainPanel.add(buildChatUI(chatContentList), gbc);

        // Add empty JPanel as an object to fill the empty space available.
        gbc.fill = GridBagConstraints.BOTH;
        gbc.weighty = 1.0;
        mainPanel.add(new JPanel(), gbc);

        mMainFrame.getContentPane().add(new JScrollPane(new VerticalScrollPane(mainPanel)));
        mMainFrame.setVisible(true);
    }

    public JPanel buildChatUI(List<ChatEntry> chatContentList) {
        JPanel chatPanel = new JPanel();
        chatPanel.setLayout(new GridBagLayout());
        chatPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));

        GridBagConstraints gbc = new GridBagConstraints();

        for (ChatEntry chatEntry : chatContentList) {
            JLabel nameLabel = new JLabel(chatEntry.name);

            JTextArea contentTextArea = new JTextArea();
            contentTextArea.setText(chatEntry.content);
            contentTextArea.setOpaque(true);
            contentTextArea.setLineWrap(true);
            contentTextArea.setWrapStyleWord(true);
            contentTextArea.setEditable(false);

            // Arrange each chat entry based on the user.
            if (chatEntry.type == 1) {
                contentTextArea.setBackground(Color.YELLOW);
                gbc.anchor = GridBagConstraints.WEST;
            }
            else {
                contentTextArea.setBackground(Color.CYAN);
                gbc.anchor = GridBagConstraints.EAST;
            }

            gbc.insets.set(0, 0, 0, 0);
            gbc.weightx = 1.0;
            gbc.gridwidth = GridBagConstraints.REMAINDER;
            gbc.fill = GridBagConstraints.NONE;
            chatPanel.add(nameLabel, gbc);

            if (gbc.anchor == GridBagConstraints.WEST) {
                gbc.fill = GridBagConstraints.HORIZONTAL;
                gbc.insets.set(0, 0, 0, 40);
                chatPanel.add(contentTextArea, gbc);
            }
            else {
                gbc.fill = GridBagConstraints.HORIZONTAL;
                gbc.insets.set(0, 40, 0, 0);
                chatPanel.add(contentTextArea, gbc);
            }
        }

        return chatPanel;
    }


    /**
     * This class is used to make the JTextArea lines wrap every time the window
     * is resized. Without this, the JTextArea lines will not shrink back if the
     * parent window shrinks. This is achieved by returning true on getScrollableTracksViewportWidth();
     */
    private class VerticalScrollPane extends JPanel implements Scrollable {

        private static final long serialVersionUID = 7477168367035025136L;

        public VerticalScrollPane() {
            this(new GridLayout(0, 1));
        }

        public VerticalScrollPane(LayoutManager lm) {
            super(lm);
        }

        public VerticalScrollPane(Component comp) {
            this();
            add(comp);
        }

        @Override
        public Dimension getPreferredScrollableViewportSize() {
            return getPreferredSize();
        }

        @Override
        public int getScrollableUnitIncrement(Rectangle visibleRect,
                int orientation, int direction) {
            return 10;
        }

        @Override
        public int getScrollableBlockIncrement(Rectangle visibleRect,
                int orientation, int direction) {
            return 100;
        }

        @Override
        public boolean getScrollableTracksViewportWidth() {
            return true;
        }

        @Override
        public boolean getScrollableTracksViewportHeight() {
            return false;
        }

    }

    /**
     * Class structure for storing a single chat entry in a full conversation.
     */
    public static class ChatEntry {
        public String name;
        public String content;

        // For type 0=sent, 1=received.
        public int type;

        public ChatEntry(String name, String content, int type) {
            this.name = name;
            this.content = content;
            this.type = type;
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                // Simulate a conversation between two users and pass it on to the
                // chat window to test the UI.
                List<ChatEntry> chatContentList = new LinkedList<>();
                chatContentList.add(new ChatEntry("David", "Hey Lori, how are you?", 0));
                chatContentList.add(new ChatEntry("Lori", "Hi David, I'm good. What have you been up to?", 1));
                chatContentList.add(new ChatEntry("David", "I've been super busy with work.", 0));
                chatContentList.add(new ChatEntry("David", "Haven't had much free time to even go out to eat.", 0));
                chatContentList.add(new ChatEntry("Lori", "I know what you mean, I've had to work on projects after projects.", 1));
                chatContentList.add(new ChatEntry("David", "Let's make some time and go to lunch tomorrow!", 0));
                chatContentList.add(new ChatEntry("Lori", "That sounds great, let's do 12pm. I know a great food truck by my building.", 1));
                chatContentList.add(new ChatEntry("David", "Perfect, I'll meet you at the entrance of your building.", 0));
                chatContentList.add(new ChatEntry("Lori", "Awesome, see you tomorrow :)", 1));
                new ChatWindow(chatContentList);
            }
        });
    }

}

最佳答案

调整窗口大小时,有一种耗时的方法来重新布局 TextArea。我为每个 ChatEntry 添加了 ComponentListener,它将调整条目 TextArea 的大小。这只是概念证明,可以提高性能。

尝试以下方法,减小窗口宽度,然后慢慢加宽以查看效果。它类似于您所描绘的目标。当窗口打开时,我还没有立即实现该布局。虽然缓慢扩大窗口效果很好,但如果移动得太快,则由于 JDK 代码内部的缓存,行可能会显得太宽。

无论如何,我想分享我的实验,也许它可以帮助你找到处理它的方法。我假设您可能必须实现自己的布局管理器才能完全实现您既定的目标并规避观察到的缓存。

计算 TextArea 内行数的方法取自另一个 StackOverflow 问题:How to count the number of lines in a JTextArea, including those caused by wrapping?

public JPanel buildChatUI(List<ChatEntry> chatContentList) {
    final JPanel chatPanel = new JPanel();
    chatPanel.setLayout(new GridBagLayout());
    chatPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));

    GridBagConstraints gbc = new GridBagConstraints();

    for (ChatEntry chatEntry : chatContentList) {
        JLabel nameLabel = new JLabel(chatEntry.name);

        final JTextArea contentTextArea = new JTextArea();
        contentTextArea.setText(chatEntry.content);
        contentTextArea.setOpaque(true);
        contentTextArea.setLineWrap(true);
        contentTextArea.setWrapStyleWord(true);
        contentTextArea.setEditable(false);
        chatPanel.addComponentListener(new ComponentListener() {
            @Override
            public void componentResized(ComponentEvent e) {
                int lc = countLines(contentTextArea);
                GridBagLayout gbl = (GridBagLayout) chatPanel.getLayout();
                GridBagConstraints constraints = gbl.getConstraints(contentTextArea);
                if (lc == 1) {
                    if (constraints.fill == GridBagConstraints.HORIZONTAL) {
                        constraints.fill = GridBagConstraints.NONE;
                        gbl.setConstraints(contentTextArea, constraints);
                    }
                } else {
                    if (constraints.fill == GridBagConstraints.NONE) {
                        constraints.fill = GridBagConstraints.HORIZONTAL;
                        gbl.setConstraints(contentTextArea, constraints);
                    }
                }
            }
            @Override public void componentMoved(ComponentEvent e) { }
            @Override public void componentShown(ComponentEvent e) { }
            @Override public void componentHidden(ComponentEvent e) { }
        });

        // Arrange each chat entry based on the user.
        if (chatEntry.type == 1) {
            contentTextArea.setBackground(Color.YELLOW);
            gbc.anchor = GridBagConstraints.WEST;
        }
        else {
            contentTextArea.setBackground(Color.CYAN);
            gbc.anchor = GridBagConstraints.EAST;
        }

        gbc.insets.set(0, 0, 0, 0);
        gbc.weightx = 1.0;
        gbc.gridwidth = GridBagConstraints.REMAINDER;
        gbc.fill = GridBagConstraints.NONE;
        chatPanel.add(nameLabel, gbc);
        gbc.fill = GridBagConstraints.HORIZONTAL;
        if (gbc.anchor == GridBagConstraints.WEST) {
            gbc.insets.set(0, 0, 0, 40);
            chatPanel.add(contentTextArea, gbc);
        }
        else {
            gbc.insets.set(0, 40, 0, 0);
            chatPanel.add(contentTextArea, gbc);
        }
    }

    return chatPanel;
}

/**
 * From https://stackoverflow.com/questions/6366776/how-to-count-the-number-of-lines-in-a-jtextarea-including-those-caused-by-wrapp
 * @param textArea the text area of interest
 * @return number of lines in text area
 */
private static int countLines(JTextArea textArea) {
    AttributedString text = new AttributedString(textArea.getText());
    FontRenderContext frc = textArea.getFontMetrics(textArea.getFont())
            .getFontRenderContext();
    AttributedCharacterIterator charIt = text.getIterator();
    LineBreakMeasurer lineMeasurer = new LineBreakMeasurer(charIt, frc);
    float formatWidth = (float) textArea.getSize().width;
    lineMeasurer.setPosition(charIt.getBeginIndex());

    int noLines = 0;
    while (lineMeasurer.getPosition() < charIt.getEndIndex()) {
        lineMeasurer.nextLayout(formatWidth);
        noLines++;
    }

    return noLines;
}

关于java - 如何让JTextArea在Java中占据GridBagLayout最大宽度的80%,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43310455/

相关文章:

java - JVM指令-sload

java - 在 Java 中实现 GridBagLayout 的问题

java - 防止 GridBagLayout 调整列大小

java - GridBagLayout对齐和Button样式

java - GridBagLayout:均匀分布的单元格

java - GridBagConstraints 填充垂直和水平

java - ActiveMQ SSL 连接 keystore 和信任库类型

java - 休息Web服务中的Spring Security

java - 设计库 - 带有 GridView/listView 的 CoordinatorLayout/CollapsingToolbarLayout

java - 构建 JSONArray