How to wrap lines in a jtable cell?

前端 未结 8 1025
面向向阳花
面向向阳花 2020-11-29 09:11

I\'m trying to implement a custom TableRenderer as described in this tutorial. I\'d like to have the renderer line-wrap each text that is to long for the given cell. The i

相关标签:
8条回答
  • 2020-11-29 09:28

    You could use a JLabel as a renderer and insert the text into a HTML tag and just add <br> where appropriate

    How to use HTML in Swing Components

    0 讨论(0)
  • 2020-11-29 09:34

    The only way to implement this properly using Swing (I don't know about JavaFX: the same principles may possibly apply) is by understanding and using setBounds on the renderer component.

    I have come to this conclusion as a result of trial and error, rather than examining the source code. But is s clear that this method is responsible for laying out text (in whatever font) and calculating and then implementing word-wrapping).

    import java.awt.*;
    import java.io.*;
    import java.util.*;
    import javax.swing.*;
    import javax.swing.event.*;
    import javax.swing.table.*;
    
    public class MultiWrapColDemo {
      public static void main(String[] args) throws FileNotFoundException {
        EventQueue.invokeLater(new ShowIt());
      }
    }
    
    class ShowIt implements Runnable {
      @Override
      public void run() {
        JTable table = new JTable();
        table.getColumnModel().addColumnModelListener( new WrapColListener( table ) );
        table.setDefaultRenderer( Object.class, new JTPRenderer() );
    
        // examples:
    //    table.setIntercellSpacing( new Dimension( 40, 20 ));
    //    table.setIntercellSpacing( new Dimension( 4, 2 ));
    
        Vector<Vector<String>> dataVector = new Vector<Vector<String>>();
        String lorem1 = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore";
        String lorem2 = "et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum";
    
        for (int i = 0; i < 12; i++) {
          Vector<String> row = null;
          if (i % 4 == 0) {
            row = new Vector<String>(Arrays.asList(new String[] { "iggle", lorem1, "poggle", "poke" }));
          } else if (i % 4 == 1) {
            row = new Vector<String>(Arrays.asList(new String[] { lorem2, "piggle", "poggle", lorem1 }));
          } else if (i % 4 == 2) {
            row = new Vector<String>(Arrays.asList(new String[] { lorem1, "piggle", lorem2, "poke" }));
          } else
            row = new Vector<String>(Arrays.asList(new String[] { "iggle", lorem2, "poggle", lorem2 }));
          dataVector.add(row);
        }
        Vector<String> columnIdentifiers = new Vector<String>(Arrays.asList(new String[] { "iggle", "piggle", "poggle",
            "poke" }));
        table.getTableHeader().setFont(table.getTableHeader().getFont().deriveFont(20f).deriveFont(Font.BOLD));
        ((DefaultTableModel) table.getModel()).setDataVector(dataVector, columnIdentifiers);
        JFrame frame = new JFrame("MultiWrapColTable");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JScrollPane jsp = new JScrollPane(table);
        frame.getContentPane().add(jsp);
        frame.pack();
        frame.setBounds(50, 50, 800, 500);
        frame.setVisible(true);
      }
    }
    
    
    // if the renderer on a column (or the whole table) is not a JTextComponent calculating its preferredSize will not do 
    // any wrapping ... but it won't do any harm....
    class JTPRenderer extends JTextPane implements TableCellRenderer {
      @Override
      public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
          int row, int column) {
        setText(value.toString());
        return this;
      }
    }
    
    class WrapColListener implements TableColumnModelListener {
    
      JTable m_table;
    
      WrapColListener( JTable table ){
        m_table = table;
      }
    
      void refresh_row_heights() {
        int n_rows = m_table.getRowCount();
        int n_cols = m_table.getColumnCount();
        int intercell_width = m_table.getIntercellSpacing().width;
        int intercell_height = m_table.getIntercellSpacing().height;
        TableColumnModel col_model = m_table.getColumnModel();
        // these null checks are due to concurrency considerations... much can change between the col margin change
        // event and the call to refresh_row_heights (although not in this SSCCE...)
        if( col_model == null ) return;
        // go through ALL rows, calculating row heights
        for (int row = 0; row < n_rows; row++) {
          int pref_row_height = 1;
          // calculate row heights from cell, setting width constraint by means of setBounds...
          for (int col = 0; col < n_cols; col++) {
            Object value = m_table.getValueAt(row, col);
            TableCellRenderer renderer = m_table.getCellRenderer(row, col);
            if( renderer == null ) return;
            Component comp = renderer.getTableCellRendererComponent( m_table, value, false, false,
                row, col);
            if( comp == null ) return;
            int col_width = col_model.getColumn(col).getWidth();
            // constrain width of component
            comp.setBounds(new Rectangle(0, 0, col_width - intercell_width, Integer.MAX_VALUE ));
            // getPreferredSize then returns "true" height as a function of attributes (e.g. font) and word-wrapping
            int pref_cell_height = comp.getPreferredSize().height  + intercell_height;
            if (pref_cell_height > pref_row_height) {
              pref_row_height = pref_cell_height;
            }
          }
          if (pref_row_height != m_table.getRowHeight(row)) {
            m_table.setRowHeight(row, pref_row_height);
          }
        }
      }
    
      @Override
      public void columnAdded(TableColumnModelEvent e) {
        refresh_row_heights();
    
      }
    
      @Override
      public void columnRemoved(TableColumnModelEvent e) {
        // probably no need to call refresh_row_heights
    
      }
    
      @Override
      public void columnMoved(TableColumnModelEvent e) {
        // probably no need to call refresh_row_heights
    
      }
    
      @Override
      public void columnMarginChanged(ChangeEvent e) {
        refresh_row_heights();
      }
    
      @Override
      public void columnSelectionChanged(ListSelectionEvent e) {
        // probably no need to call refresh_row_heights
    
      }
    
    }
    

    The above works fine in this SSCCE... but in the real world, with more complex fonts, more text and larger tables you start to run into problems. I therefore propose below a new version of the Listener class along with a new version of the renderer (just to introduce the use of a complex font...). Substitute these into the above SSCCE if interested...

    /*
     * This class reflects the fact that 1) when you drag a column boundary using the mouse a very large number of
     * ChangeEvents are generated and 2) with more complex fonts, more text and larger tables ("real world") the amount
     * of computation in calculating the row heights becomes significant and leads to an unresponsive GUI, or worse.
     * This "first" strategy to address this involves setting a pause between the detection of a change event and the
     * refreshing of the rows.  Naturally this involves a Timer, the run() method of which is not the EDT, so it
     * must then submit to EventQueue.invokeLater...
     * The larger the table, the more text involved, and the more complex the fonts... the more ingenuity will have to 
     * be used in coping with the potentially vast amount of computation involved in getting the ideal row heights.  This
     * is in the nature of the beast.  Ideas might involve: 
     * 1) adjusting the row heights immediately only for rows which are visible or likely to be visible (Viewport), and 
     * then making successive calls to EventQueue.invokeLater to deal with all the other rows
     * 2) giving cells a "memory" of their heights as a function of the allowed width.  Unfortunately it will not allow
     * the possibility of interpolating intermediate values because the question of whether a line wraps may hinge on a 
     * single pixel difference, although an imperfect solution to this would be err on the side of caution, i.e. pretend
     * that a column is a little thinner than it is to cause wrapping before it is strictly necessary... particularly when 
     * cells are out of view...
     * ... other ideas...(?)  
     */
    class FirstRealWorldWrapColListener implements TableColumnModelListener {
    
      JTable m_table;
      final static long PAUSE_TIME = 50L;
      java.util.Timer m_pause_timer = new java.util.Timer( "pause timer", true );
      TimerTask m_pause_task;
    
      class PauseTask extends TimerTask{
        @Override
        public void run() {
          EventQueue.invokeLater( new Runnable(){
            @Override
            public void run() {
              refresh_row_heights();
              System.out.println( "=== setting m_pause_task to null..." );
              m_pause_task = null;
            }});
        }
      }
    
      FirstRealWorldWrapColListener( JTable table ){
        m_table = table;
      }
    
    
      void queue_refresh(){
        if( m_pause_task != null ){
          return;
        }
        System.out.println( "=== scheduling..." );
        m_pause_task = new PauseTask();
        m_pause_timer.schedule( m_pause_task, PAUSE_TIME ); 
    
      }
    
      void refresh_row_heights() {
    
        int n_rows = m_table.getRowCount();
        int n_cols = m_table.getColumnCount();
        int intercell_width = m_table.getIntercellSpacing().width;
        int intercell_height = m_table.getIntercellSpacing().height;
        TableColumnModel col_model = m_table.getColumnModel();
        // these null checks are due to concurrency considerations... much can change between the col margin change
        // event and the call to refresh_row_heights (although not in this SSCCE...)
        if( col_model == null ) return;
        // go through ALL rows, calculating row heights
        for (int row = 0; row < n_rows; row++) {
          int pref_row_height = 1;
          // calculate row heights from cell, setting width constraint by means of setBounds...
          for (int col = 0; col < n_cols; col++) {
            Object value = m_table.getValueAt(row, col);
            TableCellRenderer renderer = m_table.getCellRenderer(row, col);
            if( renderer == null ) return;
            Component comp = renderer.getTableCellRendererComponent( m_table, value, false, false,
                row, col);
            if( comp == null ) return;
            int col_width = col_model.getColumn(col).getWidth();
            // constrain width of component
            comp.setBounds(new Rectangle(0, 0, col_width - intercell_width, Integer.MAX_VALUE ));
            // getPreferredSize then returns "true" height as a function of attributes (e.g. font) and word-wrapping
            int pref_cell_height = comp.getPreferredSize().height  + intercell_height;
            if (pref_cell_height > pref_row_height) {
              pref_row_height = pref_cell_height;
            }
          }
          if (pref_row_height != m_table.getRowHeight(row)) {
            m_table.setRowHeight(row, pref_row_height);
          }
        }
      }
    
      @Override
      public void columnAdded(TableColumnModelEvent e) {
    //    refresh_row_heights();
        queue_refresh();
    
      }
    
      @Override
      public void columnRemoved(TableColumnModelEvent e) {
        // probably no need to call refresh_row_heights
    
      }
    
      @Override
      public void columnMoved(TableColumnModelEvent e) {
        // probably no need to call refresh_row_heights
    
      }
    
      @Override
      public void columnMarginChanged(ChangeEvent e) {
    //    refresh_row_heights();
        queue_refresh();
      }
    
      @Override
      public void columnSelectionChanged(ListSelectionEvent e) {
        // probably no need to call refresh_row_heights
    
      }
    
    }
    
    // if the renderer on a column (or the whole table) is not a JTextComponent calculating its preferredSize will not do 
    // any wrapping ... but it won't do any harm....
    class JTPRenderer extends JTextPane implements TableCellRenderer {
      Font m_default_font, m_big_font, m_default_alternate_font, m_big_alternate_font;
      HashMap<AttributedCharacterIterator.Attribute, Object>  m_red_serif_attr_map;
      //
      JTPRenderer() {
        m_default_font = getFont();
        m_big_font = m_default_font.deriveFont(m_default_font.getSize() * 1.5f);
        m_red_serif_attr_map = new HashMap<AttributedCharacterIterator.Attribute, Object >();
        m_red_serif_attr_map.put( TextAttribute.FAMILY, Font.SERIF );
        m_red_serif_attr_map.put( TextAttribute.FOREGROUND, Color.RED );
        m_red_serif_attr_map.put( TextAttribute.WIDTH, TextAttribute.WIDTH_EXTENDED );
        m_default_alternate_font = m_default_font.deriveFont( m_red_serif_attr_map );
        m_big_alternate_font = m_big_font.deriveFont( m_red_serif_attr_map );
        // simpler alternate font:
    //    m_default_alternate_font = m_default_font.deriveFont( Font.BOLD | Font.ITALIC );
    //    m_big_alternate_font = m_big_font.deriveFont( Font.BOLD | Font.ITALIC );
      }
    
      @Override
      public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
          int row, int column) {
        int rc = row + column;
        if( rc % 4 == 2 )
          setFont( rc % 5 == 1 ?  m_big_alternate_font : m_default_alternate_font );
        else 
          setFont( rc % 5 == 1 ?  m_big_font : m_default_font );
        setText(value.toString());
        return this;
      }
    
    }
    
    0 讨论(0)
  • 2020-11-29 09:39

    The problem is that the height of rows in JTable is fixed, so it's not just a matter of having a renderer that wraps; I'm not sure why it doesn't, but if it did, the wrapped text would be cropped - or maybe that's exactly what you're seeing. To adjust row heights, you need to set them individually.

    0 讨论(0)
  • 2020-11-29 09:39

    Write the headers in HTML. Here is an example of one that I have. The only issue that I am experiencing is I am having a hard time having them scroll in the JPanel if I adjust the height of the headers.

        myTable.getColumnModel().getColumn(1).setPreferredWidth(75);
        myTable.getColumnModel().getColumn(1).setHeaderValue("<html><b>Day Of<br>Week</b></html>");
    
    0 讨论(0)
  • 2020-11-29 09:44

    Hi I had your same problem but the solution I implemented is inspired by the sample available from the Java Tutorial for drawing multiline text and draws the text on the cell using the text APIs.

    http://java.sun.com/docs/books/tutorial/2d/text/drawmulstring.html

    import java.awt.Component;
    import java.awt.Font;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.font.FontRenderContext;
    import java.awt.font.LineBreakMeasurer;
    import java.awt.font.TextLayout;
    import java.text.AttributedCharacterIterator;
    import java.text.AttributedString;
    import java.text.BreakIterator;
    
    import javax.swing.JTable;
    import javax.swing.table.DefaultTableCellRenderer;
    import javax.swing.table.TableCellRenderer;
    
    
    public class MultilineTableCell 
        implements TableCellRenderer {
        class CellArea extends DefaultTableCellRenderer {
            /**
             * 
             */
            private static final long serialVersionUID = 1L;
            private String text;
            protected int rowIndex;
            protected int columnIndex;
            protected JTable table;
            protected Font font;
            private int paragraphStart,paragraphEnd;
            private LineBreakMeasurer lineMeasurer;
    
            public CellArea(String s, JTable tab, int row, int column,boolean isSelected) {
                text = s;
                rowIndex = row;
                columnIndex = column;
                table = tab;
                font = table.getFont();
                if (isSelected) {
                    setForeground(table.getSelectionForeground());
                    setBackground(table.getSelectionBackground());
                }
            }
            public void paintComponent(Graphics gr) {
                super.paintComponent(gr);
                if ( text != null && !text.isEmpty() ) {
                    Graphics2D g = (Graphics2D) gr;
                    if (lineMeasurer == null) {
                        AttributedCharacterIterator paragraph = new AttributedString(text).getIterator();
                        paragraphStart = paragraph.getBeginIndex();
                        paragraphEnd = paragraph.getEndIndex();
                        FontRenderContext frc = g.getFontRenderContext();
                        lineMeasurer = new LineBreakMeasurer(paragraph,BreakIterator.getWordInstance(), frc);
                    }
                    float breakWidth = (float)table.getColumnModel().getColumn(columnIndex).getWidth();
                    float drawPosY = 0;
                    // Set position to the index of the first character in the paragraph.
                    lineMeasurer.setPosition(paragraphStart);
                    // Get lines until the entire paragraph has been displayed.
                    while (lineMeasurer.getPosition() < paragraphEnd) {
                        // Retrieve next layout. A cleverer program would also cache
                        // these layouts until the component is re-sized.
                        TextLayout layout = lineMeasurer.nextLayout(breakWidth);
                        // Compute pen x position. If the paragraph is right-to-left we
                        // will align the TextLayouts to the right edge of the panel.
                        // Note: this won't occur for the English text in this sample.
                        // Note: drawPosX is always where the LEFT of the text is placed.
                        float drawPosX = layout.isLeftToRight()
                            ? 0 : breakWidth - layout.getAdvance();
                        // Move y-coordinate by the ascent of the layout.
                        drawPosY += layout.getAscent();
                        // Draw the TextLayout at (drawPosX, drawPosY).
                        layout.draw(g, drawPosX, drawPosY);
                        // Move y-coordinate in preparation for next layout.
                        drawPosY += layout.getDescent() + layout.getLeading();
                    }
                    table.setRowHeight(rowIndex,(int) drawPosY);
                }
            }
        }
        public Component getTableCellRendererComponent(
                JTable table, Object value,boolean isSelected, boolean hasFocus, int row,int column
            )
        {
            CellArea area = new CellArea(value.toString(),table,row,column,isSelected);
            return area;
        }   
    }
    

    It resizes row heigth too but it does it well only when this renderer is used for a single column.

    And this is the way I used to invoke it for render my table.

    final int wordWrapColumnIndex = ...;
    myTable = new JTable() {    
        public TableCellRenderer getCellRenderer(int row, int column) {
            if (column == wordWrapColumnIndex ) {
                return wordWrapRenderer;
            }
            else {
                return super.getCellRenderer(row, column);
            }
        }
    };
    
    0 讨论(0)
  • 2020-11-29 09:46

    I stumbled in this same problem, and I needed to modify a little the code that it was written here, so I attach my own version:

    import java.awt.Component;
    import javax.swing.JTable;
    import javax.swing.JTextArea;
    import javax.swing.table.TableCellRenderer;
    
     public class LineWrapCellRenderer extends JTextArea implements TableCellRenderer {
    
        @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
            int row, int column) {
        this.setText((String) value);
        this.setWrapStyleWord(true);
        this.setLineWrap(true);
    
        int fontHeight = this.getFontMetrics(this.getFont()).getHeight();
        int textLength = this.getText().length();
        int lines = textLength / this.getColumnWidth();
        if (lines == 0) {
            lines = 1;
        }
    
        int height = fontHeight * lines;
        table.setRowHeight(row, height);
    
        return this;
     }
    
    }
    
    0 讨论(0)
提交回复
热议问题