I have a custom TableCellRenderer (ValueRenderer) for a JTable, the cell is a Checkbox.
I have attached an ItemListener to the valueRenderer to listen to the checkbox's state change (selected/deselected) as mentioned by this example.
My problem is that inside the itemStateChanged(ItemEvent e), I do not know how to get the row in which this checkbox is contained knowing that the ItemEvent source is the ValueRenderer.
Can you help me?
Here is some of my code:
Custom TableCellRender:
public class ValueRenderer extends JCheckBox implements TableCellRenderer {
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) {
this.setSelected((Boolean) value);
return this;
}
}
ItemListener:
public class TableRowCheckBoxListener implements ItemListener {
private JTable hqlRequestTable;
public TableRowCheckBoxListener(JTable hqlRequestTable) {
this.hqlRequestTable = hqlRequestTable;
}
#Override
public void itemStateChanged(ItemEvent e) {
/*How do I get the row which contains the checkbox clicked knowing that :
e.getSource() == ValueRenderer
e.getItem() == ValueRender
*/
}
}
If you want to know when some value changes in your table, you must not register a listener on the renderer. You must register a listener on the table model: that's where the data displayed by the table is held, and that's the object which fires an event if anything changes in the data.
The alternative is to use a custom table model consisting in a list of beans, have the table model modify the properties of the beans it holds, and have the bean fire a property change event when a property changes. You'll then register listeners on the beans themselves rather than registering a table model listener (note that the table model still has to fire table model events, though).
Related
I'm using TableCellRenderer to render a button in a cell for a JTable created with Matisse in netbeans.
My problem is ... When a double click on the button, I can reach the text field behind. So I want to set the textfield not editable.
For now, my setEnabled are on true: table_watchlistMain.setEnabled(true); I need that because I want to user to be eable to select a row ...
I'm using a DefaultTableModel... do I need to make my own model?
I'm just searching a solution to put the jtable enabled, but not editable. this is possible??
The DefaultTableModel.isCellEditable() method always returns true:
Returns true regardless of parameter values.
So, yes, you should create your own model, for example:
public class MyTableModel extends DefaultTableModel
{
#Override
public boolean isCellEditable(int row, int column)
{
return false;
}
}
So I have a class called Note and I need to put every Note inside a JComboBox.
Each Note has a String id and a String title. The title is being shown to the user, and the id is being used in the backend.
I've written a custom renderer to make this work, but I am getting compiler error: error: Note cannot be converted to String
Here is the code:
//Inside of the GUI class
cmbNotes.setRenderer(new NoteListCellRenderer());
//Populate combo box with the title of each note
NoteManager.notes.forEach((id, note) -> { //For-each loop
if (!note.isOpen()) {
cmbNotes.addItem(note); //ERROR: Note cannot be converted to String
}
});
Here is my custom renderer:
//In the same file as GUI, but outside of the GUI class
class NoteListCellRenderer extends DefaultListCellRenderer {
#Override
public Component getListCellRendererComponent(
JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus) {
if (value instanceof Note) {
value = ((Note) value).getTitle();
}
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
return this;
}
}
Thank you Riann Nel for helping me to solve this problem.
I had created the JComboBox through the design, not through code.
The design by default sets the Type Parameters to String so I went in the design and clicked on the code section on the right then changed the Type Parameters to <Note>.
I also had to add a default constructor in my Note class.
I know that by using JTable the column is sorted when we click on the column heading, but what I want is that, when I right-click on the column name a function name 'sort' should be displayed. Any suggestion in doing it?
Start by adding a MouseListener to the table. See How to write mouse listeners
You will need to translate the click point to a column, see JTable#columnAtPoint.
You will then need to update the SortKey for the table. Check out Sorting and Filtering for an example
If I understand you correctly, you want to sort by some explicit action (triggered f.i. in a popup) instead of by the normal left-click.
If so, the tricky part is to force the ui-delegate to do nothing. There are two options:
hook into the default mouse listener installed by the ui delegate, as described in a recent QA
let the ui do its stuff, but fool it by a sorter implementation that doesn't follow the rules (beware: that's as dirty as the first approach!)
The mis-behaving sorter:
public class MyTableRowSorter extends TableRowSorter {
public MyTableRowSorter(TableModel model) {
super(model);
}
/**
* Implemented to do nothing to fool tableHeader internals.
*/
#Override
public void toggleSortOrder(int column) {
}
/**
* The method that really toggles, called from custom code.
*
* #param column
*/
public void realToggleSortOrder(int column) {
super.toggleSortOrder(column);
}
}
// usage
final JTable table = new JXTable(new AncientSwingTeam());
table.setRowSorter(new MyTableRowSorter(table.getModel()));
Action toggle = new AbstractAction("toggleSort") {
#Override
public void actionPerformed(ActionEvent e) {
JXTableHeader header = SwingXUtilities.getAncestor(
JXTableHeader.class, (Component) e.getSource());
Point trigger = header.getPopupTriggerLocation();
int column = trigger != null ? header.columnAtPoint(trigger) : -1;
if (column < 0) return;
int modelColumn = header.getTable().convertColumnIndexToModel(column);
((MyTableRowSorter) header.getTable().getRowSorter())
.realToggleSortOrder(modelColumn);
}
};
JPopupMenu menu = new JPopupMenu();
menu.add(toggle);
table.getTableHeader().setComponentPopupMenu(menu);
Yeah, couldn't resist throwing in some SwingX api, lazy me :-) With plain Swing, you'll have to write some lines more but the basics are the same: install the tricksy sorter and use its custom toggle sort to really sort whereever needed, f.i. in a mouseListener.
So i am working with a JTable, It has Columns A-K. with A and B being the only editable ones. If someone edits an empty row in A, I make an API call to get B then i make a DB call to get all rows where B exists.If someone edits an empty row in B, i make the same call as the will be retrieved from the DB for that row as well. The call returns 0-N rows. If 0 rows were returned, I change the values of all row except B to N/A otherwise i populate the rows using the data.Once populated, i make all columns non-editable. The DB call occurs in its own thread as once the call is return i create my own record object which I add to the tablemodel.
I have my own TableModel and a TableModelListener to keep the data and handle changes in values.
Here is my issue. I am using TableCellRenderer and using the cellrenderer to see if the value was changed, if so then i make the calls and populate as needed. When a large number of rows is being pulled from DB, it takes a while to load and making all that records so I tried to use a ProgressBar to show the user that the screen isn't just frozen, it is progressing and by how much. However the frame that comes up is blank and nothing gets displayed. I get the feeling i am doing something either improperly or missing something.Any help much appreciated.
some code to understand what i am talking about
public class MyPanel extends JPanel {
private JTable myTable;
private MyTableModel tm;
//OTHER FIELDS
public static void createPanel() {
tm = new MyTableModel(columnnames);
myTable = new JTable(tm);
TableColumn account = myTable.getColumnModel().getColumn(
MyTableModel.ACCOUNT_INDEX);
account.setCellRenderer(new MyTableRenderer(
MyTableModel.ACCOUNT_INDEX));
}
}
public class MyTableRenderer extends DefaultTableCellRenderer{
protected int interactiveColumn;
public MyTableRenderer(int interactiveColumn) {
this.interactiveColumn = interactiveColumn;
}
public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus, int row,
int column) {
Component c = super.getTableCellRendererComponent(table, value,
isSelected, hasFocus, row, column);
if (column == interactiveColumn && hasFocus) {
//DO DB and API CALLS HERE
//IF DB CALL DISPLAY A NEW FRAME WITH PROGRESSBAR
}
return c;
}
}
Sorry for formatting issues
Use SwingWorker, which allows you to update your TableModel as you examine your result set.
Addendum: Don't try to update the TableModel from the renderer. You can update the model when your implementation of CellEditor has concluded, by starting a suitable worker in getCellEditorValue(). In that way, the revised data will be available when the renderer is next invoked for any modified cell(s). This related example outlines the approach.
Addendum: getCellEditorValue() is invoked after editing has concluded, but starting the worker in setValueAt() offers more reliable access to the target row and column.
It is weird that I set my JTable cell renderer like this:
setDefaultRenderer(Object.class, new MyTableRenderer());
My table renderer works like this:
class MyTableRenderer extends DefaultTableCellRenderer {
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
Component comp = super.getTableCellRendererComponent(table, value,
isSelected, hasFocus, row, column);
Font font = comp.getFont();
if (table.getModel().getValueAt(row, 0).equals(BUY)) {
comp.setFont(font.deriveFont(Font.BOLD));
comp.setForeground(BUY_COLOR);
}
else {
comp.setFont(font.deriveFont(Font.BOLD));
comp.setForeground(SELL_COLOR);
}
return comp;
}
}
But it turns out, it didin't apply those columns that have type "BigDecimal", Other String fields are all working fine.
And then, I add one more line:
setDefaultRenderer(BigDecimal.class, new MyTableRenderer());
Then everything just work fine.
Why it is like this?
JTable by default installs a renderer for type Number. BigDecimal is-a Number so the default renderer is used instead of your custom renderer.
BTW: your custom renderer is buggy in that it doesn't take potentially sorted/filtered rows into account (the row/column index params of the method are view coordinates).
#eugener: your answer is wrong - it's not the storage that's important but the lookup ;-)
Cheers
Jeanette
Take look at the source code of the JTable:
public void setDefaultRenderer(Class<?> columnClass, TableCellRenderer renderer) {
if (renderer != null) {
defaultRenderersByColumnClass.put(columnClass, renderer);
} else {
defaultRenderersByColumnClass.remove(columnClass);
}
}
It uses a map where column class is a key and means that class comparison is literal. This should explain the reason for behavior you're experiencing. This is by design.