Issue with default Sorting on Virtual Table & ViewerComparator - java

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.

Related

Hiding Multiple Elements in JTable via ButtonClick

I am currently working on a tool which edits data dynamically in a JTable. I want to hide the targeted row whenever a button is clicked. Right now I am using RowFilter. Whenever the button isClicked, a new filter is created:
RowFilter<MyTableModel, Object> rowFilter = null;
try {
rowFilter = RowFilter.notFilter(RowFilter.regexFilter(((String)dataTable.getValueAt(dataTable.getSelectedRow(), 0)),0));
} catch (java.util.regex.PatternSyntaxException e) {
return;
}
sorter.setRowFilter(rowFilter);
This only works for one element each time the button is clicked. I want to stay them hidden, so you can continously hide elemtens in the table. It is important to mention that I do not want to delete the rows, just hide them.
I hope someone has an easy answer for this, looking for quite a while now.
This method sorter.setRowFilter(rowFilter); is replacing the filter every time you "add" a new filter. So, it's "forgetting" the old rules. What you have to do is edit the existing filter to include the new rules for filtering.
Check out the documentation for more details.
In any case, I extracted a part of the documentation which you should try to implement.
From RowFilter Javadoc:
Subclasses must override the include method to indicate whether the
entry should be shown in the view. The Entry argument can be used to
obtain the values in each of the columns in that entry. The following
example shows an include method that allows only entries containing
one or more values starting with the string "a":
RowFilter<Object,Object> startsWithAFilter = new RowFilter<Object,Object>() {
public boolean include(Entry<? extends Object, ? extends Object> entry) {
for (int i = entry.getValueCount() - 1; i >= 0; i--) {
if (entry.getStringValue(i).startsWith("a")) {
// The value starts with "a", include it
return true;
}
}
// None of the columns start with "a"; return false so that this
// entry is not shown
return false;
}
};
This means that the include() method is going to return true or false depending if an item should be shown.
Therefore, you should only set the RowFilter once, and reimplment the include() method to match all the rules you currently have set upon your view.

Typing Number Into JSpinner with Subclassed SpinnerListModel

I want to have a JSpinner that displays an non-patterened sequence of numbers (say, a sequence of prime numbers). This pattern is too complicated for a SpinnerNumberModel, so I decided to subclass SpinnerListModel. The constructor looks something like this:
public CustomSpinnerListModel() {
Vector<Integer> values = new Vector<Integer>();
values.add(1);
values.add(3);
values.add(5);
values.add(7);
this.setList(values);
}
This generates the model just fine and I can move through the values using the buttons on the JSpinner. However, typing a value in doesn't work. For instance, if the spinner is set to 3 and I type in 7, it remains at 3 (presumably because it doesn't think that 7 is a valid value). This works with the SpinnerNumberModel, so I'm not sure what's going on.
EDIT: I found out that if I save the numbers as string values, typing works. However, SpinnerNumberModel saves everything as Integers and that works too. So I'm not sure why my integers don't work, but SpinnerNumberModel's do.
I think the following solution is better than the suggestion to implement a Formatter, as it is not a formatting issue, but an issue of restricting the possible values, which should be the responsibility of the model. I had a similar problem and stumbling upon this threads solution, lead to a very ugly implementation. So hopefully what I came up with will keep you out of trouble.
This generates the model just fine and I can move through the values using the buttons on the JSpinner. However, typing a value in doesn't work. For instance, if the spinner is set to 3 and I type in 7, it remains at 3 (presumably because it doesn't think that 7 is a valid value). This works with the SpinnerNumberModel, so I'm not sure what's going on.
The Problem here is that setting a new model with setModel has the undocumented side effect of changing the JTextFieldEditor attribute depending on the type of the Model:
http://fuseyism.com/classpath/doc/javax/swing/JSpinner-source.html
By default, JSpinner uses a model of class SpinnerNumberModel with an editor of class DefaultNumberEditor. When you set the model to SpinnerListModel, it will instead use a ListEditor. In your case this is a bad choice, since it requires you to enter every prime number into a list to give it to the SpinnerListModel for input verification. Otherwise, as you pointed out, your input is ignored.
So the simple solution here is to subclass SpinnerNumberModel, which allows any number, instead of a specific list of values:
class PrimeNumberModel extends SpinnerNumberModel {
Object currentValue;
#Override
public Object getNextValue() {
return findNextPrimeFrom(currentValue);
}
#Override
public Object getPreviousValue() {
return findPreviousPrimeFrom(currentValue);
}
#Override
public void setValue(Object o) {
throwOnNonePrime(o); //Verify Input
super.setValue(o);
}
private void throwOnNonePrime(Object o) {
try {
int num = Integer.valueOf(o.toString());
if(!isPrime(num))
throw new IllegalArgumentException(o.toString());
} catch (NumberFormatException nfe) {
throw new IllegalArgumentException(o.toString());
}
}
}
I think you could do it with strings and then use a method to get the number.
like this:
Spinner1(){
String[] values={"1","3","5","7"};
SpinnerModel model=new SpinnerListModel(values);
JSpinner spinner=new JSpinner(model);
}
int getValue(Object obj){
int out=0;
return out=Integer.parseInt((String)obj);
}

Implementing a recently used or favorites dropdown in JComboBox

I am looking for code that will add favorites / MRU type behavior to a JComboBox.
I could code this myself, but it sure seems like someone else has probably already done it.
I found the following (which looks exactly like what I want, but the source code is nowhere near complete): http://java.sys-con.com/node/36658
Any suggestions? I need to keep this relatively light, so I'd prefer to not use a component that's part of a monolithic widget library, and open source is preferred.
Consider extending DefaultComboBoxModel: override addElement() and insertElementAt() to insert at zero and remove the last element.
Addendum: Here's an example; per SO, the license is cc-wiki. I'd use Preferences to persist the entries.
class MRUComboBoxModel extends DefaultComboBoxModel {
#Override
public void addElement(Object element) {
this.insertElementAt(element, 0);
}
#Override
public void insertElementAt(Object element, int index) {
super.insertElementAt(element, 0);
int size = this.getSize();
if (size > 10) {
this.removeElementAt(size - 1);
}
}
}
What about just subclassing JComboBox and overriding the
public void addItem(Object anObject)
to give it the functionality you want?
You can just keep an internal list of items synched with the effective one, and whenever you add a new item it can check if size() >= maxItems and trim down least recent ones.
Then you should find a way to refresh an item whenever it is used. If its selection it's enough to be refreshed you can write an ItemListener that does it. Otherwise you'll need a specified external action or an observer/observable pattern..

Problem with db4o (java) when running a query

I'm glancing through parts of the official db4o tutorial, and I'm trying to make a modification to the code they give you for running native queries:
//the original
List<Pilot> pilots = db.query(new Predicate<Pilot>() {
public boolean match(Pilot pilot) {
return pilot.getPoints() == 100;
}
});
//modified
List<Pilot> pilots = db.query(new Predicate<Pilot>() {
public boolean match(Pilot pilot) {
return pilot.getGames() >= 100;
}
});
I've added this to their Pilot class:
//in declarations
private ArrayList<String> games;
//modified constructors
public Pilot() {
this.name=null;
this.points=0;
}
public Pilot(String name,int points) {
this.name=name;
this.points=points;
this.games = new ArrayList<String>();
int numGames = (int) (Math.random() * 1000 + 1);
for(int i=0;i<numGames;i++) {
this.games.add(name=" vs Computer");
}
}
//new method
public int getGames() {
return games.size();
}
I've already populated a database with 500 objects using the second constructor, and all the data in the db looks correct with the OME eclipse addon. I've tested getGames() and it works as expected.
My problem is that when I run the modified query, it returns all the objects in the db and I don't understand why. I've tried changing the query to include a more standard if true, else false structure and changing the query to include requiring a certain amount of points to no avail. Whatever I do, it seems it always evaluates (pilot.getGames() >= 100) to be true.
Can anyone help me as to understand why?
I think you've found a bug. db4o tries to translate the native-queries into a soda-query. This avoid instantiating to objects to perform queries. Now here this translation somehow does not work!
When you turn the optimization off it works. You can do this via configuration:
EmbeddedConfiguration cfg = Db4oEmbedded.newConfiguration();
cfg.common().optimizeNativeQueries(false);
ObjectContainer db = Db4oEmbedded.openFile(cfg,DB_FILE)
However I don't recommend this because then all queries will run slowly. I've found an easy workaround. Change the declaration of the games-field to List<String>. (And other, future List-fields). Like this:
class Pilot {
private List<String> games;
// rest
}
This will 'deoptimize' a native query as soon as you access the size() or other methods, hence avoids this bug.
Now a 'deoptimized' query can run quite slow. So if you have lots of objects and the performance is unacceptable I would do this for this query: Create an addional field which stores the current size of the list. Then you use this additional size-field for this kind of query. Additionally you can then index the size-field.
I've reported this as a bug:

Preserve JTable selection across TableModel change

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

Categories