We're seeing JTable selection get cleared when we do a fireTableDataChanged() or fireTableRowsUpdated() from the TableModel.
Is this expected, or are we doing something wrong? I didn't see any property on the JTable (or other related classes) about clearing/preserving selection on model updates.
If this is default behavior, is there a good way to prevent this? Maybe some way to "lock" the selection before the update and unlock after?
The developer has been experimenting with saving the selection before the update and re-applying it. It's a little slow.
This is Java 1.4.2 on Windows XP, if that matters. We're limited to that version based on some vendor code we use.
You need to preserve the selection and then re-apply it.
First of all you will need to get a list of all the selected cells.
Then when you re-load the JTable with the new data you need to programmatically re-apply those same selections.
The other point I want to make is, if the number or rows or columns in your table are increasing or decreasing after each table model reload, then please don't bother preserving the selection.
The user could have selected row 2 column 1 having a value say "Duck", before model updation. But after model updation that same data can now occur in row 4 column 1, and your original cell row 2 column 1 could have new data such as "Pig". Now if you forcibly set the selection to what it was before the model updation, this may not be what the user wanted.
So programmatically selecting cells could be a double edged sword. Don't do it, if you are not sure.
You can automatically preserve a table's selection if the STRUCTURE of that table hasn't changed (i.e. if you haven't add/removed any columns/rows) as follows.
If you've written your own implementation of TableModel, you can simply override the fireTableDataChanged() method:
#Override
public void fireTableDataChanged() {
fireTableChanged(new TableModelEvent(this, //tableModel
0, //firstRow
getRowCount() - 1, //lastRow
TableModelEvent.ALL_COLUMNS, //column
TableModelEvent.UPDATE)); //changeType
}
and this should ensure that your selection is maintained provided that only the data and not the structure of the table has changed. The only difference between this, and what would be called if this method weren't overridden is that getRowCount() - 1 is passed for the lastRow argument instead of Integer.MAX_VALUE, the latter of which acts a signifier that not only has all the data in the table changed but that the number of rows may have as well.
I had the same issue in an application. In my case the model in the table was a list of objects, where the object properties where mapped to columns. In that case, when the list was modified, I retrieved the selected index and stored the object that was selected before updating the list. After the list is modified and before the table is updated, I would calculate the position of the selected object. If it was still present after the modification, then I would set the selection to the new index.
Just setting the selected index in the table after the modification will not work, because the object may change position in the list.
As a side note, I found that working with GlazedLists makes life much easier when dealing with tables.
This is default behavior. If you call fireTableDataChanged() the entire table is rebuild from scratch as you set entirely new model. In this case the selection is, naturally, lost. If you call fireTableRowsUpdated() the selection is also cleared in general cases. The only way is to remember selection and then set this. Unfortunately there is no guarantee that the selection will be still valid. Be careful if restoring selection.
for reference, as #Swapnonil Mukherjee stated, this did the trick with a table with selectable rows:
// preserve selection calling fireTableDataChanged()
final int[] sel = table.getSelectedRows();
fireTableDataChanged();
for (int i=0; i<sel.length; i++)
table.getSelectionModel().addSelectionInterval(sel[i], sel[i]);
If I recall correctly, saving selection and re-applying it is what we have done too...
I was facing same issue and when tried to search the reason I got this question but it seems a bug in Java SDK. http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4276786
WORK AROUND
A temporary work-around is available. It should be removed once this bug is fixed as it's suitability has NOT been tested against fixed releases.
Use this subclass of JTable.
Note: This is for the MetalLookAndFeel. If using other look and feels, the inner FixedTableUI subclass will have to extend the TableUI subclass for that look and feel.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.table.*;
import javax.swing.event.*;
import javax.swing.plaf.basic.*;
public class FixedTable extends JTable {
private boolean isControlDownInDrag;
public FixedTable(TableModel model) {
super(model);
setUI(new FixedTableUI());
}
private class FixedTableUI extends BasicTableUI {
private MouseInputHandler handler = new MouseInputHandler() {
public void mouseDragged(MouseEvent e) {
if (e.isControlDown()) {
isControlDownInDrag = true;
}
super.mouseDragged(e);
}
public void mousePressed(MouseEvent e) {
isControlDownInDrag = false;
super.mousePressed(e);
}
public void mouseReleased(MouseEvent e) {
isControlDownInDrag = false;
super.mouseReleased(e);
}
};
protected MouseInputListener createMouseInputListener() {
return handler;
}
}
public void changeSelection(int rowIndex, int columnIndex, boolean toggle, boolean extend) {
if (isControlDownInDrag) {
ListSelectionModel rsm = getSelectionModel();
ListSelectionModel csm = getColumnModel().getSelectionModel();
int anchorRow = rsm.getAnchorSelectionIndex();
int anchorCol = csm.getAnchorSelectionIndex();
boolean anchorSelected = isCellSelected(anchorRow, anchorCol);
if (anchorSelected) {
rsm.addSelectionInterval(anchorRow, rowIndex);
csm.addSelectionInterval(anchorCol, columnIndex);
} else {
rsm.removeSelectionInterval(anchorRow, rowIndex);
csm.removeSelectionInterval(anchorCol, columnIndex);
}
if (getAutoscrolls()) {
Rectangle cellRect = getCellRect(rowIndex, columnIndex, false);
if (cellRect != null) {
scrollRectToVisible(cellRect);
}
}
} else {
super.changeSelection(rowIndex, columnIndex, toggle, extend);
}
}
}
Note Curtsey to http://bugs.sun.com
Related
I have a GWT DataGrid with a multi-selection model and check-boxes to show selection/select/deselect rows. That's all well and good.
But, I also want to have a second, independent selection model. If a user double-clicks on a row, I want to handle that event, and for the event handler to know which row was double-clicked. The double-clicking should not affect the check-box selection.
I tried this:
final SelectionModel<MyRecord> selectionModel = new MultiSelectionModel...
//Yes I need a MultiSelectionModel
dataGrid.addDomHandler(new DoubleClickHandler() {
public void onDoubleClick(DoubleClickEvent event) {
selectionModel.get??? //no suitable getter for double-clicked
}
}, DoubleClickEvent.getType());
But ran into a dead-end when I found now way to get the double-clicked row in the event handler. One way would be to register both a Multi- and Single- selection model, but doubt DataGrid will support that.
Neither can I work out how to get the clicked row from the DoubleClickEvent object.
I have implemented a button cell with a FieldUpdater. This works, but it's not ideal.
Any suggestions?
If I understand correctly you want to get the index of the row.
You could do it like this: (this way you'll get the "Real" index)
AbstractSelectionModel<T> selectionModel = (AbstractSelectionModel<T>)dataGrid.getSelectionModel();
ArrayList<Integer> intList = new ArrayList<Integer>();
List<Row> list = (List<Row>)_dataProvider.getList();
int i = 0;
for(Row row : list)
{
if( selectionModel.isSelected(row) )
intList.add(i);
i++;
}
UPDATE:
To get only the current row you could do that:
datagrid.getKeyboardSelectedRow()
I'm 3 years too late to the party, but I think the more correct solution would be:
dataGrid.addCellPreviewHandler(new CellPreviewEvent.Handler<YOUR_MODEL_TYPE>() {
#Override
public void onCellPreview(final CellPreviewEvent<YOUR_MODEL_TYPE> event) {
if (BrowserEvents.DBLCLICK.equalsIgnoreCase(event.getNativeEvent().getType())) {
int row = event.getIndex();
doStuff(row); // do whatever you need the row index for
}
}
});
We have a Virtual Table in my Eclipse RCP application. We make a call to the backend to retrieve the data to be populated in the virtual table.
We want default sorting on the table on a single column. We use ViewerComparator to achieve sorting functionality. My problem is, I am not able to get this sorting working when the table loads with the data for the 1st time. But when I click on the column, everything works fine as expected.
This is how, I set the Comparator to the column
TableViewerColumn tvc = viewer.addColumn(100, SWT.LEFT, "Name");
viewer.setColumnComparator(tvc,
new Comparator<Person>() {
#Override
public int compare(Person o1,Person o2) {
double firstValue = Double.parseDouble(o1
.getAge());
double secondValue = Double.parseDouble(o2
.getAge());
return firstValue > secondValue ? 1 : -1;
}
});
setColumnComparator method in custom viewer
public void setColumnComparator(TableViewerColumn tvc, Comparator<T> cmp){
final MyViewerComparator c = new MyViewerComparator(cmp);
final TableColumn tc = tvc.getColumn();
setComparator(c);
getTable().setSortDirection(c.getDirection());
getTable().setSortColumn(tc);
refresh();
tc.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent e) {
<same code as above>
}
});
MyViewerComparator
class MyViewerComparator extends ViewerComparator{
Comparator<T> cmp;
boolean desc = true;
MyViewerComparator(Comparator<T> cmp){
this.cmp = cmp;
}
int getDirection(){
return desc?SWT.UP:SWT.DOWN;
}
void flipDirection(){
desc = !desc;
}
#Override
public int compare(Viewer viewer, Object e1, Object e2) {
if(e1 == null || e2==null){
return 0;
}
int rc = cmp.compare((T)e1, (T)e2);
if(desc)
return -rc;
return rc;
}
}
When the table loads the data for the 1st time, it goes inside the Bolded condition in the above code as one of the object is ALWAYS NULL
Note: This functionality works totally fine if I use a Standard table rather than VIRTUAL TABLE. I am not sure whether I can change it to use Standard table as we want the lazy load functionality as well..
ContentProvider used is: ObservableListContentProvider
Please advise..
A late answer that hopefully still helps others. I encountered exactly the same problem when using SWT.VIRTUAL with an ObservableListContentProvider in combination with sorting.
The original intent of SWT.VIRTUAL is that not all elements in the contents need to be fetched to show only part of the contents. A custom content provider needs to be implemented which only has to return the elements that need to be currently shown on the screen. You also have to tell the table the total number of elements in existence. In such a use case, a table cannot be sorted in the normal way with a ViewerComparator because not all elements are known. However SWT.VIRTUAL can also be used as a performance optimization for rendering a table with many elements. This seems to work fine with the non-observable ArrayContentProvider.
But when using ObservableListContentProvider I am seeing exactly the same issue as you have. Somehow it tries to be smart and update only the elements that have actually changed. Somewhere in the depths of it's implementation something goes wrong for virtual tables, I have no clue exactly what. But I do have a solution: don't use ObservableListContentProvider at all and simply refresh the table viewer. You can e.g. use a plain ArrayContentProvider and add the following listener to the IObservableList contents of the viewer:
new IListChangeListener() {
#Override
public void handleListChange(ListChangeEvent event) {
viewer.refresh();
}
};
I actually implemented my own "SimpleObservableListContentProvider" that does exactly this, but also takes care of switching table input by implementing the inputChanged method to remove this listener from the old input list and add it to the new one.
I am trying to make my table select an entire row when you click on a cell (which can be done by turning off column select), but, I don't want the extra thick border around the specific cell you clicked to be highlighted. I was hoping this would be easy but apparently it involves renderers so I did a lot of research and the closest I can get is this:
JTable contactTable = new JTable(tableModel);
contactTable.setCellSelectionEnabled(true);
contactTable.setColumnSelectionAllowed(false);
contactTable.setRowSelectionAllowed(false);
contactTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
// This renderer extends a component. It is used each time a
// cell must be displayed.
class MyTableCellRenderer extends JLabel implements TableCellRenderer {
// This method is called each time a cell in a column
// using this renderer needs to be rendered.
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int rowIndex, int vColIndex) {
// 'value' is value contained in the cell located at
// (rowIndex, vColIndex)
if (isSelected) {
// cell (and perhaps other cells) are selected
}
if (hasFocus) {
// this cell is the anchor and the table has the focus
this.setBackground(Color.blue);
this.setForeground(Color.green);
} else {
this.setForeground(Color.black);
}
// Configure the component with the specified value
setText(value.toString());
// Set tool tip if desired
// setToolTipText((String)value);
// Since the renderer is a component, return itself
return this;
}
// The following methods override the defaults for performance reasons
public void validate() {}
public void revalidate() {}
protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {}
public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {}
}
int vColIndex = 0;
TableColumn col = contactTable.getColumnModel().getColumn(vColIndex);
col.setCellRenderer(new MyTableCellRenderer());
I copied the Renderer from an example and only changed the hasFocus() function to use the colors I wanted. Setting colors in isSelected() did nothing.
The problem with this code is:
It only works on one column specified by vColIndex at the bottom. Obviously I want this applied to all columns so clicking on a cell in one highlights the entire row. I could make a for loop to change it to every cell but I figure there's a better way of doing this that changes the cellRenderer for ALL columns at once.
setForegroundColor() works to change text but setBackgroundColor() does nothing to the cells background. I would like it to actually change the background color like the property implies.
Solution for #2: Use this.setOpaque(true); before assigning backgroundcolor.
When the renderer does work, it only works on a single Cell. How can I get it to color all the Cells in the row?
Solution for #3: I figured it out! Instead of using hasFocus(), which only affects the single cell, if you enable row selection (table.setRowSelectionAllowed(true)) then you put the color changing in the if(isSelected) statement. Then the whole row is considered selected and it colors all the cells in!
3 was the big one but if anyone knows #1 or can explain to me why it was designed such that you can only apply the renderer to one column at a time it would be much appreciated.
too direct just do add line
tablename.setSelectionBackground(Color.red);
in my case
jtbillItems.setSelectionBackground(Color.red);
The tutorial article Concepts: Editors and Renderers explains that "a single cell renderer is generally used to draw all of the cells that contain the same type of data," as returned by the model's getColumnClass() method.
Alternatively, override prepareRenderer(), which is invoked for all cells, to selectively alter a row's appearance. This example compares the two approaches, and the article Table Row Rendering expands on the versatility of the approach.
For the second question you can try setSelectionBackground(Color) and setSelectionForeGround(Color) methods. I'm not sure how you can solve the first one. And one last suggestion you can use some swing designer plugin such ass JBuilder. It would help lot.
I have question that how can I delete all datas from my jTable in GUI when a user entered a key?
thanks
You can set a new empty data model:
TableModel newModel = new DefaultTableModel();
jtable.setModel(newModel);
You need to understand that a JTable is a view of the data, while the actual data resides in the TableModel. If you need to clear out the table, then you need to clear out the TableModel.
If your TableModel is an AbstractTableModel, you must provide implementations of 3 methods:
public int getRowCount();
public int getColumnCount();
public Object getValueAt(int row, int column);
Frequently the actual data objects are stored in an additional data structure (e.g. a list), and then the AbstractTableModel queries that list.
List<DomainObject> objects = new ArrayList<DomainObject>();
public int getRowCount() { return objects.size(); }
// How many columns you make depends on what features of the objects you're exposing.
public int getColumnCount() { return NUMBER_OF_COLUMNS; }
public Object getValueAt(int row, int column) {
DomainObject object = objects.get(row);
... // pull out the property based on the column they pass in
}
// By exposing this method, you can allow your Controller code to reach into this model
// and delete all the rows.
public void clear() {
objects.clear()
}
What HH is suggesting you do is change the model of your JTable to reference an empty model, which will in effect clear out the table. However, the columns etc. will not be persisted correctly (the new DefaultTableModel has no idea what those column names would be).
After you've researched how the view and model fit together more, take a look at GlazedLists. It allows a very powerful way to create TableModels which provide dynamic views of your data, e.g. by filtering out rows that do not match certain criteria.
To sum up - you're not going to find a method on the JTable to clear out its contents, because that's the job of the TableModel. You need some way of ensuring that the TableModel's backing data structures are cleared out.
If you are using the DefaultTableModel then you can just use:
model.setRowCount(0);
This is better than creating a new DefaultTableModel. Creating a new TableModel causes the TableColumnModel to be recreated, which means all the TableColumns will be resize to default values and recreated in the order in which the columns exist in the model. The user may have changed these properties and shouldn't be forced to do it again.
If you are just deleting certain rows that contain a particulsar value, then you can use the DefaultTableModel.removeRow(...) method. Make sure you start by deleting row from the end of the model and count down to 0.
call removeAll of j_table method at addActionListener
button1.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
j_table.removeAll();
data_model_table.setRowCount(0);
}
});
Qt solution is a single call to resizeColumnsToContent(), in .NET one can use TextRenderer.MeasureText(), JTable could use AUTO_RESIZE_ALL_COLUMNS.
In SWT, is there a way to programmaticaly resize columns after populating them?
Calling computeSize(SWT.DEFAULT, SWT.DEFAULT) returns the same value thus disregarding character left overs in columns.
TableColumn has setWidth(), but how do I obtain the size hint for the current content taking into account font face, etc?
Solved with:
private static void resizeColumn(TableColumn tableColumn_)
{
tableColumn_.pack();
}
private static void resizeTable(Table table_)
{
for (TableColumn tc : table.getColumns())
resizeColumn(tc);
}
In many cases, the table entries change at run-time to reflect changes in the data model. Adding entry to the data model requires to resize columns as well, but in my case calling .pack() after the modification of the model does not solved completly the problem. In particolar with decorations the last entry is never resized. This seams to be due to async table viewer update. This snipped solved my problem:
public class LabelDecoratorProvider extends DecoratingStyledCellLabelProvider {
public LabelDecoratorProvider(IStyledLabelProvider labelProvider,
ILabelDecorator decorator, IDecorationContext decorationContext) {
super(labelProvider, decorator, decorationContext);
}
#Override
public void update(ViewerCell cell) {
super.update(cell);
if (TableViewer.class.isInstance(getViewer())) {
TableViewer tableViewer = ((TableViewer)getViewer());
Table table = tableViewer.getTable();
for (int i = 0, n = table.getColumnCount(); i < n; i++)
table.getColumn(i).pack();
}
}
}