如何在 jtable 单元格中换行?

发布于 2024-07-24 21:44:20 字数 1596 浏览 5 评论 0原文

我正在尝试实现一个自定义 TableRenderer,如 this 中所述教程。 我想让渲染器对给定单元格太长的每个文本进行换行。 这个想法是,使用 TextArea 作为渲染器,因为它支持换行。 但是,以下代码的行为不符合预期:

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);         
        return this;
    }

}

我将此渲染器设置为

table.setDefaultRenderer(String.class, new LineWrapCellRenderer());

但单元格条目保持展开状态。 如果我将 this.setBackground(Color.YELLOW) 添加到 getTableCellRendererComponent() 方法, 所有细胞都如预期般呈黄色,但未包裹。

有任何想法吗?

更新:正如 Michael Borgwardt 在评论中所述,问题不是换行,而是行高:JTables 行的大小是固定的,因此如果单元格变得更高(因为文本现在是多个) -lines),我们必须增加行高。 但多少钱呢? 我会检查这是否值得另一个问题。 如果没有,我会在这里添加这个解决方案。

更新2:以下代码将确定行高(如果放置在getTableCellRendererComponent()中):

int fontHeight = this.getFontMetrics(this.getFont()).getHeight();
int textLength = this.getText().length();
int lines = textLength / this.getColumns() +1;//+1, cause we need at least 1 row.           
int height = fontHeight * lines;            
table.setRowHeight(row, height);

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 idea is, to use a TextArea as renderer, as it supports line wrapping. However, the following code does not behave as expected:

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);         
        return this;
    }

}

I set this renderer with

table.setDefaultRenderer(String.class, new LineWrapCellRenderer());

But the cell entries stay unwrapped.
If I add this.setBackground(Color.YELLOW) to the getTableCellRendererComponent() method,
all cells are yellow as expected, but not wrapped.

Any ideas?

UPDATE: As Michael Borgwardt stated in the comments, the problem is not the line wrap, but the row height: JTables rows are fixed size, so if a cell is getting higher (cause the text is now multi-lined), we have to increase the row height.
But how much? I will check if this is worth another SO-question. If not, I will add this solution here.

Update2: The following code will determine the row height (if placed in getTableCellRendererComponent()):

int fontHeight = this.getFontMetrics(this.getFont()).getHeight();
int textLength = this.getText().length();
int lines = textLength / this.getColumns() +1;//+1, cause we need at least 1 row.           
int height = fontHeight * lines;            
table.setRowHeight(row, height);

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

情绪 2024-07-31 21:44:20

问题在于 JTable 中的行高是固定的,因此这不仅仅是渲染器换行的问题; 我不确定为什么不这样做,但如果这样做,换行的文本将被裁剪 - 或者也许这正是您所看到的。 要调整行高,您需要单独设置它们。

这里'一些代码

int rows = 10;
int cols = 5;
JTable table = new JTable(rows, cols);

// Set the 1st row to 60 pixels high
table.setRowHeight(0, 60);

// Set the height of all rows to 32 pixels high,
// regardless if any heights were assigned to particular rows
table.setRowHeight(32);
// the height of the 1st row is set to 32 pixels high

// Returns the preferred height of a row.
// The result is equal to the tallest cell in the row.
public int getPreferredRowHeight(JTable table, int rowIndex, int margin) {
    // Get the current default height for all rows
    int height = table.getRowHeight();

    // Determine highest cell in the row
    for (int c=0; c<table.getColumnCount(); c++) {
        TableCellRenderer renderer = table.getCellRenderer(rowIndex, c);
        Component comp = table.prepareRenderer(renderer, rowIndex, c);
        int h = comp.getPreferredSize().height + 2*margin;
        height = Math.max(height, h);
    }
    return height;
}

// The height of each row is set to the preferred height of the
// tallest cell in that row.
public void packRows(JTable table, int margin) {
    packRows(table, 0, table.getRowCount(), margin);
}

// For each row >= start and < end, the height of a
// row is set to the preferred height of the tallest cell
// in that row.
public void packRows(JTable table, int start, int end, int margin) {
    for (int r=0; r<table.getRowCount(); r++) {
        // Get the preferred height
        int h = getPreferredRowHeight(table, r, margin);

        // Now set the row height using the preferred height
        if (table.getRowHeight(r) != h) {
            table.setRowHeight(r, h);
        }
    }
}

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.

Heres' some code for that:

int rows = 10;
int cols = 5;
JTable table = new JTable(rows, cols);

// Set the 1st row to 60 pixels high
table.setRowHeight(0, 60);

// Set the height of all rows to 32 pixels high,
// regardless if any heights were assigned to particular rows
table.setRowHeight(32);
// the height of the 1st row is set to 32 pixels high

// Returns the preferred height of a row.
// The result is equal to the tallest cell in the row.
public int getPreferredRowHeight(JTable table, int rowIndex, int margin) {
    // Get the current default height for all rows
    int height = table.getRowHeight();

    // Determine highest cell in the row
    for (int c=0; c<table.getColumnCount(); c++) {
        TableCellRenderer renderer = table.getCellRenderer(rowIndex, c);
        Component comp = table.prepareRenderer(renderer, rowIndex, c);
        int h = comp.getPreferredSize().height + 2*margin;
        height = Math.max(height, h);
    }
    return height;
}

// The height of each row is set to the preferred height of the
// tallest cell in that row.
public void packRows(JTable table, int margin) {
    packRows(table, 0, table.getRowCount(), margin);
}

// For each row >= start and < end, the height of a
// row is set to the preferred height of the tallest cell
// in that row.
public void packRows(JTable table, int start, int end, int margin) {
    for (int r=0; r<table.getRowCount(); r++) {
        // Get the preferred height
        int h = getPreferredRowHeight(table, r, margin);

        // Now set the row height using the preferred height
        if (table.getRowHeight(r) != h) {
            table.setRowHeight(r, h);
        }
    }
}
话少情深 2024-07-31 21:44:20

您好,我遇到了同样的问题,但我实现的解决方案受到 Java 教程中用于绘制多行文本的示例的启发,并使用文本 API 在单元格上绘制文本。

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;
    }   
}

它也会调整行高的大小,但只有当此渲染器用于单列时它才能很好地发挥作用。

这就是我用来调用它来渲染我的表格的方式。

final int wordWrapColumnIndex = ...;
myTable = new JTable() {    
    public TableCellRenderer getCellRenderer(int row, int column) {
        if (column == wordWrapColumnIndex ) {
            return wordWrapRenderer;
        }
        else {
            return super.getCellRenderer(row, column);
        }
    }
};

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);
        }
    }
};
掩于岁月 2024-07-31 21:44:20

除了这个问题之外,我还想与您分享多行单元格编辑器的解决方案。 它有点hacky(存储对编辑行的引用),但可以完成工作。

import javax.swing.*;
import javax.swing.table.TableCellEditor;
import java.awt.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

class MultilineTableCellEditor extends AbstractCellEditor implements TableCellEditor {

    JComponent component = new JTextArea();
    JTable table;
    int lastRowIndex;

    public MultilineTableCellEditor() {
        JTextArea textArea = ((JTextArea) component);
        textArea.setLineWrap(true);
        textArea.setWrapStyleWord(true);
        textArea.addComponentListener(new ComponentAdapter() {
            @Override
            public void componentResized(ComponentEvent e) {
                super.componentResized(e);
                table.setRowHeight(lastRowIndex, (int) (textArea.getPreferredSize().getHeight()));
            }
        });
        textArea.addKeyListener(new KeyAdapter() {
            @Override
            public void keyTyped(KeyEvent e) {
                super.keyTyped(e);
                table.setRowHeight(lastRowIndex, (int) (textArea.getPreferredSize().getHeight()));
            }
        });
    }

    public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected,
                                                 int rowIndex, int vColIndex) {
        this.table = table;
        lastRowIndex = rowIndex;

        ((JTextArea) component).setText((String) value);
        component.setFont(table.getFont());

        return component;
    }

    public Object getCellEditorValue() {
        return ((JTextArea) component).getText();
    }
}

用法如下:

    JTable table = new JTable(tableModel) {
        // Cell renderer by Alessandro Rossi (posted as solution to this question)
        MultilineTableCell renderer = new MultilineTableCell();
        MultilineTableCellEditor editor = new MultilineTableCellEditor();

        @Override
        public TableCellRenderer getCellRenderer(int row, int column) {
            if (column == multilineColumn) {
                return renderer;
            }
            return super.getCellRenderer(row, column);
        }

        @Override
        public TableCellEditor getCellEditor(int row, int column) {
            if ( column == multilineColumn ) {
                return editor;
            }
            return super.getCellEditor(row, column);
        }
    };

In addition to this question I'd like to share with you solution for multiline cell editor. It's a bit hacky (stores reference to edited row), but does the job.

import javax.swing.*;
import javax.swing.table.TableCellEditor;
import java.awt.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

class MultilineTableCellEditor extends AbstractCellEditor implements TableCellEditor {

    JComponent component = new JTextArea();
    JTable table;
    int lastRowIndex;

    public MultilineTableCellEditor() {
        JTextArea textArea = ((JTextArea) component);
        textArea.setLineWrap(true);
        textArea.setWrapStyleWord(true);
        textArea.addComponentListener(new ComponentAdapter() {
            @Override
            public void componentResized(ComponentEvent e) {
                super.componentResized(e);
                table.setRowHeight(lastRowIndex, (int) (textArea.getPreferredSize().getHeight()));
            }
        });
        textArea.addKeyListener(new KeyAdapter() {
            @Override
            public void keyTyped(KeyEvent e) {
                super.keyTyped(e);
                table.setRowHeight(lastRowIndex, (int) (textArea.getPreferredSize().getHeight()));
            }
        });
    }

    public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected,
                                                 int rowIndex, int vColIndex) {
        this.table = table;
        lastRowIndex = rowIndex;

        ((JTextArea) component).setText((String) value);
        component.setFont(table.getFont());

        return component;
    }

    public Object getCellEditorValue() {
        return ((JTextArea) component).getText();
    }
}

Used as so:

    JTable table = new JTable(tableModel) {
        // Cell renderer by Alessandro Rossi (posted as solution to this question)
        MultilineTableCell renderer = new MultilineTableCell();
        MultilineTableCellEditor editor = new MultilineTableCellEditor();

        @Override
        public TableCellRenderer getCellRenderer(int row, int column) {
            if (column == multilineColumn) {
                return renderer;
            }
            return super.getCellRenderer(row, column);
        }

        @Override
        public TableCellEditor getCellEditor(int row, int column) {
            if ( column == multilineColumn ) {
                return editor;
            }
            return super.getCellEditor(row, column);
        }
    };
悲念泪 2024-07-31 21:44:20

您可以使用 JLabel 作为呈现器,并将文本插入 HTML 标记,然后在适当的位置添加

如何在 Swing 组件中使用 HTML

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

困倦 2024-07-31 21:44:20

我偶然发现了同样的问题,我需要修改一下这里编写的代码,所以我附上我自己的版本:

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;
 }

}

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;
 }

}
放我走吧 2024-07-31 21:44:20

使用 Swing 正确实现此功能的唯一方法(我不了解 JavaFX:可能适用相同的原理)是理解并在渲染器组件上使用 setBounds

我是通过反复试验得出这个结论的,而不是检查源代码。 但很明显,该方法负责布局文本(以任何字体)并计算然后实现自动换行。

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

  }

}

上面的方法在这个 SSCCE 中工作得很好……但在现实世界中,随着字体更复杂、文本更多和表格更大,你就会开始遇到问题。 因此,我在下面建议使用新版本的 Listener 类以及新版本的渲染器(只是为了介绍复杂字体的使用......)。 如果有兴趣,请将这些替换到上面的 SSCCE 中......

/*
 * 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;
  }

}

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;
  }

}

用 HTML 编写标题。 这是我拥有的一个例子。 我遇到的唯一问题是,如果我调整标题的高度,我很难让它们在 JPanel 中滚动。

    myTable.getColumnModel().getColumn(1).setPreferredWidth(75);
    myTable.getColumnModel().getColumn(1).setHeaderValue("<html><b>Day Of<br>Week</b></html>");

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>");
腻橙味 2024-07-31 21:44:20

如上所述,需要计算行高,但当前的解决方案可以改进。 事实上,它对我不起作用。 jtxt.getColumns() 返回零导致除以零。 这是一些我认为更干净的代码:

// set the width on the jTextArea causing a calc of preferred height
jtxt.setSize(table.getWidth(), Short.MAX_VALUE);
int prefH = jtxt.getPreferredSize().height;
table.setRowHeight(row, prefH);

As noted above the row height needs to be calculated but the current solution could be improved. In fact, it wasn't working for me. jtxt.getColumns() was returning zero causing a divide by zero. Here's some code I think is cleaner:

// set the width on the jTextArea causing a calc of preferred height
jtxt.setSize(table.getWidth(), Short.MAX_VALUE);
int prefH = jtxt.getPreferredSize().height;
table.setRowHeight(row, prefH);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文