I want to render a JTable component's cell to support line wrapping, so I am using a JTextarea inside the cell, because text area allows line wrapping. This is my code:
//Render column with header Person
table.getColumn("Person").setCellRenderer(new RenderRedGreen());
final class RenderRedGreen extends DefaultTableCellRenderer {
JTextArea textarea;
#Override public Component getTableCellRendererComponent(
JTable aTable, Object aNumberValue, boolean aIsSelected,
boolean aHasFocus, int aRow, int aColumn )
{
String value = (String)aNumberValue;
textarea = new JTextArea();
aTable.add(textarea);
textarea.setLineWrap(true);
textarea.setWrapStyleWord(true);
textarea.setText(value);
aTable.setRowHeight(90);
if (aNumberValue == null) return this;
Component renderer = super.getTableCellRendererComponent(
aTable, aNumberValue, aIsSelected, aHasFocus, aRow, aColumn
);
if (value.equals("Me"))
renderer.setForeground(Color.red);
else
renderer.setForeground(Color.black);
return this;
}
}
The problem is that the text is not wrapped, so I can not see long texts completely. What am I doing wrong?
These lines of code would help you to wrap the data and show multiple lines properly :
textarea.setWrapStyleWord(true);
textarea.setLineWrap(true);
DefaultCaret caret = (DefaultCaret) textarea.getCaret();
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
Related
I am trying to include a JTable in JOptionPane.showMessageDialog and adding some more text below the table. To do so, I have tried next code:
import javax.swing.JOptionPane;
import javax.swing.*;
public class TextInTable {
public static void main(String[] args) {
String[][] rowValues = {
{"1","2","3"}
};
String[] columnNames = {
"A","B", "C"
};
JTable table = new JTable(rowValues, columnNames);
JOptionPane.showMessageDialog(null, new JScrollPane(table) +"\n"+ "I need to add some text here \n" + "and here”);
}
}
However, the JTable does not show correctly if I put this code (+"\n"+ "I need to add some text here \n" + "and here”) after the table.
This is what I am trying to do:
Any idea about how to solve it? Thanks in advance.
You need to create your own JPanel the way you want it with the components you would like and then pass that panel to the JOptionPane. Below I provide a runnable demo of this. Read all the comments in code as they explain what is going on. You can delete them later if you like. Here is what it produces on Screen:
Here is the runnable code:
package joptionpanewithjtabledemo;
public class JOptionPaneWithJTableDemo {
public static void main(String[] args) {
// Application started this way to avoid the need for statics.
new JOptionPaneWithJTableDemo().startApp(args);
}
private void startApp(String[] args) {
/* Create a JDialog box to use as a parent for the JOptionPane
just in case the JOptionPane has no parent and needs to be
displyed on top of a window that has its own ON TOP property
set to true and the JOptionPane's parent property is set to
null. If this is the case then the message box will be hidden
behind it! If your JOptionPane will be displayed on an actual
parent window then use that window's variable name as parent.
You can then delete anything in code related to iDialog.
*/
javax.swing.JDialog iDialog = new javax.swing.JDialog();
iDialog.setAlwaysOnTop(true);
iDialog.setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
iDialog.setLocationRelativeTo(null);
// Create a JPanel to display within the JOptionPane MessageBox //
javax.swing.JPanel panel = new javax.swing.JPanel();
// Size the Panel to what you want to hold the JTable and JLabel
panel.setPreferredSize(new java.awt.Dimension(400, 250));
panel.setLayout(new java.awt.BorderLayout()); // Set BorderLayout as the layout manager
// JTable Column Names
Object[] columnNames = {"A", "B", "C"};
// Some fictitious data for the JTable...
Object[][] data = {
{1000, 2000, 3000}, {1001, 2001, 3001}, {1002, 2002, 3002},
{1003, 2003, 3003}, {1004, 2004, 3004}, {1005, 2005, 3005},
{1006, 2006, 3006}, {1007, 2007, 3007}, {1008, 2008, 3008},
{1009, 2009, 3009}, {1010, 2010, 3010}, {1011, 2011, 3011},
};
// Declare a JTable and initialize with the table data and column names.
javax.swing.JTable table = new javax.swing.JTable(data, columnNames);
/* Align JTable Header Text to Center.
Alignment options are: Align_LEFT (2), Align_CENTER (0), Align_RIGHT (4),
Align_LEADING (10), or Align_TRAILING (11) */
int alignType = 0;
for (int i = 0; i < table.getColumnCount(); i++) {
table.getTableHeader().getColumnModel().getColumn(i)
.setHeaderRenderer(new HeaderRenderer(table, alignType));
}
// Declare a JScrollPane and place the JTable into it.
javax.swing.JScrollPane tableScrollPane = new javax.swing.JScrollPane(table);
// Ensure the ScrollPane size.
tableScrollPane.setPreferredSize(new java.awt.Dimension(400, 200));
panel.add(tableScrollPane, java.awt.BorderLayout.NORTH); // Add the scrollpane to top of panel.
/* Use basic HTML to create you message text. Gives you more
flexability towards how your message will look. */
String msg = "<html><font size='4'>I need to add some text "
+ "<font color=blue>here</font><center>and right "
+ "<font color=red>here</font>!</center></html>";
javax.swing.JLabel msgLabel = new javax.swing.JLabel(msg);
// Set the initial text alignment in the JLabel.
msgLabel.setHorizontalAlignment(javax.swing.JLabel.CENTER);
//msgLabel.setBorder(BorderFactory.createLineBorder(Color.blue, 1));
// Ensure label width and desired height.
msgLabel.setPreferredSize(new java.awt.Dimension(panel.getWidth(), 50));
panel.add(msgLabel, java.awt.BorderLayout.SOUTH); // add label to bottom of panel.
// Display The Message Box...
javax.swing.JOptionPane.showMessageDialog(iDialog, panel, "My Table Message Box",
javax.swing.JOptionPane.INFORMATION_MESSAGE);
iDialog.dispose();
}
// Inner class - A Header cell renderer class for header text alignment options.
class HeaderRenderer implements javax.swing.table.TableCellRenderer {
javax.swing.table.DefaultTableCellRenderer renderer;
int horAlignment;
HeaderRenderer(javax.swing.JTable table, int horizontalAlignment) {
horAlignment = horizontalAlignment;
renderer = (javax.swing.table.DefaultTableCellRenderer) table.getTableHeader().getDefaultRenderer();
}
#Override
public java.awt.Component getTableCellRendererComponent(javax.swing.JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int col) {
java.awt.Component c = renderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col);
javax.swing.JLabel label = (javax.swing.JLabel) c;
label.setHorizontalAlignment(horAlignment);
return label;
}
}
}
for this, I tried this method
table.setFont(new Font("Lucida Console", Font.PLAIN, 18));
but by using this I cannot find the appropriate Font at the time of Writing in the cell of the JTable.
I attach an image so you can find my question clearly .in this image u can see that at the time of writing font is small but after going to the next cell it becomes to change but I want that big font you can see that in image at the time of writing in the cell.
The problem is that table.setFont(new Font("Lucida Console", Font.PLAIN, 18)); will set the font of the cells when not being edited, it will not even set the font of the header. When editing, you have override the default setup by defining your own DefaultCellEditor. There are two ways to do so, the first (easier and cleaner) way is to create a JTextField and customize it the way you like and then pass it to the DefaultCellEditor constructor. The second (longer and not as clean) way is to override the getTableCellEditorComponent in the DefaultCellEditor and achieve the same result. I have included both solutions in a MCVE:
import java.awt.Color;
import java.awt.Font;
import javax.swing.DefaultCellEditor;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
public class Example extends JFrame {
private final JTable table;
private final String[] header = new String[]{"Column 0", "Column 1", "Column 2", "Column 3"};
private String[][] data = new String[][]{
{"(0,0)", "(1,0)", "(2,0)", "(3,0)"},
{"(0,1)", "(1,1)", "(2,1)", "(3,1)"},
{"(0,2)", "(1,2)", "(2,2)", "(3,2)"},
{"(0,3)", "(1,3)", "(2,3)", "(3,3)"}};
private final Font tableFont = new Font("Lucida Console", Font.PLAIN, 18);
public Example() {
table = new JTable(data, header);
table.getTableHeader().setFont(tableFont);//font of the header
table.setFont(tableFont);//set the font of the whole table
//Since each cell is editable, you could think about it as a JTextField. You can create a
//new JTextField and customize it. Then, you pass it as the new cell editor to the columns
//of the JTable.
JTextField textField = new JTextField();
textField.setFont(tableFont);//this is what you need.
//Extra changes, no boarder and selection colour is yellow... just to get the point across.
textField.setBorder(null);
textField.setSelectionColor(Color.YELLOW);
//Create DefaultCellEditor and pass the textfield to the constructor.
DefaultCellEditor customCellEditor = new DefaultCellEditor(textField);
//Loop through all the columns and set the cell editor as the customized one.
for (int i = 0; i < table.getColumnCount(); i++) {
table.getColumnModel().getColumn(i).setCellEditor(customCellEditor);
}
/*
OR, don't create a JTextField and use the following instead:
DefaultCellEditor customCellEditor2 = new DefaultCellEditor(new JTextField()) {
#Override
public java.awt.Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected, int row, int column) {
JTextField result = (JTextField) super.getTableCellEditorComponent(table, value,
isSelected, row, column);
result.setFont(tableFont);//this is what you need.
result.setBorder(null);
result.setSelectionColor(Color.YELLOW);
return result;
}
};
//Loop through all the columns and set the cell editor as the customized one.
for (int i = 0; i < table.getColumnCount(); i++) {
table.getColumnModel().getColumn(i).setCellEditor(customCellEditor2);
}
*/
//probably, you should make the height of the cells larger.
for (int i = 0; i < table.getRowCount(); i++) {
table.setRowHeight(i, 25);
}
add(new JScrollPane(table));
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
pack();
setVisible(true);
}
public static void main(String[] args) {
new Example();
}
Maybe you could check how many characters have been typed in and calculate approximately how big the font should be.
Calculation shouldn't be hard, is it? And then just change the size in this cell.
I already read/tried these posts but that didn't help:
Display multiple lines within a Jlist cell
How to get multiline for a Jlist text?
Problem displaying components of JList
What I need is a ListCellRenderer which returns a panel with an icon on the left and a text of dynamic length on the right (like in any forum: on the left a user avatar, on the right the post text). The texts are NOT known to me, so I can't set a fixed cell height. Further, the text length differs from list cell to list cell. So every list cell needs its own height depending on the length of the text. Actually a really common layout ... but not for Swing. The cell height just doesn't expand according to the text length.
I already read almost any post out there about dynamic cell heights and multiline texts in JList, but couldn't find a solution. So I decided to give a small SSCCE. Please give me a hint on how to achieve what I described or please fix my code if you think it's easy.
Thanks
Here is ths SSCCE:
public class MultiLineList extends JFrame
{
private static final long serialVersionUID = 1L;
public static void main(final String[] args)
{
new MultiLineList();
}
private MultiLineList()
{
setTitle("MultiLineList");
setSize(800, 450);
setResizable(true);
setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
this.getContentPane().setLayout(new BorderLayout());
final DefaultListModel model = new DefaultListModel();
model.addElement("This is a short text");
model.addElement("This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. ");
model.addElement("This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. ");
final JList list = new JList(model);
list.setCellRenderer(new MyCellRenderer());
this.add(list);
this.getContentPane().invalidate();
this.getContentPane().validate();
}
public class MyCellRenderer extends DefaultListCellRenderer
{
#Override
public Component getListCellRendererComponent(final JList list, final Object value, final int index, final boolean isSelected, final boolean hasFocus)
{
final String text = (String) value;
//create panel
final JPanel p = new JPanel();
p.setLayout(new BorderLayout());
//icon
final JPanel IconPanel = new JPanel(new BorderLayout());
final JLabel l = new JLabel("icon"); //<-- this will be an icon instead of a text
IconPanel.add(l, BorderLayout.NORTH);
p.add(IconPanel, BorderLayout.WEST);
//text
final JTextArea ta = new JTextArea();
ta.setText(text);
ta.setLineWrap(true);
ta.setWrapStyleWord(true);
p.add(ta, BorderLayout.CENTER);
return p;
}
}
}
Edit 1: oops - seeing #Andrew's screenshot, realized that this isn't working as expected, the text is actually longer than shown with this (overlooked an internal comment "PENDING: not working for JList in JScrollPane" ;-) Will dig a bit and delete this answer if I can't make it work soon.
Edit 2: got it - the renderer implementation as shown below is okay, the culprit is the JList with its occasional less than optimal size caching. There are two parts of that
BasicListUI doesn't take into account that resizing the list might require clearing the internal size (actually row height) cache, application code must force it to do so, f.i. in a ComponentListener
list's Scrollable implementation of tracksViewportWidth contains logic which stands in the way (leads to looping stretch-out of the area until it's a single line), subclass to return true.
Code that uses the renderer below:
final JList list = new JList(model) {
/**
* #inherited <p>
*/
#Override
public boolean getScrollableTracksViewportWidth() {
return true;
}
};
list.setCellRenderer(new MyCellRenderer());
ComponentListener l = new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
// next line possible if list is of type JXList
// list.invalidateCellSizeCache();
// for core: force cache invalidation by temporarily setting fixed height
list.setFixedCellHeight(10);
list.setFixedCellHeight(-1);
}
};
list.addComponentListener(l);
add(new JScrollPane(list));
First answer (a renderer implementation which uses JTextArea as rendering component)
TextArea is a bit tricky in sizing: it needs to get initialized to something reasonable:
public class MyCellRenderer implements ListCellRenderer {
private JPanel p;
private JPanel iconPanel;
private JLabel l;
private JTextArea ta;
public MyCellRenderer() {
p = new JPanel();
p.setLayout(new BorderLayout());
// icon
iconPanel = new JPanel(new BorderLayout());
l = new JLabel("icon"); // <-- this will be an icon instead of a
// text
iconPanel.add(l, BorderLayout.NORTH);
p.add(iconPanel, BorderLayout.WEST);
// text
ta = new JTextArea();
ta.setLineWrap(true);
ta.setWrapStyleWord(true);
p.add(ta, BorderLayout.CENTER);
}
#Override
public Component getListCellRendererComponent(final JList list,
final Object value, final int index, final boolean isSelected,
final boolean hasFocus) {
ta.setText((String) value);
int width = list.getWidth();
// this is just to lure the ta's internal sizing mechanism into action
if (width > 0)
ta.setSize(width, Short.MAX_VALUE);
return p;
}
}
import java.awt.*;
import javax.swing.*;
public class MultiLineList
{
private static final long serialVersionUID = 1L;
public static void main(final String[] args)
{
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new MultiLineList();
}
});
}
private MultiLineList()
{
JFrame f = new JFrame("MultiLineList");
f.setResizable(true);
f.setVisible(true);
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.getContentPane().setLayout(new BorderLayout());
final DefaultListModel model = new DefaultListModel();
model.addElement("This is a short text");
model.addElement("This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. ");
model.addElement("This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. ");
final JList list = new JList(model);
list.setCellRenderer(new MyCellRenderer());
f.add(list);
f.pack();
}
public class MyCellRenderer extends DefaultListCellRenderer
{
final JPanel p = new JPanel(new BorderLayout());
final JPanel IconPanel = new JPanel(new BorderLayout());
final JLabel l = new JLabel("icon"); //<-- this will be an icon instead of a text
final JLabel lt = new JLabel();
String pre = "<html><body style='width: 200px;'>";
MyCellRenderer() {
//icon
IconPanel.add(l, BorderLayout.NORTH);
p.add(IconPanel, BorderLayout.WEST);
p.add(lt, BorderLayout.CENTER);
//text
}
#Override
public Component getListCellRendererComponent(final JList list, final Object value, final int index, final boolean isSelected, final boolean hasFocus)
{
final String text = (String) value;
lt.setText(pre + text);
return p;
}
}
}
I have a JTable for which the renderer returns a JPanel composed of multiple JLabel instances. One of those JLabels can contain HTML used among other things to split the output over multiple lines using <br/> tags.
To show the multiple lines in the table, the renderer calls in the getTableCellRendererComponent method
table.setRowHeight(row, componentToReturn.getPreferredSize().height);
to dynamically update the row height, based on the contents. This only works correctly if componentToReturn indicates a correct preferred size.
It looks however that the getPreferredSize returns bogus values. The preferred height of the returned component is smaller than the sum of the heights of the labels inside the component.
Here is a little program illustrating this behaviour (without using a JTable)
import java.awt.*;
import javax.swing.*;
public class SwingLabelTest {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
LabelPanel renderer = new LabelPanel();
Component component = renderer.getComponent(false);
//asking for a bigger component will not
//update the preferred size of the returned component
component = renderer.getComponent(true);
}
});
}
private static class LabelPanel {
private final JPanel compositePanel;
private final JLabel titleLabel = new JLabel();
private final JLabel propertyLabel = new JLabel();
public LabelPanel() {
JPanel labelPanel = new JPanel();
labelPanel.setLayout(new BoxLayout(labelPanel, BoxLayout.PAGE_AXIS));
labelPanel.add(titleLabel);
labelPanel.add(propertyLabel);
compositePanel = new JPanel(new BorderLayout());
//normally it contains more components,
//but that is not needed to illustrate the problem
compositePanel.add(labelPanel, BorderLayout.CENTER);
}
public Component getComponent( boolean aMultiLineProperty ) {
titleLabel.setText("Title");
if ( aMultiLineProperty ){
propertyLabel.setText("<html>First line<br/>Property: value</html>");
} else {
propertyLabel.setText("Property: value");
}
int titleLabelHeight = titleLabel.getPreferredSize().height;
int propertyLabelHeight = propertyLabel.getPreferredSize().height;
int compositePanelHeight = compositePanel.getPreferredSize().height;
if ( compositePanelHeight < titleLabelHeight + propertyLabelHeight){
throw new RuntimeException("Preferred size of the component returned "
+ "by the renderer is incorrect");
}
return compositePanel;
}
}
}
As I am aware that the previous example is a bit far-fetched, here an example which includes a JTable
import java.awt.*;
import javax.swing.*;
import javax.swing.table.*;
public class SwingTableTest {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
DefaultTableModel tableModel = new DefaultTableModel(0, 1);
JTable table = new JTable(tableModel);
table.setDefaultRenderer(Object.class, new DataResultRenderer());
tableModel.addRow(new Object[]{new Object()});
tableModel.addRow(new Object[]{new Object()});
tableModel.addRow(new Object[]{new Object()});
JFrame testFrame = new JFrame("TestFrame");
testFrame.getContentPane().add(new JScrollPane(table));
testFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
testFrame.setSize(new Dimension(300, testFrame.getPreferredSize().height));
testFrame.setVisible(true);
}
});
}
private static class DataResultRenderer implements TableCellRenderer {
private final JPanel compositePanel;
private final JLabel titleLabel = new JLabel();
private final JLabel propertyLabel = new JLabel();
public DataResultRenderer() {
JPanel labelPanel = new JPanel();
labelPanel.setOpaque(false);
labelPanel.setLayout(new BoxLayout(labelPanel, BoxLayout.PAGE_AXIS));
labelPanel.add(titleLabel);
labelPanel.add(propertyLabel);
compositePanel = new JPanel(new BorderLayout());
//normally it contains more components,
//but that is not needed to illustrate the problem
compositePanel.add(labelPanel, BorderLayout.CENTER);
}
#Override
public Component getTableCellRendererComponent(
JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column) {
titleLabel.setText("Title");
if ( row == 2 ){
propertyLabel.setText("<html>Single property: value</html>");
} else {
String text = "<html>";
text += "First property<br/>";
text += "Second property<br/>";
text += "Third property:value";
text += "</html>";
propertyLabel.setText(text);
}
int titleLabelHeight = titleLabel.getPreferredSize().height;
int propertyLabelHeight = propertyLabel.getPreferredSize().height;
int compositePanelHeight = compositePanel.getPreferredSize().height;
if ( compositePanelHeight < titleLabelHeight + propertyLabelHeight){
throw new RuntimeException("Preferred size of the component returned "
+ "by the renderer is incorrect");
}
table.setRowHeight(row, compositePanel.getPreferredSize().height);
return compositePanel;
}
}
}
I am looking for a way to update the row height of the table to ensure that the multi-line content is completely visible, without knowing up front how many lines each row will contain.
So either I need a solution to retrieve the correct preferred size, or my approach is completely wrong and then I need a better one.
Note that the above examples are simplified. In the real code, the "renderer" (the code responsible for creating the component) is decorated a few times. This means that the outer renderer is the only with access to the JTable, and it has no knowledge about what kind of Component the inner code returns.
Because setRowHeight() "Sets the height, in pixels, of all cells to rowHeight, revalidates, and repaints," the approach is unsound. Absent throwing an exception, profiling shows 100% CPU usage as an endless cascade of repaints tries to change the row height repeatedly. Moreover, row selection becomes unreliable.
Some alternatives include these:
Use TablePopupEditor to display multi-line content on request from a TableCellEditor.
Update an adjacent multi-line panel from a TableModelListener, as shown here.
I have set the default font in my JTable as show below
myTable.setFont(new java.awt.Font("Verdana", 1, 10));
I wanted to show a bigger font in my JTable,while some data is being typed into the cells.So I used MyTableCellEditor custom class.
public class MyTableCellEditor extends AbstractCellEditor implements TableCellEditor {
JComponent component = new JTextField();
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected,
int rowIndex, int vColIndex) {
((JTextField) component).setText((String) value);
((JTextField) component).setFont(new Font("Verdana", 1, 12));
return component;
}
public Object getCellEditorValue() {
return ((JTextField) component).getText();
}
}
Below is the code where I attached the CustomCellEditor to my table.
myTable.getColumnModel().getColumn(1).setCellEditor(new MyTableCellEditor());
But this code do not seem to work.The cells font becomes small while editing and once I finish editing and hit enter,the default JTable font which I set ( Verdana 10 ) takes effect.Why is this happening ? I have already set CustomCellEditor font as ( Verdana 12 ) to my cells.
Don't create a new class for this. Just change the property of the DefaultCellEditor:
JTextField textField = new JTextField();
textField.setFont(new Font("Verdana", 1, 12));
textField.setBorder(new LineBorder(Color.BLACK));
DefaultCellEditor dce = new DefaultCellEditor( textField );
myTable.getColumnModel().getColumn(1).setCellEditor(dce);