As simple as Renderers and Editors sound and despite the dozen or so SO bookmarks I return to regarding similar issues I’m missing something elementary. I want to drag any old text file into a 2-column JTable, have the first column display the filename and the second contain a JComboBox whose options depend on the contents of the dragged file. (In the code below I just fake a few entries.)
This all works fine until I make a selection from a combo box - the selection doesn’t display - just a combo box, populated correctly but no selection made. I know it must have something to do with my misuse of renderers/editors but after at least two weeks of flailing I’m seeking professional help. And if you think I’ve totally missed the boat on how renderers and editors are written, well, I’m glad you didn’t see my earlier attempts.
Hopefully this code qualifies as an SSCCE - sincere apologies if I’ve included something I shouldn’t have. I’ve retained the DnD stuff just in case it has some significance.
For what it’s worth, I use a static list of ComboBoxModels (one per row) since each JComboBox contains different options, and likewise TableCellEditors (although I don’t know if that’s the right way to go about it).
To run this just drag any file into the table that appears and then make a selection from the JComboBox in the right column and watch it ignore you. Thanks very much, even if you have some advice without taking the trouble of running this.
Java 1.7/OS X 10.9.5/Eclipse Mars.2
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.swing.AbstractCellEditor;
import javax.swing.DefaultCellEditor;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.MutableComboBoxModel;
import javax.swing.SwingUtilities;
import javax.swing.TransferHandler;
import javax.swing.event.ListDataListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
public class Main extends JFrame {
static List<AComboBoxModel> priceComboModels = new ArrayList<AComboBoxModel>();
static List<DefaultCellEditor> editors = new ArrayList<DefaultCellEditor>();
public Main() {
setLayout(new BorderLayout());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setPreferredSize(new Dimension(500, 400));
JPanel panel = new JPanel(new BorderLayout());
JTable table = new JTable(0, 2) {
public TableCellEditor getCellEditor(int rinx, int cinx) {
if (cinx == 0) {
return super.getCellEditor(rinx, cinx);
}
return editors.get(rinx);
}
};
table.setPreferredScrollableViewportSize(new Dimension(360, 80));
table.setTransferHandler(new ATransferHandler());
table.setModel(new ATableModel());
TableColumnModel tcm = table.getColumnModel();
tcm.getColumn(0).setHeaderValue("File Name");
tcm.getColumn(1).setHeaderValue("Selection");
TableColumn column = tcm.getColumn(1);
column.setCellRenderer(new ACellRenderer());
column.setCellEditor(new ACellEditor());
table.setDragEnabled(true);
table.setFillsViewportHeight(true);
JScrollPane sp = new JScrollPane(
table,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED
);
panel.add(sp, BorderLayout.CENTER);
panel.setPreferredSize(new Dimension(200, 300));
add(panel, BorderLayout.CENTER);
pack();
}
public static int addComboModel(AComboBoxModel model) {
priceComboModels.add(model);
return priceComboModels.size() - 1;
}
public static AComboBoxModel getComboModelAt(int inx) {
return priceComboModels.get(inx);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new Main().setVisible(true);
}
});
}
}
class ATableModel extends DefaultTableModel {
List<ARecord> data = new ArrayList<ARecord>();
public void addRow(ARecord row) {
data.add(row);
fireTableRowsInserted(data.size() - 1, data.size() - 1);
}
#Override
public int getRowCount() {
return data == null ? 0 : data.size();
}
#Override
public int getColumnCount() {
return 2;
}
public void setValueAt(Object value, int rinx, int cinx) {
ARecord row = data.get(rinx);
switch (cinx) {
case 0:
row.setFilename((String) value);
break;
case 1:
row.setCbox((JComboBox) value);
break;
}
}
#Override
public Object getValueAt(int rinx, int cinx) {
Object returnValue = null;
ARecord row = data.get(rinx);
switch (cinx) {
case 0:
returnValue = row.getFilename();
break;
case 1:
returnValue = row.getCbox();
break;
}
return returnValue;
}
// I assume this is unnecessary since column 1 defaults to text
// and column 2 is handled by ACellRenderer. I think.
// #Override
// public Class getColumnClass(int cinx) {
// return cinx == 0 ? String.class : JComboBox.class;
// }
}
//////////////////////////////////////////////////////////////////////////////////
// This class handles the drag and drop.
class ATransferHandler extends TransferHandler {
int getSourceActions(JList<String> lst) {
return TransferHandler.COPY;
}
Transferable createTransferable(JList<String> list) {
return null;
}
void exportDone(JList<String> lst, Transferable data, int action) {
}
public boolean canImport(TransferHandler.TransferSupport info) {
return true;
}
//////////////////////////////////////////////////////////////////////////
// This is the method of interest where the dropped text file is handled.
//////////////////////////////////////////////////////////////////////////
public boolean importData(TransferHandler.TransferSupport info) {
if (! info.isDrop()) return false;
JTable table = (JTable)info.getComponent();
Transferable tr = info.getTransferable();
List<File> files = null;
try {
files = (List<File>)tr.getTransferData(DataFlavor.javaFileListFlavor);
} catch(UnsupportedFlavorException | IOException e) {
}
ATableModel tm = (ATableModel)table.getModel();
String[] options;
// For each dropped text file...
for (File fl : files) {
String fname = fl.getName();
// Just fill the JComboBox with some unique options for now
// (in practice this comes from the dropped text file contents).
String dummyText = fname.substring(0, 5);
options = new String[] { dummyText + "_A", dummyText + "_B", dummyText + "_C" };
// Create a ComboBoxModel for this JComboBox containing the selection options.
AComboBoxModel cboModel = new AComboBoxModel(options);
// Create the combo box itself.
JComboBox<String> cbox = new JComboBox<String>();
// Assign the model to the box.
cbox.setModel(cboModel);
// Create and add to the editor list the table cell editor.
Main.editors.add(new DefaultCellEditor(cbox));
// Also add the ComboBoxModel to the model list.
Main.addComboModel(cboModel);
// Add the row to the model data.
tm.addRow(new ARecord(fname, cbox));
}
return true;
}
}
///////////////////////////////////////////////////////////////////////////////////////////
class ARecord {
String filename;
JComboBox cbox;
// Just a bean to describe a table row (a filename and a JComboBox).
public ARecord(String filename, JComboBox cbox) {
super();
this.filename = filename;
this.cbox = cbox;
}
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
public JComboBox getCbox() {
return cbox;
}
public void setCbox(JComboBox cbox) {
this.cbox = cbox;
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// This is the model for the JComboBoxes. A different model is instantiated
// for each row since each one has different contents.
class AComboBoxModel implements MutableComboBoxModel {
List<String> items = new ArrayList<String>();
public AComboBoxModel(String[] items) {
this.items = Arrays.asList(items);
}
#Override
public int getSize() {
return items.size();
}
#Override
public Object getElementAt(int index) {
return items.get(index);
}
#Override
public void addListDataListener(ListDataListener l) {
}
#Override
public void removeListDataListener(ListDataListener l) {
}
#Override
public void setSelectedItem(Object anItem) {
}
#Override
public Object getSelectedItem() {
return null;
}
#Override
public void addElement(Object item) {
}
#Override
public void removeElement(Object obj) {
}
#Override
public void insertElementAt(Object item, int index) {
}
#Override
public void removeElementAt(int index) {
}
}
//////////////////////////////////////////////////////////////////////////////////////
// I won't pretend that I'm confident as to how this should work. My guess is that
// I should just retrieve the appropriate ComboBoxModel, assign it and return.
class ACellRenderer extends JComboBox implements TableCellRenderer {
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
int rinx, int cinx) {
setModel(Main.getComboModelAt(rinx));
return this;
}
}
/////////////////////////////////////////////////////////////////////////////////////////
class ACellEditor extends AbstractCellEditor implements TableCellEditor {
static JComboBox box = null;
// This is where I think I'm actually lost. I don't understand the significance of
// returning a JComboBox when one was already created when the text file was
// dropped. Is it correct to just assign the appropriate ComboBoxModel to a JComboBox
// and return it here?
public Component getTableCellEditorComponent(JTable table,
Object value,
boolean isSelected,
int rinx,
int cinx) {
box = (JComboBox)(table.getModel().getValueAt(rinx, cinx));
box.setModel(Main.getComboModelAt(rinx));
return box;
}
#Override
public Object getCellEditorValue() {
return box;
}
}
make a selection from the JComboBox in the right column and watch it ignore you
Something is wrong with your custom editor and I'm not sure what. You have a big problem in that you are trying to use a JComboBox as the data of the editor. This is completely wrong.
But the good new is that there is no need for you to use a custom renderer or a custom editor.
You should NOT be storing a JComboBox in the TableModel. You simply store the String of the selected item from the combo box. (This will be done for you automatically by the default combo box editor).
There is no need for you to create a new editor for every file that is dragged to the table.
the second contain a JComboBox whose options depend on the contents of the dragged file
So the only part of the table that you need to customize is the getCellEditor(...) method.
I would guess you would have a different editor for a given file extension.
So the basic code might be something like:
int modelColumn = convertColumnIndexToModel( column );
if (modelColumn == 1)
{
String file = getModel.getValueAt(row, 0);
if (file.endsWith(".txt"))
return txtEditor;
else if (file.endsWith(".html"))
return htmlEditor;
}
return super.getCellEditor(row, column);
Check out:
How to add unique JComboBoxes to a column in a JTable (Java) for a working example. The logic in that posting does have a separate editor by row for demonstration purposes only. The example demonstrates that the code works with the default renderers and editors. All you need to do is provide the items for each combo box editor.
In your case the editor will be based on the file type so the logic needs to test the data in the first column.
Note: the nested if/else statement is not a good solution. You might want to use a Hashmap of filetype/editor. Then the getCellEditor(...) method would just be a Hashmap lookup once you extract the filetype for the File.
So your dragging code should have nothing to do with the editors of the table. You need to know before hand which file types you want to support and define the valid items for each of those file types.
Also, your TableModel should NOT extend DefaultTableModel. You are providing your own data storage and implementing all the methods so you should just be extending the AbstractTableModel.
Related
How can I obtain a multiline JTable header where the header column correctly enlarges to fit some text and then wraps to a new line?
Something like shown below:
Currently searching for the above requirements returns a lot of solutions of which none really solves the problem:
http://www.javarichclient.com/multiline-column-header/
Creating multi-line header for JTable
Java JTable header word wrap
The above solutions all propose using HTML code, for instance:
String[] columnNames = {
"<html><center>Closing<br>Date</html>",
"<html><center>Open<br>Price</html>",
"<html>Third<br>column</html>"
};
That solution is not elegant for a couple of reasons, mainly because in the case of variable columns names I need to pass the string to a function which strips spaces and subtitutes them with <br> symbols, however if the column text contains very short text that appears in a line of its own.
I would need to decide a minimum and a maximum length of a column and then be able to make text centering possible, the above solution quickly becomes overengineered and unmanageable.
http://www.java2s.com/Code/Java/Swing-Components/MultiLineHeaderTable.htm
http://www.java2s.com/Code/Java/Swing-Components/MultiLineHeaderExample.htm
Above solutions require manually creating a header array with words already correctly split up as in:
public static Object[][] tableHeaders = new Object[][] {
new String[] { "Currency" },
new String[] { "Yesterday's", "Rate" },
new String[] { "Today's", "Rate" },
new String[] { "Rate", "Change" } };
-or-
DefaultTableModel dm = new DefaultTableModel();
dm.setDataVector(
new Object[][] { { "a", "b", "c" }, { "A", "B", "C" } },
new Object[] { "1st\nalpha", "2nd\nbeta", "3rd\ngamma" });
Still not elegant because variable text in the column names would not be feasible.
How to change JTable header height?
Manually setting the header height as in the above solutions is only half of what I want to do, because then text would still not correctly wrap and deciding the height is still not feasible.
Currently all I was able was to create a custom TableCellRenderer but yet no solution:
import java.awt.Component;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import java.util.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import javax.swing.*;
import javax.swing.table.*;
/**
* #version 1.0 11/09/98
*/
public class MultiLineHeaderExample extends JFrame
{
MultiLineHeaderExample()
{
super("Multi-Line Header Example");
DefaultTableModel dm = new DefaultTableModel();
dm.setDataVector(new Object[][]
{
{
"a", "b", "c"
},
{
"A", "B", "C"
}
},
new Object[]
{
"My First Column, Very Long But Space Separated", "short col", "VeryLongNoSpaceSoShouldSomeHowWrap"
});
JTable table = new JTable(dm);
MultiLineHeaderRenderer renderer = new MultiLineHeaderRenderer();
Enumeration enumK = table.getColumnModel().getColumns();
while (enumK.hasMoreElements())
{
((TableColumn) enumK.nextElement()).setHeaderRenderer(renderer);
}
JScrollPane scroll = new JScrollPane(table);
getContentPane().add(scroll);
setSize(400, 110);
setVisible(true);
}
public static void main(String[] args)
{
MultiLineHeaderExample frame = new MultiLineHeaderExample();
frame.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
}
}
class MultiLineHeaderRenderer extends JList implements TableCellRenderer
{
public MultiLineHeaderRenderer()
{
ListCellRenderer renderer = getCellRenderer();
((JLabel) renderer).setHorizontalAlignment(JLabel.CENTER);
setCellRenderer(renderer);
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column)
{
setFont(table.getFont());
String str = (value == null) ? "" : value.toString();
BufferedReader br = new BufferedReader(new StringReader(str));
String line;
Vector v = new Vector();
try
{
while ((line = br.readLine()) != null)
{
v.addElement(line);
}
}
catch (IOException ex)
{
ex.printStackTrace();
}
setListData(v);
return this;
}
}
This here also uses JTextArea and also resizes the header height when the table is resized. The key to the correct calculation of the table header height is setSize(width, getPreferredSize().height);
class MultiLineTableHeaderRenderer extends JTextArea implements TableCellRenderer
{
public MultiLineTableHeaderRenderer() {
setEditable(false);
setLineWrap(true);
setOpaque(false);
setFocusable(false);
setWrapStyleWord(true);
LookAndFeel.installBorder(this, "TableHeader.cellBorder");
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
int width = table.getColumnModel().getColumn(column).getWidth();
setText((String)value);
setSize(width, getPreferredSize().height);
return this;
}
}
you need a Conponent that is able to wordwrap its content like JTextArea.
I changed the cell renderer from your SSCCE so that is works initially, but it has a nasty resize behavior.
class MultiLineHeaderRenderer extends JTextArea implements TableCellRenderer {
public MultiLineHeaderRenderer()
{
setAlignmentY(JLabel.CENTER);
setLineWrap(true);
setWrapStyleWord(true);
setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(Color.BLACK),
BorderFactory.createEmptyBorder(3,3,3,3)
));
}
#Override
public Component getTableCellRendererComponent(JTable table,
Object value,
boolean isSelected,
boolean hasFocus,
int row,
int column) {
setFont(table.getFont());
String str = (value == null) ? "" : value.toString();
setText(str);
int columnWidth= getColumnWidth();
setRows(str.length()/columnWidth);
return this;
}
}
Here is another approach. This solution has the following advantages:
You need not manually break the column names.
The columns dynamically word-wrap as you resize the columns and/or window.
The header appearance will automatically be consistent with the installed look-and-feel.
Unlike other solutions I have seen, this works even if the first column doesn't wrap (as in the example below).
It has the following disadvantage, however: It creates an unused JTableHeader object for each column, so it's a bit inelegant and probably not suitable if you have many columns.
The basic idea is that you wrap the column names in an <html> tags, and, crucially, every TableColumn gets its own TableCellRenderer object.
I came to this solution after debugging deep into the guts of the Swing table header layout plumbing. Without getting too much into the weeds, the problem is that if the TableColumns don't have a headerRenderer defined, the same default renderer is used for every column header cell. The layout code used for JTableHeader only bothers to ask the renderer of the first column header for its preferred size (see feature 4. above), and because the renderer is re-used, the call to its setText() method triggers the creation of a new View for the label, which, for reasons I'm too tired to even think about explaining, causes the header renderer to always report its preferred unwrapped height.
Here is a quick-and-dirty proof-of-concept:
package scratch;
import java.util.*;
import javax.swing.*;
import javax.swing.table.*;
#SuppressWarnings("serial")
public class WordWrappingTableHeaderDemo extends JFrame {
class DemoTableModel extends AbstractTableModel {
private ArrayList<String> wrappedColumnNames = new ArrayList<String>();
private int numRows;
DemoTableModel(List<String> columnNames, int numRows) {
for (String name: columnNames)
wrappedColumnNames.add("<html>" + name + "</html>");
this.numRows = numRows;
}
public int getRowCount() {
return numRows;
}
public int getColumnCount() {
return wrappedColumnNames.size();
}
public Object getValueAt(int rowIndex, int columnIndex) {
return Integer.valueOf(10000 + (rowIndex + 1)*(columnIndex + 1));
}
public String getColumnName(int column) {
return wrappedColumnNames.get(column);
}
public Class<?> getColumnClass(int columnIndex) {
return Integer.class;
}
}
public WordWrappingTableHeaderDemo() {
DefaultTableColumnModel tableColumnModel = new DefaultTableColumnModel() {
public void addColumn(TableColumn column) {
// This works, but is a bit kludgey as it creates an unused JTableHeader object for each column:
column.setHeaderRenderer(new JTableHeader().getDefaultRenderer());
super.addColumn(column);
}
};
JTable table = new JTable();
table.setFillsViewportHeight(true);;
table.setColumnModel(tableColumnModel);
table.setModel(
new DemoTableModel(Arrays.asList("Name", "The Second Column Name is Very Long", "Column Three"), 20));
getContentPane().add(new JScrollPane(table));
}
public static void createAndShowGUI() {
WordWrappingTableHeaderDemo app = new WordWrappingTableHeaderDemo();
app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
app.setLocationByPlatform(true);
app.pack();
app.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {createAndShowGUI();});
}
}
How can I obtain a multiline JTable header where the header column correctly enlarges to fit some text and then wraps to a new line?
Something like shown below:
Currently searching for the above requirements returns a lot of solutions of which none really solves the problem:
http://www.javarichclient.com/multiline-column-header/
Creating multi-line header for JTable
Java JTable header word wrap
The above solutions all propose using HTML code, for instance:
String[] columnNames = {
"<html><center>Closing<br>Date</html>",
"<html><center>Open<br>Price</html>",
"<html>Third<br>column</html>"
};
That solution is not elegant for a couple of reasons, mainly because in the case of variable columns names I need to pass the string to a function which strips spaces and subtitutes them with <br> symbols, however if the column text contains very short text that appears in a line of its own.
I would need to decide a minimum and a maximum length of a column and then be able to make text centering possible, the above solution quickly becomes overengineered and unmanageable.
http://www.java2s.com/Code/Java/Swing-Components/MultiLineHeaderTable.htm
http://www.java2s.com/Code/Java/Swing-Components/MultiLineHeaderExample.htm
Above solutions require manually creating a header array with words already correctly split up as in:
public static Object[][] tableHeaders = new Object[][] {
new String[] { "Currency" },
new String[] { "Yesterday's", "Rate" },
new String[] { "Today's", "Rate" },
new String[] { "Rate", "Change" } };
-or-
DefaultTableModel dm = new DefaultTableModel();
dm.setDataVector(
new Object[][] { { "a", "b", "c" }, { "A", "B", "C" } },
new Object[] { "1st\nalpha", "2nd\nbeta", "3rd\ngamma" });
Still not elegant because variable text in the column names would not be feasible.
How to change JTable header height?
Manually setting the header height as in the above solutions is only half of what I want to do, because then text would still not correctly wrap and deciding the height is still not feasible.
Currently all I was able was to create a custom TableCellRenderer but yet no solution:
import java.awt.Component;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import java.util.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import javax.swing.*;
import javax.swing.table.*;
/**
* #version 1.0 11/09/98
*/
public class MultiLineHeaderExample extends JFrame
{
MultiLineHeaderExample()
{
super("Multi-Line Header Example");
DefaultTableModel dm = new DefaultTableModel();
dm.setDataVector(new Object[][]
{
{
"a", "b", "c"
},
{
"A", "B", "C"
}
},
new Object[]
{
"My First Column, Very Long But Space Separated", "short col", "VeryLongNoSpaceSoShouldSomeHowWrap"
});
JTable table = new JTable(dm);
MultiLineHeaderRenderer renderer = new MultiLineHeaderRenderer();
Enumeration enumK = table.getColumnModel().getColumns();
while (enumK.hasMoreElements())
{
((TableColumn) enumK.nextElement()).setHeaderRenderer(renderer);
}
JScrollPane scroll = new JScrollPane(table);
getContentPane().add(scroll);
setSize(400, 110);
setVisible(true);
}
public static void main(String[] args)
{
MultiLineHeaderExample frame = new MultiLineHeaderExample();
frame.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
}
}
class MultiLineHeaderRenderer extends JList implements TableCellRenderer
{
public MultiLineHeaderRenderer()
{
ListCellRenderer renderer = getCellRenderer();
((JLabel) renderer).setHorizontalAlignment(JLabel.CENTER);
setCellRenderer(renderer);
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column)
{
setFont(table.getFont());
String str = (value == null) ? "" : value.toString();
BufferedReader br = new BufferedReader(new StringReader(str));
String line;
Vector v = new Vector();
try
{
while ((line = br.readLine()) != null)
{
v.addElement(line);
}
}
catch (IOException ex)
{
ex.printStackTrace();
}
setListData(v);
return this;
}
}
This here also uses JTextArea and also resizes the header height when the table is resized. The key to the correct calculation of the table header height is setSize(width, getPreferredSize().height);
class MultiLineTableHeaderRenderer extends JTextArea implements TableCellRenderer
{
public MultiLineTableHeaderRenderer() {
setEditable(false);
setLineWrap(true);
setOpaque(false);
setFocusable(false);
setWrapStyleWord(true);
LookAndFeel.installBorder(this, "TableHeader.cellBorder");
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
int width = table.getColumnModel().getColumn(column).getWidth();
setText((String)value);
setSize(width, getPreferredSize().height);
return this;
}
}
you need a Conponent that is able to wordwrap its content like JTextArea.
I changed the cell renderer from your SSCCE so that is works initially, but it has a nasty resize behavior.
class MultiLineHeaderRenderer extends JTextArea implements TableCellRenderer {
public MultiLineHeaderRenderer()
{
setAlignmentY(JLabel.CENTER);
setLineWrap(true);
setWrapStyleWord(true);
setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(Color.BLACK),
BorderFactory.createEmptyBorder(3,3,3,3)
));
}
#Override
public Component getTableCellRendererComponent(JTable table,
Object value,
boolean isSelected,
boolean hasFocus,
int row,
int column) {
setFont(table.getFont());
String str = (value == null) ? "" : value.toString();
setText(str);
int columnWidth= getColumnWidth();
setRows(str.length()/columnWidth);
return this;
}
}
Here is another approach. This solution has the following advantages:
You need not manually break the column names.
The columns dynamically word-wrap as you resize the columns and/or window.
The header appearance will automatically be consistent with the installed look-and-feel.
Unlike other solutions I have seen, this works even if the first column doesn't wrap (as in the example below).
It has the following disadvantage, however: It creates an unused JTableHeader object for each column, so it's a bit inelegant and probably not suitable if you have many columns.
The basic idea is that you wrap the column names in an <html> tags, and, crucially, every TableColumn gets its own TableCellRenderer object.
I came to this solution after debugging deep into the guts of the Swing table header layout plumbing. Without getting too much into the weeds, the problem is that if the TableColumns don't have a headerRenderer defined, the same default renderer is used for every column header cell. The layout code used for JTableHeader only bothers to ask the renderer of the first column header for its preferred size (see feature 4. above), and because the renderer is re-used, the call to its setText() method triggers the creation of a new View for the label, which, for reasons I'm too tired to even think about explaining, causes the header renderer to always report its preferred unwrapped height.
Here is a quick-and-dirty proof-of-concept:
package scratch;
import java.util.*;
import javax.swing.*;
import javax.swing.table.*;
#SuppressWarnings("serial")
public class WordWrappingTableHeaderDemo extends JFrame {
class DemoTableModel extends AbstractTableModel {
private ArrayList<String> wrappedColumnNames = new ArrayList<String>();
private int numRows;
DemoTableModel(List<String> columnNames, int numRows) {
for (String name: columnNames)
wrappedColumnNames.add("<html>" + name + "</html>");
this.numRows = numRows;
}
public int getRowCount() {
return numRows;
}
public int getColumnCount() {
return wrappedColumnNames.size();
}
public Object getValueAt(int rowIndex, int columnIndex) {
return Integer.valueOf(10000 + (rowIndex + 1)*(columnIndex + 1));
}
public String getColumnName(int column) {
return wrappedColumnNames.get(column);
}
public Class<?> getColumnClass(int columnIndex) {
return Integer.class;
}
}
public WordWrappingTableHeaderDemo() {
DefaultTableColumnModel tableColumnModel = new DefaultTableColumnModel() {
public void addColumn(TableColumn column) {
// This works, but is a bit kludgey as it creates an unused JTableHeader object for each column:
column.setHeaderRenderer(new JTableHeader().getDefaultRenderer());
super.addColumn(column);
}
};
JTable table = new JTable();
table.setFillsViewportHeight(true);;
table.setColumnModel(tableColumnModel);
table.setModel(
new DemoTableModel(Arrays.asList("Name", "The Second Column Name is Very Long", "Column Three"), 20));
getContentPane().add(new JScrollPane(table));
}
public static void createAndShowGUI() {
WordWrappingTableHeaderDemo app = new WordWrappingTableHeaderDemo();
app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
app.setLocationByPlatform(true);
app.pack();
app.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {createAndShowGUI();});
}
}
It is my first post in StackOverflow.
I did not find a SSCCE on a JTable whose row cells contain JRadioButton's - in several (not all) columns, contiguous columns and not always the same columns for each row - and whose ButtonGroup's correspond with the rows.
So I tried to elaborate it after revisiting the topics of Renderer and Editor in 'How to use Tables - http://docs.oracle.com/javase/tutorial/uiswing/components/table.html
and also after studying the following discussions :
- Adding (a column of) JRadioButton into JTable Adding jRadioButton into jTable (Guillaume Polet)
- How to add JRadioButton to group in JTable How to add JRadioButton to group in JTable
I make use of ...
- Swing
- TableCellEditor and TableCellRenderer
- Beans.PropertyChangeSupport, PropertyChangeListener
My Java code works ... but with an error of behaviour and incompletely.
It is even though going on a functionnality that is - I think - of great interest to much Java programmers.
A personal style rule : All syntax words of Java, Swing, ... : English, as in the libraries. All other items, particular to my application : Another language; here French.
Finally, here is the "One copy and paste" class file [TestTable2] containing 4 sub-classes [RendeurEditeur_CelluleBoutonRadio], [MonModeleTable], [GestionnObjetsDUneRangee] and [MonObjet]. It works. 420 lines for this SSCCE to copy and paste :
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractCellEditor;
import javax.swing.JFrame;
import javax.swing.JRadioButton;
// import javax.swing.ButtonGroup;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException; // main()
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
public class TestTable2
{
private JFrame cadre;
private JTable table;
protected void initUI()
{
table = new JTable(new MonModeleTable());
short nbreColo = (short) table.getColumnCount();
table.setRowHeight(25);
TableColumn col;
// With Java, you can specify cell renderers and editors either by column or by data type.
for (int i = 0 ; i < nbreColo ; i++)
{
col = table.getColumnModel().getColumn(i);
col.setCellEditor (new RendeurEditeur_CelluleBoutonRadio());
col.setCellRenderer(new RendeurEditeur_CelluleBoutonRadio());
}
cadre = new JFrame();
cadre.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
cadre.add(new JScrollPane(table), BorderLayout.CENTER);
cadre.pack();
cadre.setVisible(true);
}
/**
* #param args the command line arguments
* #throws ClassNotFoundException
* #throws InstantiationException
* #throws IllegalAccessException
* #throws UnsupportedLookAndFeelException
*/
public static void main(String[] args) throws ClassNotFoundException, InstantiationException,
IllegalAccessException, UnsupportedLookAndFeelException
{
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
SwingUtilities.invokeLater(new Runnable()
{
#Override public void run() // Implements method from java.lang.Runnable
{
new TestTable2().initUI();
}
} );
}
private class RendeurEditeur_CelluleBoutonRadio extends AbstractCellEditor
implements TableCellRenderer, TableCellEditor, ActionListener
{
private final JRadioButton radioButton;
public RendeurEditeur_CelluleBoutonRadio()
{
this.radioButton = new JRadioButton();
radioButton.addActionListener(this);
radioButton.setOpaque(false);
}
#Override public Component getTableCellRendererComponent(JTable table, Object valeur,
boolean estSelectionne, boolean hasFocus, int row, int column)
{
radioButton.setSelected(Boolean.TRUE.equals(valeur));
return radioButton;
}
#Override public Component getTableCellEditorComponent(JTable table, Object valeur,
boolean estSelectionne, int row, int column)
{
radioButton.setSelected(Boolean.TRUE.equals(valeur));
return radioButton;
}
#Override public void actionPerformed(ActionEvent e)
{
stopCellEditing();
}
#Override public Object getCellEditorValue()
{
return radioButton.isSelected();
}
}
/**
*
*/
class MonModeleTable extends AbstractTableModel implements PropertyChangeListener
{
// Manages the rows of radioButtons and their groupings.
private GestionnObjetsDUneRangee gestionn_DonneesDUneRang;
private final List<GestionnObjetsDUneRangee> gestionn_DonneesTteLatable = new ArrayList<>();
public MonModeleTable()
{
super(); // AbstractTableModel()
MonObjet objet;
for (short idxRang = 0 ; idxRang < 5 ; idxRang++)
{
gestionn_DonneesDUneRang = new GestionnObjetsDUneRangee();
/* To record 'this' (= MonModeleTable) as listener of the messages sent
* by the object ...
* - to which a property (gestionn_DonneesDUneRang) of 'MonModeleTable'-itself
* refers to, and
* - pertaining to a class (GestionnObjetsDUneRangee) containing a property (suppoChangePropri)
* refering to an instanciation of PropertyChangeSupport. This later has as argument
* the object of class 'GestionnObjetsDUneRangee'. */
// Leaking 'this' in constructor : Passing suspicious parameter in the constructor.
gestionn_DonneesDUneRang.getSuppoChangePropri().addPropertyChangeListener(this);
for (short idxColo = 0 ; idxColo < 4 ; idxColo++)
{
objet = new MonObjet(idxRang, idxColo);
gestionn_DonneesDUneRang.ajoutObjetÀRangee(objet);
if (idxColo == 0)
objet.setSelectionne(true);
/* To record 'MonModeleTable' as listener of the messages sent by each
* object of the list 'gestionn_DonneesDUneRang'.*/
objet.getSupportChangtPropri().addPropertyChangeListener(this);
}
gestionn_DonneesTteLatable.add(gestionn_DonneesDUneRang);
}
}
// Rem.: Identity of the object's row and column (JRadioButton) available in 'evt.getSource();'.
#Override public void propertyChange(PropertyChangeEvent evt)
{
System.out.print(evt.getPropertyName() + "\t");
Object objet2 = evt.getSource();
if (objet2 == gestionn_DonneesDUneRang)
{
if (evt.getPropertyName().equals("objecten")) // MonModeleTable
{
((MonObjet) evt.getNewValue()).getSupportChangtPropri().addPropertyChangeListener(this);
}
fireTableDataChanged();
System.out.println("");
} else if (objet2 instanceof MonObjet)
{ // When another button of the row has been activated ("geselecteerd")
short[] coordBP = ((MonObjet) objet2).getCoordBP();
fireTableRowsUpdated(coordBP[0], coordBP[0]); // [0] : 2 x row
System.out.println("");
}
}
#Override public int getColumnCount()
{
return gestionn_DonneesDUneRang.getObjetsDUneRangee().size();
}
#Override public int getRowCount()
{
return gestionn_DonneesTteLatable.size();
}
// Exporting non-public type through public API
public MonObjet getValueAt(int col)
{
return gestionn_DonneesDUneRang.getObjetsDUneRangee().get(col);
}
// Implements method from javax.swing.table.TableModel
#Override public Object getValueAt(int idxRang, int idxColo)
{
return getValueAt(idxColo).isSelectionne();
}
// This method can also import idxRang2 and idxColo2 in this class.
// Overrides method from javax.swing.table.AbstractTableModel
#Override public void setValueAt(Object bpActionne, int idxRang2, int idxColo2)
{
getValueAt(idxColo2).setSelectionne(Boolean.TRUE.equals(bpActionne));
}
// Overrides method from javax.swing.table.AbstractTableModel
#Override public boolean isCellEditable(int idxRang3, int idxColo3)
{
return true; // idxRang3 == 1;
}
/** There must be a specification of a renderer for the cells for the table not to invoke the
* table model's 'getColumnClass' method (which gets the data type of the column's cells). */
// Overrides method from javax.swing.table.AbstractTableModel
#Override public Class<?> getColumnClass(int rang)
{
return Object.class;
}
Class<?> getClasseDeCellule(int idxRang, int idxColo)
{
// To be modified.
return Object.class;
}
// Overrides method from javax.swing.table.AbstractTableModel
#Override public String getColumnName(int colo)
{ // Column titles
return "Col " + (colo + 1);
}
}
/** Bean.
*/
class GestionnObjetsDUneRangee
{
private List<MonObjet> objetsDUneRangee = new ArrayList<>();
// private final ButtonGroup groupeHorizBoutRad = new ButtonGroup();
private final PropertyChangeSupport suppoChangePropri = new PropertyChangeSupport(this);
// GestionnObjetsDUneRangee() { }
public void ajoutObjetÀRangee(MonObjet objet)
{
objetsDUneRangee.add(objet);
setGestionn_ObjetsDUneRangee2(objet);
suppoChangePropri.firePropertyChange("objecten", null, objet);
}
public List<MonObjet> getObjetsDUneRangee()
{
return objetsDUneRangee;
}
// ?
public void setObjetsDUneRangee2(List<MonObjet> objetsDUneRangee2)
{
List<MonObjet> ancienObjetsDUneRangee = objetsDUneRangee;
objetsDUneRangee = objetsDUneRangee2;
suppoChangePropri.firePropertyChange("objectenVanEenRij",
ancienObjetsDUneRangee, objetsDUneRangee);
}
// Method called from 'ajoutObjetÀRangee(...)' in this class.
void setGestionn_ObjetsDUneRangee2(MonObjet monObjet)
{
monObjet.gestionn_ObjetsDUneRangee = this;
monObjet.getSupportChangtPropri().firePropertyChange("dataVanEenRij", null, monObjet.gestionn_ObjetsDUneRangee);
}
// Puts all the JRadioButton's of a row in their right states (Only one selected)
public void miseàjourTousBP_Rangee(MonObjet myObject) // setSelectionne()
{
for (MonObjet obj : objetsDUneRangee)
{
obj.setSelectionne(myObject == obj);
}
}
GestionnObjetsDUneRangee getGestionn_ObjetsDUneRangee2(MonObjet monObjet)
{
return monObjet.gestionn_ObjetsDUneRangee;
}
PropertyChangeSupport getSuppoChangePropri()
{
return suppoChangePropri;
}
/*
public void
addPropertyChangeListener(PropertyChangeListener listener)
{
suppoChangePropri.addPropertyChangeListener(listener);
}
public void
removePropertyChangeListener(PropertyChangeListener listener)
{
suppoChangePropri.removePropertyChangeListener(listener);
}
*/
}
/**
* Bean
*/
class MonObjet
{
private boolean selectionne;
GestionnObjetsDUneRangee gestionn_ObjetsDUneRangee;
private final PropertyChangeSupport supportChangtPropri;
short[] coordBP = new short[2]; // idxRang and idxColo of the JRadioButton object
MonObjet(short idxRang, short idxColo)
{
supportChangtPropri = new PropertyChangeSupport(this);
coordBP[0] = idxRang;
coordBP[1] = idxColo;
}
PropertyChangeSupport getSupportChangtPropri()
{
return supportChangtPropri;
}
boolean isSelectionne()
{
return selectionne;
}
// Called by 'positionnerSelectionne(objet)' in 'gestionn_DObjetsDUneRang').
void setSelectionne(boolean selectionne2)
{
if (this.selectionne != selectionne2)
{ // Passes here only if a change of the state occured.
this.selectionne = selectionne2;
if (selectionne)
{ // Passes here only when the radiobutton just was activated.
// 'selectionne' of the object is already 'true'.
gestionn_ObjetsDUneRangee.miseàjourTousBP_Rangee(this);
}
supportChangtPropri.firePropertyChange("geselecteerd", !selectionne, selectionne);
}
}
short[] getCoordBP()
{
return coordBP;
}
public void setGestionn_ObjetsDUneRangee(GestionnObjetsDUneRangee gestionn_ObjetsDUneRangee2)
{
GestionnObjetsDUneRangee ancienGestionn_ObjetsDUneRangee = gestionn_ObjetsDUneRangee;
gestionn_ObjetsDUneRangee = gestionn_ObjetsDUneRangee2;
supportChangtPropri.firePropertyChange("ManagerObjectenVanEenRij",
ancienGestionn_ObjetsDUneRangee, gestionn_ObjetsDUneRangee2);
}
/*
public void addPropertyChangeListener(PropertyChangeListener listener)
{
supportChangtPropri.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener)
{
supportChangtPropri.removePropertyChangeListener(listener);
}
*/
}
}
}
The issue : The RadioButton's in the rows under the one where one was just activated, in the column of the activated one, also change. Observe by running my code, please.
In 'MonModèleTable', in 'void propertyChange(PropertyChangeEvent evt)', the 'gestionn_ObjetsDUneRangée' of 'objet2' always refers to the one of the last row. How to get there the right 'gestionn_ObjetsDUneRangée', I mean the one which corresponds to the row of the JRadioButton that was just activated ?
Is there an expert agreeing that this case is of general interest and who can help us (Guillaume Polet ?) ?
Many thanks in advance.
Note: I'm using JRE rev. 1.8.0_73 and NetBeans IDE rev. 8.0.2.
I am trying to make a Checkbox change value on click in a JTable. Here is the code I use for that in the MouseListener
public void mouseClicked(MouseEvent e) {
Point mouse = e.getPoint();
int row = table.rowAtPoint(mouse);
int col = table.columnAtPoint(mouse);
if (col == 0) tableModel.setValueAt(new Boolean(!(Boolean) tableModel.getValueAt(row, col)), row, col);
}
The problem is, that when I sort the table, this happens
Here is an SSCCE
import javax.swing.JFrame;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
#SuppressWarnings("serial")
public class SSCCE extends JFrame {
JTable table;
public SSCCE() {
setSize(300, 200);
Object[][] data = { {false, "This is false"}, {true, "This is true"}};
table = new JTable(new CustomTableModel(data));
add(table);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setVisible(true);
}
private class CustomTableModel extends AbstractTableModel {
Object[][] data;
public CustomTableModel(Object[][] data) {
this.data = data;
}
public Class<?> getColumnClass(int columnIndex) {
return data[0][columnIndex].getClass();
}
public int getColumnCount() {
return data[0].length;
}
public int getRowCount() {
return data.length;
}
public Object getValueAt(int rowIndex, int columnIndex) {
return data[rowIndex][columnIndex];
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new SSCCE();
}
});
}
}
Is there a way around this? Or a better method (not ListListener) to detect clicks on cells?
There is no need to use a MouseListener. You just need to use a proper editor for the column and the table will handle it for you.
Read the section from the Swing tutorial on How to Use Tables for more information and working examples.
Basically you need to do two things:
Add Boolean data to the TableModel
Override the getColumnClass(...) method of the TableModel to return Boolean.class for that column and the table will choose the appropriate editor.
Above is the answer for your question, but for future information the MouseEvent is relative to the table, so you want to use table methods to access the data. That is you would use table.getValueAt(...) and table.setValueAt(...). These reference the data as it is currently displayed in the view of the table. That is the view could be sorted or the column could have been moved.
I have celltable with 4 column (name size addedBy modifiedBy )
all the value is filled during the run time.
The table actully show the documents.
Documents can be pdf, txt , doc so
i want to add icon before the name of the document.
and i also want one more Image column before name column
My code so far.
*
private CellTable<FDocument> getDocumentTable() {
if (documentTable == null) {
documentTable = new CellTable<FDocument>();
documentTable.setSize("600px", "300px");
documentTable.addColumn(nameColumnD, "NAME");
documentTable.addColumn(sizeColumnD, "SIZE");
documentTable.addColumn(modified_by_ColumnD, "MODIFIED BY");
documentTable.addColumn(dateColumnD, "MODIFIED ON");
}
return documentTable;
}
TextColumn<FDocument> idColumnD = new TextColumn<FDocument>() {
#Override
public String getValue(FDocumentobject) {
// TODO Auto-generated method stub
return object.getId();
}
};
TextColumn<FDocument> nameColumnD = new TextColumn<FDocument>() {
#Override
public String getValue(FDocumentobject) {
return object.getName();
}
};
TextColumn<FDocument> sizeColumnD = new TextColumn<FDocument>() {
#Override
public String getValue(FDocumentobject) {
return object.getSize();
}
};
TextColumn<FDocument> modified_by_ColumnD = new TextColumn<FDocument>() {
#Override
public String getValue(FilenetDocument object) {
return object.getModifiedBy();
}
};
TextColumn<FDocument> dateColumnD = new TextColumn<FDocument>(){
#Override
public String getValue(FDocumentobject){
return object.getModifiedOn();
}
};
private void addValuesToTable(){
List<FDocument> FDC = null;
/*
* Adding data to folder Table
*/
ArrayList<FDocument> documentsArrayList = new ArrayList<FDocument>();
Iterator<String> iteratorDocument = documents.getDocuments().getDocumentCollection().keySet().iterator();
while(iteratorDocument.hasNext()){
String key = iteratorDocument.next().toString();
FDocument value = documents.getDocuments().getDocumentCollection().get(key);
documentsArrayList.add(new FDocument(value.getName(), value.getSize(),value.getModifiedBy(), value.getModifiedOn(),value.getId()));
}
FDC = documentsArrayList;
// Create a data provider.
ListDataProvider<FDocument> dataProvider1 = new ListDataProvider<FDocument>();
// Connect the table to the data provider.
dataProvider1.addDataDisplay(documentTable);
// Add the data to the data provider, which automatically pushes it to the widget.
List<FDocument> listDocument = dataProvider1.getList();
for (FDocument fDocument: FDC) {
listDocument.add(fDocument1);
}
This the way i have made my table and the value.
Plz any one tell me the procedure
to add icon before the name of the document.
and i also want one more Image column before name column
Use DefaultTableCellRenderer to set custom renderer against the column with the desired icon. As shown below, the renderer creates a new label to mask all the cells within the selected column.
private class CellRenderer extends DefaultTableCellRenderer
{
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
{
JLabel label = new JLabel((String)value);
label.setOpaque(true);
Icon icon = new ImageIcon("icon.png");
label.setIcon(icon);
return label;
}
}
And then apply it to your table as shown below:
table.getColumnModel().getColumn(0).setCellRenderer(new CellRenderer());
Edited:
If you're using GWT then see here for an example code for Grid Cell format.
For creating image column, You need to define a custom renderer that sets the icon on the label
import java.awt.Component;
import javax.swing.ImageIcon;
import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer;
public class MyRenderer extends DefaultTableCellRenderer {
/*
* #see TableCellRenderer#getTableCellRendererComponent(JTable, Object, boolean, boolean, int, int)
*/
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus,
int row, int column) {
ImageIcon icon = new ImageIcon(getClass().getResource("images/moon.gif"));
setText((String)value);
setIcon(icon);
return this;
}
}
And then, use the renderer in a table as follows,
import java.awt.GridLayout;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
public class Sample {
public static void main(String[] args) {
JFrame f = new JFrame("Sample");
f.getContentPane().setLayout(new GridLayout(1,0,0,0));
TableModel model = new AbstractTableModel() {
public Object getValueAt(int rowIndex, int columnIndex) {
return rowIndex + "," + columnIndex;
}
public int getColumnCount() {
return 3;
}
public int getRowCount() {
return 10;
}
};
JTable table = new JTable(model);
table.getColumnModel().getColumn(1).setCellRenderer(new MyRenderer());
JScrollPane pane = new JScrollPane(table);
f.getContentPane().add(pane);
f.pack();
f.setVisible(true);
}
}
Both of these will probably need a custom TableCellRenderer...
Refer to the API docs here... http://docs.oracle.com/javase/1.4.2/docs/api/javax/swing/table/DefaultTableCellRenderer.html
You basically want to overwrite the getTableCellRendererComponent() method, and add in any additional code for rendering the image.
For example, to add an icon before the document, you would do the following...
public void MyTableCellRenderer extends DefaultTableCellRenderer {
public MyTableCellRenderer(){
super();
}
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column){
Component renderer = super.getTableCellRendererComponent(table,value,isSelected,hasFocus,row,column);
if (row == 0 && renderer instanceof JLabel){ // where row == 0 is the row you want to add the icon to
((JLabel)renderer).setIcon(new ImageIcon("image.png"));
}
return renderer;
}
}
You would need to set this as the TableCellRenderer for the column or table.
You would also do a similar thing for adding an image column to the table.