2009-06-08 115 views
31

我試圖按照this tutorial中所述實現自定義TableRenderer。 我想讓渲染器換行每個文本的長度爲給定的單元格。 這個想法是,使用TextArea作爲渲染器,因爲它支持換行。但是,下面的代碼不會像預期的那樣:如何在jtable單元格中換行?

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); 
+3

不要更改渲染器中的表狀態 - 如**從來沒有** – kleopatra 2012-10-26 08:20:51

+15

親愛的Cleopatra !!!請給我們一個工作解決方案,而不是隻說你可以做得更好。 – 2012-11-01 10:23:30

+0

請檢查這個http://stackoverflow.com/questions/33937074/jtable-cell-wrapping/38932843#38932843 – Yougesh 2016-08-13 12:53:10

回答

11

的問題是,在JTable中列的高度是固定的,所以它不只是爲具有一個渲染器包裹的事S;我不確定它爲什麼沒有,但如果它確實如此,封裝的文本會被裁剪 - 或者,這也正是你所看到的。要調整行高,您需要單獨設置它們。

+0

這似乎是問題。將行高設置爲更大的值後,會出現換行。現在的問題:如何獲得完美的新高度。 – Arvodan 2009-06-08 14:08:08

6

嗨我有你的同樣的問題,但我實現的解決方案的靈感來自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; 
    } 
} 

它會調整行heigth太多,但它確實很好,只有當該渲染器用於單個列。

這就是我用來渲染我的表的方式。

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

在HTML中寫入標題。這是我擁有的一個例子。我遇到的唯一問題是我很難讓他們滾動JPanel,如果我調整標題的高度。

myTable.getColumnModel().getColumn(1).setPreferredWidth(75); 
    myTable.getColumnModel().getColumn(1).setHeaderValue("<html><b>Day Of<br>Week</b></html>"); 
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; 
    } 

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

除了這個問題,我想與你分享多線單元編輯器的解決方案。這有點哈克(存儲引用編輯的行),但做這項工作。

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

我在這個同樣的問題迷迷糊糊的,我需要改變一點點,它在這裏寫的代碼,所以我附上我自己的版本:

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

}