我正在尝试创建一个聊天窗口,但我似乎无法弄清楚如何使JTextArea占用GridBagLayout中可用宽度空间的最大值的80%。换句话说,如果文本似乎占据窗口的整个宽度,它应该只能占据宽度的80%并且只是包裹线条。此外,如果文本小于80%,比如40%,那么JTextArea应该只包装40%而不是整个80%。
我正在使用GridBagLayout并且我将GridBagConstraints weightx设置为1.0,并且填充到HORIZONTAL,插入40左右,取决于用户。但我似乎无法使JTextArea包装文本,并允许它在窗口上使用80%的宽度,否则环绕文本。
以下是我当前的聊天窗口:
请注意David的第一个条目不应占用整个宽度。它应该只包装文本。
以下是我写的当前代码:
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);
}
});
}
}
答案 0 :(得分:1)
调整窗口大小时重新布局TextAreas是一种耗时的方法。我已经为每个ChatEntry添加了ComponentListeners,它将调整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;
}
答案 1 :(得分:0)
Thanks to Kriegel, I was finally able to achieve what I desired. I was able to take some of the code you posted and implemented it another way.
Here is a screenshot of what everything now looks like:
Here is the code:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.geom.Area;
import java.awt.geom.RoundRectangle2D;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
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);
BubblePane bubble = new BubblePane(chatPanel, chatEntry.content);
// Arrange each chat entry based on the user.
if (chatEntry.type == 1) {
bubble.setBackground(Color.YELLOW);
gbc.anchor = GridBagConstraints.WEST;
}
else {
bubble.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, 10, 40);
chatPanel.add(bubble, gbc);
}
else {
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets.set(0, 40, 10, 0);
chatPanel.add(bubble, gbc);
}
}
return chatPanel;
}
private class BubblePane extends JTextArea {
private static final long serialVersionUID = -6113801969569504295L;
private int radius = 10;
private int strokeThickness = 3;
private int padding = strokeThickness / 2;
private JPanel mParent;
public BubblePane(JPanel parent, String text) {
mParent = parent;
setOpaque(false);
setLineWrap(true);
setWrapStyleWord(true);
setEditable(false);
setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
setText(text);
}
@Override
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(getBackground());
int x = padding + strokeThickness;
int width = getWidth() - (strokeThickness * 2);
int bottomLineY = getHeight() - strokeThickness;
g2d.fillRect(x, padding, width, bottomLineY);
g2d.setRenderingHints(new RenderingHints(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON));
g2d.setStroke(new BasicStroke(strokeThickness));
RoundRectangle2D.Double rect = new RoundRectangle2D.Double(x, padding,
width, bottomLineY, radius, radius);
Area area = new Area(rect);
g2d.draw(area);
int lc = countLines(this);
GridBagLayout gbl = (GridBagLayout) mParent.getLayout();
GridBagConstraints constraints = gbl.getConstraints(this);
if (lc == 1) {
if (constraints.fill == GridBagConstraints.HORIZONTAL) {
constraints.fill = GridBagConstraints.NONE;
gbl.setConstraints(this, constraints);
this.setSize(
getFontMetrics(getFont()).stringWidth(getText()) +
this.getBorder().getBorderInsets(this).left +
this.getBorder().getBorderInsets(this).right,
getHeight() +
this.getBorder().getBorderInsets(this).top +
this.getBorder().getBorderInsets(this).bottom);
}
} else {
if (constraints.fill == GridBagConstraints.NONE) {
constraints.fill = GridBagConstraints.HORIZONTAL;
gbl.setConstraints(this, constraints);
}
}
super.paintComponent(g);
}
private 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;
}
}
/**
* 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);
}
});
}
}