我正在尝试按照this tutorial中的说明实现自定义TableRenderer。 我想让渲染器换行包装给定单元格的每个文本。 这个想法是,使用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行是固定大小的,所以如果一个单元格越来越高(导致文本现在是多线的),我们必须增加行高。 但是多少钱?我会检查这是否值得另一个SO问题。如果没有,我会在这里添加此解决方案。
Update2:以下代码将确定行高(如果放在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);
答案 0 :(得分:11)
问题是JTable中行的高度是固定的,所以这不仅仅是渲染渲染器的问题;我不确定它为什么没有,但如果确实如此,那么包装好的文字会被裁剪掉 - 或许这正是你所看到的。要调整行高,您需要单独设置它们。
答案 1 :(得分:6)
您好我遇到了同样的问题,但我实施的解决方案的灵感来自Java Tutorial提供的用于绘制多行文本的示例,并使用文本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);
}
}
};
答案 2 :(得分:3)
除了这个问题,我还想与您分享多线格编辑器的解决方案。它有点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);
}
};
答案 3 :(得分:2)
您可以将JLabel用作渲染器并将文本插入HTML标记,只需在适当的位置添加<br>
答案 4 :(得分:2)
我偶然发现了同样的问题,我需要修改一下这里编写的代码,所以我附上自己的版本:
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;
}
}
答案 5 :(得分:0)
以HTML格式编写标题。这是我的一个例子。我遇到的唯一问题是,如果我调整标题的高度,我很难让它们在JPanel
中滚动。
myTable.getColumnModel().getColumn(1).setPreferredWidth(75);
myTable.getColumnModel().getColumn(1).setHeaderValue("<html><b>Day Of<br>Week</b></html>");
答案 6 :(得分:0)
在渲染器组件上使用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;
}
}
答案 7 :(得分:0)
如上所述,需要计算行高,但可以改进当前的解决方案。事实上,它不适合我。 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);