I am a software developer apprentice and have to write a graphical project specific configuration editor for my company. I load the data from the configuration excel file of the project with Apache POI and wrap the data into ConfigValue Objects. For different ConfigValue objects there has to be different cell editors and renderers...
The GUI of my programm uses a custom JTable and DefaultTableModel. Every value in the table / model is a ConfigValue which should rendered differently for defined different ConfigTypes. (As far I got it all working - import, wrapping, load into table)
But I have some problems with the TableCellRenderer or TableCellEditor of one of the custom types which should rendered as a ComboBox which contains all possible backend entity values. The ComboBox gets rendered and the correct beginning values are displayed... But when I change one cell to another ConfigValue... The renderer does not display this value... (it always changes to the same value (first of the editor's value) for a cell)
Can anyone help me out what I am doing wrong with my Editor/Renderer?
public class ConfComboBoxCellEditor extends DefaultCellEditor {
public ConfComboBoxCellEditor(List<ConfigValue> possibleValues) {
super(new JComboBox(possibleValues.toArray()));
}
#Override
public Object getCellEditorValue() {
Object cellEditorValue = super.getCellEditorValue();
System.out.println("DEBUG - CELL EDITOR - get editor value --> " + ((ConfigValue) cellEditorValue).toString());
return cellEditorValue;
}
}
public class ConfComboBoxCellRenderer extends JComboBox<ConfigValue> implements TableCellRenderer {
public ConfComboBoxCellRenderer() {
System.out.println("NEW CELL RENDERER");
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
ConfComboBoxCellRenderer renderer = (ConfComboBoxCellRenderer) table.getCellRenderer(row, column);
renderer.removeAllItems();
renderer.addItem((ConfigValue) value);
renderer.setSelectedItem(value);
System.out.println("DEBUG - CELL RENDERER " + row + ", " + column + " - get cell render comp --> " + ((ConfigValue) value));
return this;
}
}
Can anyone help me out what I am doing wrong with my Editor/Renderer?
JTable support JComboBox as TableCellEditor, there isn't any issue to set different dataset for each of JComboBoxes used as TableCellEditor
TableCellRenderer only shows, painting the value stored in DefaultTableModel, then every code lines inside renderer.xxxXxx are missinterpreting of Renderers Concept in Swing, contraproductive and could be heavy tasks, Renderer isn't place to set/getValue, a new event is fired from all mouse/key events from all cell are visible in JViewport, plus internal events from JTable/TableModel APIs,
your Renderer isn't about how to painting JComboBox as rendering component
nothing cleaver, nor specifics without an SSCCE/MCVE, short runnable, compilable with hardcoded value for JTable/DefaultTableModel in local variable
Related
I'm trying to create a simple To-Do list Java application connected to MS Access and I used JTable and DefaultTableModel to display the list. I want to mark completed tasks by changing its color when I click on a button. I have a boolean field named 'completed' that serves as indicator.
String header[] = {"priority", "task"};
String data[][];
DefaultTableModel model = new DefaultTableModel(data, header);
JTable table = new JTable(model);
// to be replaced with code that affects only specific cells not the whole table
table.setFont(customFont);
I already have a Font object that I called customFont, which is ready to be applied. My question is, how do I apply it only to specific cells where completed==true.
Sample codes would be much appreciated.
easiest of ways is look at prepareRenderer(), best of all is #camickr Table Row Rendering
JTable is View, based on TableModel, in most cases you have to convert the view against model converXxxToXxx from inside of prepareRenderer or getTableCellRendererComponent, because JTable could be sorted of filtered
methods
code
public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
Component c = super.prepareRenderer(renderer, row, column);
returns access to the specific cell in JTable - (TableCellRenderer renderer, int row, int column)
Use DefaultTableCellRenderer, then you can use setForeground() and setBackground().
refer to the page..
http://www.jyloo.com/news/?pubId=1282737395000
or see this example...
/*This is the raw code I have written.*/
JTable Tbl=new JTable(2,2){
DefaultTableCellRenderer colortext=new DefaultTableCellRenderer();
{
colortext.setForeground(Color.RED);
}
#Override
public TableCellRenderer getCellRenderer(int arg0, int arg1) {
return colortext;
}
};
I believe you can specify this behavior in
TableCellRenderer.getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus,
int row, int column)
method of the table
We are using a JTable which displays data along with Status (New, Processed, Closed). Each status row has a different color which is achieved by overloading prepareRenderer() of JTable.
Now we need to sort that table and we are using table.setAutoCreateRowSorter(true); to achieve that. The rows get sorted properly, but the color of rows remains the same. We need to reapply the color to all the rows after this operation based on the status column.
I was wondering what could be the best way to achieve that. There are several ways I can think of:
Repaint/Revalidate the table. But simply doing this would not work I think.
Capture mouseClicked event and identify whether column header was clicked then call prepareRenderer() manually and then call repaint/revalidate
Then I read one of the questions here wherein one of the answers was mentioned not to call repaint/revalidate directly, rather change the underlying data model and it will automatically call the above methods.
I don't know how to go about it. Can anyone please provide an insight into what is the correct way to achieve this?
For change cell color in JTable with setAutoCreateRowSorter(true) I used method table.getRowSorter().convertRowIndexToModel(row) in my TableCellRenderer
import javax.swing.*;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableModel;
import java.awt.*;
public class OwnTableCellRenderer extends DefaultTableCellRenderer {
public OwnTableCellRenderer() {
super();
setOpaque(true);
}
public Component getTableCellRendererComponent(JTable table,
Object value,
boolean isSelected,
boolean hasFocus,
int row,
int column) {
setBackground(Color.white);
setForeground(Color.black);
TableModel model = table.getModel();
int modelRow = table.getRowSorter().convertRowIndexToModel(row);
int columnStatusPosition = 5;
String statusColumnValue = (String) model.getValueAt(modelRow, columnStatusPosition);
if (statusColumnValue.equals("ACTIVE")) {
if (isSelected) {
setBackground(Color.green);
} else {
setBackground(Color.yellow);
}
}
setText(value != null ? value.toString() : "");
return this;
}
}
And then
table.setDefaultRenderer(Object.class, new OwnTableCellRenderer());
This summer is not a real summer, i thought.
A cup of coffee make me feel it as a summer, tough (lol).
I have a little bit naughty JTable. OMG.
Below is my JTable that use my own customized TableModel. You could see it on its method of getColumnClass(), there... it was made for returning as JLabel only.
ANd then, I also customize the DefaultRenderer.
jtbl_inGameEasy = new javax.swing.JTable();
jtbl_inGameEasy.setFont(new java.awt.Font("squeaky chalk sound", 0, 14)); // NOI18N
jtbl_inGameEasy.setForeground(new java.awt.Color(255, 255, 255));
jtbl_inGameEasy.setModel(new javax.swing.table.DefaultTableModel(
new Object [][] {
},
new String [] {
"null", "null", "null", "null", "null"
}
) {
boolean[] canEdit = new boolean [] {
false, false, false, false, false
};
public boolean isCellEditable(int rowIndex, int columnIndex) {
return canEdit [columnIndex];
}
public Class getColumnClass(int columnIndex) {
return JLabel.class;
}
});
jtbl_inGameEasy.setDefaultRenderer(JLabel.class, new JLabelTableRenderer());
jtbl_inGameEasy.setFocusable(false);
jtbl_inGameEasy.setOpaque(false);
jtbl_inGameEasy.setRowHeight(55);
jtbl_inGameEasy.setShowHorizontalLines(false);
jtbl_inGameEasy.setShowVerticalLines(false);
jtbl_inGameEasy.setTableHeader(null);
jtbl_inGameEasy.addMouseListener(new java.awt.event.MouseAdapter() {
public void mousePressed(java.awt.event.MouseEvent evt) {
jtbl_inGameEasyMousePressed(evt);
}
});
And Where is the JTableRenderer? Here...
this is my Custom Renderer below...
public class JLabelTableRenderer extends DefaultTableCellRenderer {
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column) {
if (value instanceof JLabel) {
//This time return only the JLabel without icon
return (JLabel) value;
} else {
return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
}
}
}
And then, I need to pu JLabel inside each of the Cell.
I start to add some JLabel(s) inside the Object of Array over the Cells of the JTable using this code; (which is no problem).
DefaultTableModel o_dtb = jtbl_inGameEasy.getModel();
o_dtb.addRow(myArrayCustomizedObjectofJLabel);
Everything works fine, I guess. But because of My purpose is that ---> To put an Icon or to make it Invisible once the user Click on JTable Cells, thus I I tried to do my MouseEvent once pressed and calling these lines of code;
private void jtbl_inGameEasyMousePressed(java.awt.event.MouseEvent evt) {
// Checking inGameEasy Table Ans
javax.swing.JTable source = (javax.swing.JTable) evt.getSource();
int row = source.rowAtPoint(evt.getPoint());
int column = source.columnAtPoint(evt.getPoint());
DefaultTableModel o_dtb = (DefaultTableModel) jtbl_inGameEasy.getModel();
String s_questAns = "" + Game.getCurrentQuestion().getResult();
String s_userAns = "" + o_dtb.getValueAt(row, column);
// These two lines below are not Working, why yaa??
((JLabel) o_dtb.getValueAt(row, column)).setVisible(false);
((JLabel) o_dtb.getValueAt(row, column)).setIcon(myIcon);
if (s_questAns.equals(s_userAns)) {
Game.correct();
System.err.println("nice ans!");
} else {
jll_txtMiss.setText("Miss : " + Game.wrong());
System.err.println("nope!");
}
nextQuestion();
}
And seems to me that the Marked (below the commented) code above are not working.
Yeah, the JLabel casted couldn't be changed its Icon (image), and nor its visibility as well. Is it the Model cause all of this?
NB: My Cells data added later after the Model created differently.
It seems to me that what you want to do is to just leave all table cells empty, except the ones clicked by the user, where some mark icon should appear, right?
If so, your table model should not contain JLabel instances. A JLabel is a visual component, used to render some data graphically. The data itself is not a JLabel. In your case, I think it should thus be a Boolean (true when clicked by the user, false otherwise).
Now you could use a custom renderer (although the default one for booleans could also be OK) to display your booleans. This renderer would be a subclass of DefaultTableCellRenderer, which is itself a subclass of JLabel. The renderer (the same instance for all the cells configured to use this renderer) would just display a marked icon if the Boolean value to render is true, and a not marked icon (or no icon at all) when the Boolean value to render is false.
Then, your click handler would just have one mission: make the clicked cell contain true rather than false. To do that, it would just have to change the appropriate value in the table model.
To recap: a table model is used to hold data. Think of it as the data you would find in a database. Would you hold a JLabel in the database? No, you would hold a boolean, a string or an integer. The table can render this data as you want, and this is the mission of the renderer.
Side note: stop with the hungarian notation: it doesn't make sense in Java. It makes code hard to read. Everything except primitive types is an object, and you can't assign a meaningful prefix to every possible type. Rather, use readable and meaningful english names : tableModel rather than o_dtb, correctAnswer rather than s_questAns, userAnswer rather than s_userAns.
I am creating my first JTable that requires me to create a custom AbstractTableModel, TableCellEditor, and DefaultTableCellRenderer. Given that I have not needed to create these before, I've made some significant progress in getting my table to behave as desired.
However, I am getting overwhelmed with all the different methods I am overriding, and am spinning my wheels trying to figure out how to modify the ImageIcon of a particular cell. The cell must contain a JLabel, as it needs both an ImageIcon as well as a text string. I can already set the initial ImageIcon (although I am probably doing it incorrectly), but I can't set an updated ImageIcon. Nothing fails, but no change is made.
In a general sense, what is the best way to get and set an icon to a JLabel cell of a JTable, assuming all of these models, editors, and renderers have already been instantiated?
My model has already been defined to return JLabel.class for these cells, if you're wondering, and I also do a fireTableCellUpdated(row, col) once the change has supposedly been made. If I do a System.out.println(getIcon()) before and after the update, I can even see the source has changed.
Here is some of the code (updated with URL/ImageIcon fix in place):
class MonitorTable extends JTable {
MonitorTableModel model = new MonitorTableModel(rows, columnNames);
setModel(model);
...
public void setIconAt(ImageIcon icon, int row, int col) {
model.setIconAt(icon, row, col);
} // End setIconAt(ImageIcon, int, int)
...
class MonitorTableModel extends AbstractTableModel {
...
public void setIconAt(ImageIcon icon, int row, int col) {
StatusTableCellRenderer cell =
(StatusTableCellRenderer)getColumnModel().getColumn(col).getCellRenderer().
getTableCellRendererComponent(myTableObject, null, false, false, row, col);
System.out.println(cell.getIcon()); // Shows initial icon source
cell.setIcon(icon);
fireTableCellUpdated(row, col); // Should update the table
System.out.println(cell.getIcon()); // Shows new icon source
System.out.println("Cell updated");
} // End setIconAt(ImageIcon, int, int)
} // End class MonitorTableModel
public class StatusTableCellRenderer extends DefaultTableCellRenderer {
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int col) {
setIcon(imgGray);
setText((String)value);
return this;
} // End getTableCellRendererComponent(JTable, Object, boolean, boolean, int, int)
} // End class StatusTableCellRenderer
} // End class MonitorTable
My model has already been defined to return JLabel.class for these cells,
But according to the code in your renderer you expect a String value in these cells:
setText((String)value);
I don't like your setIcon() method. I would not pass in the URL. I would pass in the Icon. Maybe you have a problem that the icon has not been read into memory at the time the cell is rendered.
what is the best way to get and set an icon to a JLabel cell of a JTable,
You should not store a JLable in the TableModel. It is expensive to store Swing components in the model, that is why Swing components use renderers. Instead you store a custom Object like "LabelInfo" which contains two properties, the text and the Icon. Then your custom renderer will extend the default renderer and invoke super.getTableCellRendererComponent(). You can then access your object and rest the text/icon properties of the renderer. You should not be creating objects in the renderer.
Now when you want to change something in the model you can do:
LabelInfo info = (LabelInfo)table.getValueAt(row, column);
info.setIcon(...);
table.setValueAt(info, row, column);
Thats all you need. There is not custom code to repaint the cell or anything because that is already built intothe setValueAt(...) method. of your table model.
Edit: a simple example for using a custom Object in the TableModel.
1) to add the object to the model you do something like:
LabelInfo info = new LabelInfo("some Text", yourIcon);
table.setValueAt(info, row, column);
2) the code for your custom renderer would be:
class LabelInfoRenderer extends DefaultTableCellRenderer
{
#Override
public Component getTableCellRendererComponent(
JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
{
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
LableInfo info = (LabelInfo)value;
setIcon( info.getIcon() );
return this;
}
}
Call the fireTableDataChanged from your model.
Try call the repaint method from JLabel too.
The better way to do it, is implement a CellRenderer, with returns a JPanel and make the draw method in the paintComponent. You can load a BufferedImage instead of a ImageIcon and use it to draw in the JPanel.
Try change you Renderer to:
public class StatusTableCellRenderer extends DefaultTableCellRenderer {
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int col) {
JLabel comp = new JLabel(new ImageIcon(imgGray));
comp.setText((String)value);
return comp;
} // End getTableCellRendererComponent(JTable, Object, boolean, boolean, int, int)
}
You are doing some mess, this is not the right way to work with the TableModel, you should return the URL for the image in the getValueAt(int row, int col), after it, register the CellRenderer to correspond to cells with URL.class. The renderer is automatically called from the JTable, you don't need to extends JTable either, you have only to implement the Renderer and the Model.
The setIconAt should only call the setValueAt and put your URL at the column, the renderer take care of the rest.
I fixed this by changing setIcon(imgGray) to if (getIcon() == null) setIcon(imgGray);.
The issue is my getTableCellRendererComponent method was setting the icon to imgGray every time. Apparently my setIconAt method, which calls getTableCellRendererComponent, was being overridden, even though the "new" icon value was processed after the "old" value was (re)set.
I ended up removing all my setIcon methods and moved the relevant logic into my StatusTableCellRenderer class. That way I pass the value of the cell and let the renderer do the icon setting based on that value. It makes more sense this way, and works beautifully. I have confirmed that initial setting and all subsequent updates are performing as expected.
The logic of setting the icon is pretty simple -- set the predefined icon based on certain predefined threshold values.
double val;
if (getIcon() == null) setIcon(imgGray); // Initialize
if ((value == null) || (value == "")) {
val = 0;
} else {
val = Double.parseDouble(value.toString());
} // End if
if (val <= THRESHOLD1) {
setIcon(icon1);
} else if (val <= THRESHOLD2) {
setIcon(icon2);
...
} // End if
setText(value.toString());
I was very concerned about suggestions to make brand new objects to use, when the default JLabel was exactly what I needed. It was both unnecessary and a potential performance hit to the JTable. Thank you all for your insight and assistance. This was driving me batty!
The following ListCellRenderer does not receive click events on the nested ComboBoxes. Do I need to enable something?
class FilterCellRenderer implements ListCellRenderer {
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
Filter filter = (Filter)value;
JPanel filterPanel = new JPanel();
FlowLayout layout = new FlowLayout();
layout.setAlignment(FlowLayout.LEFT);
filterPanel.setLayout(layout);
filterPanel.add(new JLabel(filter.getLabel()));
final List<Object> options = filter.getOptions();
if (options.size() > 1) {
JComboBox optionCombo = new JComboBox(new AbstractComboBoxModel() {
public int getSize() {
return options.size();
}
public Object getElementAt(int index) {
return options.get(index);
}
});
optionCombo.setSelectedItem(filter.getValue());
filterPanel.add(optionCombo);
}
if (isSelected) {
filterPanel.setBackground(list.getSelectionBackground());
filterPanel.setForeground(list.getSelectionForeground());
}
return filterPanel;
}
}
Renderer components in swing work like "rubber stamps" -they are just used to render/paint a value and are not added to the parent container in the usual way (just think how a single component could be added in multiple places!).
It sounds like you may want an editor rather than a renderer (an editor is a fully-fledged component, added in one place at any given time). Failing that you will have to install the MouseListener on the JList instead.
Since I didn't need to select rows, I ended up just dynamically adding and elements to a JPanel with a custom layout. Allowed for full component behaviour without having to hack a table.
It's a little bit tricky this. I believe you need to replace the JList with a single column JTable. Then set a table cell editor as well as renderer. IIRC, there might be a problem losing the first click (which gets used to select that cell edited).
Also it's a very good idea to reuse the components between each call to getCellRendererComponent. The components are used as a stamp and then discarded. Performance will suck massively if they are recreated each time.