javaswinguser-interfacelayoutgridbaglayout

How can I make my Panel wrap text and increase in height instead of width when contained in a GridBagLayout?


[FINAL EDIT]

Ok folks.

I don't know why it does not work in my chat program... but I managed to fix the SSCCE.. with the scrollable implementation it works now. Thank you all!

Code:

package de.sky.cjat;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;

import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.Scrollable;
import javax.swing.UIManager;

public class MyKskb {
    
    private int row;
    
    class SomethingPanel extends JPanel implements Scrollable {

        public SomethingPanel(LayoutManager manager){
            super(manager);
        }
        
        @Override
        public Dimension getPreferredScrollableViewportSize(){
            return getPreferredSize();
        }

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

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

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

        @Override
        public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction){
            return 1;
        }
    }
    
    public MyKskb(){
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.getContentPane().setPreferredSize(new Dimension(800, 600));
//      final JPanel content = new JPanel(new GridBagLayout());
        final SomethingPanel content = new SomethingPanel(new GridBagLayout());

//      final JPanel empty = new JPanel();
//      empty.setPreferredSize(new Dimension(0, 0));
//      GridBagConstraints gbc = new GridBagConstraints();
//      content.add(empty, gbc);
        content.getInputMap().put(KeyStroke.getKeyStroke("ENTER"), "§");
        content.getActionMap().put("§", new AbstractAction(){
            @Override
            public void actionPerformed(ActionEvent e){
                JPanel p = new JPanel(new BorderLayout());
                p.setBorder(BorderFactory.createLineBorder(Color.BLACK, 2));
                p.setBackground(Color.GRAY);
                
                GridBagConstraints gbc = new GridBagConstraints();
                gbc.insets = new Insets(10, 10, 10, 10);
                gbc.weightx = 1.0;
                gbc.gridx = 0;
                gbc.gridy = row++;
                gbc.fill = GridBagConstraints.HORIZONTAL;
                
//              content.remove(empty);
//              gbc.gridy = row;
//              gbc.weighty = 1.0;
//              content.add(empty, gbc);
            
                JTextArea text = new JTextArea(
                        Math.random() > 0.5 ? 
                        "ABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABC" : 
                        "FFFFFFFFFFFFABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABC");
                text.setLineWrap(true);
                p.add(text);
                content.add(p, gbc);
                content.revalidate();
            }
        });
        JScrollPane scrollPane = new JScrollPane(content);
        f.pack();
        f.setLocationRelativeTo(null);
        f.add(scrollPane);
        f.setVisible(true);
    }
    
    public static void main(String[] args) throws Exception {
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        new MyKskb();
    }
}

[END]

[QUESTION]

I'm writing a Chat Program, and the layout looks like this:

enter image description here

The Panel with the messages has a GridBagLayout and is wrapped in a JScrollPane.

The problem is, if the message is too long, the message panel stretches and gets too big:

enter image description here

So what I want to achieve, is to limit the size of the panel, which is wrapped in the JScrollPane - I want the gray panels with the message text to increase their height, if the text gets longer, not wider. I.e. wrap the text, without increasing the panel width.

Is there a way to achieve this with the grid bag layout, or am I missing something? Or should I use another layout manager?

Edit: The full Hierarchy:

JScrollPane
    JPanel (gbl)
        JPanel (custom painting)
            JComponent (JLabel but it can vary)
        JPanel (custom painting)
            JComponent (JLabel but it can vary)
        JPanel (custom painting)
            JComponent (JLabel but it can vary)
        ....    

edit: I tried to implement Scrollable like this:

@Override
    public Dimension getPreferredScrollableViewportSize(){
        return new Dimension(100, getHeight());
    }

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

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

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

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

but the same thing happens if the text gets too long... I don't know what getPreferredScrollableViewportSize should return, but also getSize() didn't work...

EDIT: here is a SSCCE, it shows the basic structure of my GUI: (press enter to add new component)

package de.sky.cjat;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;

import javax.swing.AbstractAction;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.KeyStroke;
import javax.swing.UIManager;

public class Kskb {
    
    private int row;
    
    public Kskb(){
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.getContentPane().setPreferredSize(new Dimension(800, 600));
        final JPanel content = new JPanel(new GridBagLayout());
        final JPanel empty = new JPanel();
        empty.setPreferredSize(new Dimension(0, 0));
        GridBagConstraints gbc = new GridBagConstraints();
        content.add(empty, gbc);
        content.getInputMap().put(KeyStroke.getKeyStroke("ENTER"), "§");
        content.getActionMap().put("§", new AbstractAction(){
            @Override
            public void actionPerformed(ActionEvent e){
                JPanel p = new JPanel();
                p.setBackground(Color.GRAY);
                JLabel text = new JLabel("ABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABC");
                p.add(text);
                
                GridBagConstraints gbc = new GridBagConstraints();
                gbc.insets = new Insets(10, 10, 10, 10);
                gbc.weightx = 1.0;
                gbc.gridy = row++;
                gbc.fill = GridBagConstraints.HORIZONTAL;
                gbc.gridwidth = GridBagConstraints.REMAINDER;
                gbc.anchor = GridBagConstraints.NORTHWEST;
                
                content.add(p, gbc);
                
                content.remove(empty);
                gbc.gridy = row;
                gbc.weighty = 1.0;
                content.add(empty, gbc);
                
                content.revalidate();
            }
        });
        JScrollPane scrollPane = new JScrollPane(content);
        f.pack();
        f.setLocationRelativeTo(null);
        f.add(scrollPane);
        f.setVisible(true);
    }
    
    public static void main(String[] args) throws Exception {
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        new Kskb();
    }
}

Solution

  • Following up on @durandal's advice, the problem is the JLabel. A JLabel does not wrap text. So you need to use a different component. Here is your code modified with a JTextArea:

    import java.awt.*;
    import java.awt.event.ActionEvent;
    
    import javax.swing.*;
    import javax.swing.border.*;
    
    public class Kskb {
    
        private int row;
    
        public Kskb()
        {
            JFrame f = new JFrame();
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            f.getContentPane().setPreferredSize(new Dimension(800, 600));
    //        final JPanel content = new JPanel(new GridBagLayout());
            final ScrollablePanel content = new ScrollablePanel(new GridBagLayout());
            content.setScrollableWidth( ScrollablePanel.ScrollableSizeHint.FIT );
            final GridBagConstraints gbc = new GridBagConstraints();
            gbc.insets = new Insets(10, 10, 10, 10);
            gbc.gridx = 0;
            gbc.gridy = 0;
            gbc.weightx = 1.0;
            gbc.fill = GridBagConstraints.HORIZONTAL;
            content.getInputMap().put(KeyStroke.getKeyStroke("ENTER"), "");
            content.getActionMap().put("", new AbstractAction(){
                @Override
                public void actionPerformed(ActionEvent e)
                {
                    JPanel p = new JPanel( new BorderLayout() );
                    p.setBorder( new EmptyBorder(10, 10, 10, 10) );
                    p.setBackground(Color.GRAY);
                    JTextArea text = new JTextArea();
                    text.append("ABCABCABCABCABCABCABCABC ABCABCABCABCABCABCABCABCABCABCABCABCABCABC ABCABCABCABCABCABCABCABCABCABCABCABCABCA BCABCABCABCABCABCABCAB CABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABC");
                    text.setLineWrap( true );
                    p.add(text);
    
                    gbc.gridy = row++;
                    content.add(p, gbc);
                    content.revalidate();
                }
            });
    
            JScrollPane scrollPane = new JScrollPane(content);
            f.pack();
            f.setLocationRelativeTo(null);
            f.add(scrollPane);
            f.setVisible(true);
        }
    
        public static void main(String[] args) throws Exception {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            new Kskb();
        }
    }
    

    The key is the Scrollable implementation which forces the panels width to be adjusted ad the frame is adjusted. This code uses the Scrollable Panel, which is just a reusable implementation of the Scrollable interface that you can configure with methods instead of creating a new class and overriding the methods.