Typing Number Into JSpinner with Subclassed SpinnerListModel - java

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);
}

Related

Java class: limit instance variable to one of several possible values, depending on other instance variables

I am sorry for the vague question. I am not sure what I'm looking for here.
I have a Java class, let's call it Bar. In that class is an instance variable, let's call it foo. foo is a String.
foo cannot just have any value. There is a long list of strings, and foo must be one of them.
Then, for each of those strings in the list I would like the possibility to set some extra conditions as to whether that specific foo can belong in that specific type of Bar (depending on other instance variables in that same Bar).
What approach should I take here? Obviously, I could put the list of strings in a static class somewhere and upon calling setFoo(String s) check whether s is in that list. But that would not allow me to check for extra conditions - or I would need to put all that logic for every value of foo in the same method, which would get ugly quickly.
Is the solution to make several hundred classes for every possible value of foo and insert in each the respective (often trivial) logic to determine what types of Bar it fits? That doesn't sound right either.
What approach should I take here?
Here's a more concrete example, to make it more clear what I am looking for. Say there is a Furniture class, with a variable material, which can be lots of things, anything from mahogany to plywood. But there is another variable, upholstery, and you can make furniture containing cotton of plywood but not oak; satin furniture of oak but not walnut; other types of fabric go well with any material; et cetera.
I wouldn't suggest creating multiple classes/templates for such a big use case. This is very opinion based but I'll take a shot at answering as best as I can.
In such a case where your options can be numerous and you want to keep a maintainable code base, the best solution is to separate the values and the logic. I recommend that you store your foo values in a database. At the same time, keep your client code as clean and small as possible. So that it doesn't need to filter through the data to figure out which data is valid. You want to minimize dependency to data in your code. Think of it this way: tomorrow you might need to add a new material to your material list. Do you want to modify all your code for that? Or do you want to just add it to your database and everything magically works? Obviously the latter is a better option. Here is an example on how to design such a system. Of course, this can vary based on your use case or variables but it is a good guideline. The basic rule of thumb is: your code should have as little dependency to data as possible.
Let's say you want to create a Bar which has to have a certain foo. In this case, I would create a database for BARS which contains all the possible Bars. Example:
ID NAME FOO
1 Door 1,4,10
I will also create a database FOOS which contains the details of each foo. For example:
ID NAME PROPERTY1 PROPERTY2 ...
1 Oak Brown Soft
When you create a Bar:
Bar door = new Bar(Bar.DOOR);
in the constructor you would go to the BARS table and query the foos. Then you would query the FOOS table and load all the material and assign them to the field inside your new object.
This way whenever you create a Bar the material can be changed and loaded from DB without changing any code. You can add as many types of Bar as you can and change material properties as you goo. Your client code however doesn't change much.
You might ask why do we create a database for FOOS and refer to it's ids in the BARS table? This way, you can modify the properties of each foo as much as you want. Also you can share foos between Bars and vice versa but you only need to change the db once. cross referencing becomes a breeze. I hope this example explains the idea clearly.
You say:
Is the solution to make several hundred classes for every possible
value of foo and insert in each the respective (often trivial) logic
to determine what types of Bar it fits? That doesn't sound right
either.
Why not have separate classes for each type of Foo? Unless you need to define new types of Foo without changing the code you can model them as plain Java classes. You can go with enums as well but it does not really give you any advantage since you still need to update the enum when adding a new type of Foo.
In any case here is type safe approach that guarantees compile time checking of your rules:
public static interface Material{}
public static interface Upholstery{}
public static class Oak implements Material{}
public static class Plywood implements Material{}
public static class Cotton implements Upholstery{}
public static class Satin implements Upholstery{}
public static class Furniture<M extends Material, U extends Upholstery>{
private M matrerial = null;
private U upholstery = null;
public Furniture(M matrerial, U upholstery){
this.matrerial = matrerial;
this.upholstery = upholstery;
}
public M getMatrerial() {
return matrerial;
}
public U getUpholstery() {
return upholstery;
}
}
public static Furniture<Plywood, Cotton> cottonFurnitureWithPlywood(Plywood plywood, Cotton cotton){
return new Furniture<>(plywood, cotton);
}
public static Furniture<Oak, Satin> satinFurnitureWithOak(Oak oak, Satin satin){
return new Furniture<>(oak, satin);
}
It depends on what you really want to achieve. Creating objects and passing them around will not magically solve your domain-specific problems.
If you cannot think of any real behavior to add to your objects (except the validation), then it might make more sense to just store your data and read them into memory whenever you want. Even treat rules as data.
Here is an example:
public class Furniture {
String name;
Material material;
Upholstery upholstery;
//getters, setters, other behavior
public Furniture(String name, Material m, Upholstery u) {
//Read rule files from memory or disk and do all the checks
//Do not instantiate if validation does not pass
this.name = name;
material = m;
upholstery = u;
}
}
To specify rules, you will then create three plain text files (e.g. using csv format). File 1 will contain valid values for material, file 2 will contain valid values for upholstery, and file 3 will have a matrix format like the following:
upholstery\material plywood mahogany oak
cotton 1 0 1
satin 0 1 0
to check if a material goes with an upholstery or not, just check the corresponding row and column.
Alternatively, if you have lots of data, you can opt for a database system along with an ORM. Rule tables then can be join tables and come with extra nice features a DBMS may provide (like easy checking for duplicate values). The validation table could look something like:
MaterialID UpholsteryID Compatability_Score
plywood cotton 1
oak satin 0
The advantage of using this approach is that you quickly get a working application and you can decide what to do as you add new behavior to your application. And even if it gets way more complex in the future (new rules, new data types, etc) you can use something like the repository pattern to keep your data and business logic decoupled.
Notes about Enums:
Although the solution suggested by #Igwe Kalu solves the specific case described in the question, it is not scalable. What if you want to find what material goes with a given upholstery (the reverse case)? You will need to create another enum which does not add anything meaningful to the program, or add complex logic to your application.
This is a more detailed description of the idea I threw out there in the comment:
Keep Furniture a POJO, i.e., just hold the data, no behavior or rules implemented in it.
Implement the rules in separate classes, something along the lines of:
interface FurnitureRule {
void validate(Furniture furniture) throws FurnitureRuleException;
}
class ValidMaterialRule implements FurnitureRule {
// this you can load in whatever way suitable in your architecture -
// from enums, DB, an XML file, a JSON file, or inject via Spring, etc.
private Set<String> validMaterialNames;
#Overload
void validate(Furniture furniture) throws FurnitureRuleException {
if (!validMaterialNames.contains(furniture.getMaterial()))
throws new FurnitureRuleException("Invalid material " + furniture.getMaterial());
}
}
class UpholsteryRule implements FurnitureRule {
// Again however suitable to implement/config this
private Map<String, Set<String>> validMaterialsPerUpholstery;
#Overload
void validate(Furniture furniture) throws FurnitureRuleException {
Set<String> validMaterialNames = validMaterialsPerUpholstery.get(furniture.getUpholstery();
if (validMaterialNames != null && !validMaterialNames.contains(furniture.getMaterial()))
throws new FurnitureRuleException("Invalid material " + furniture.getMaterial() + " for upholstery " + furniture.getUpholstery());
}
}
// and more complex rules if you need to
Then have some service along the lines of FurnitureManager. It's the "gatekeeper" for all Furniture creation/updates:
class FurnitureManager {
// configure these via e.g. Spring.
private List<FurnitureRule> rules;
public void updateFurniture(Furniture furniture) throws FurnitureRuleException {
rules.forEach(rule -> rule.validate(furniture))
// proceed to persist `furniture` in the database or whatever else you do with a valid piece of furniture.
}
}
material should be of type Enum.
public enum Material {
MAHOGANY,
TEAK,
OAK,
...
}
Furthermore you can have a validator for Furniture that contains the logic which types of Furniture make sense, and then call that validator in every method that can change the material or upholstery variable (typically only your setters).
public class Furniture {
private Material material;
private Upholstery upholstery; //Could also be String depending on your needs of course
public void setMaterial(Material material) {
if (FurnitureValidator.isValidCombination(material, this.upholstery)) {
this.material = material;
}
}
...
private static class FurnitureValidator {
private static boolean isValidCombination(Material material, Upholstery upholstery) {
switch(material) {
case MAHOGANY: return upholstery != Upholstery.COTTON;
break;
//and so on
}
}
}
}
We often are oblivious of the power inherent in enum types. The Java™ Tutorials clearly states "you should use enum types any time you need to represent a fixed set of constants."
How do you simply make the best of enum in resolving the challenge you presented? - Here goes:
public enum Material {
MAHOGANY( "satin", "velvet" ),
PLYWOOD( "leather" ),
// possibly many other materials and their matching fabrics...
OAK( "some other fabric - 0" ),
WALNUT( "some other fabric - 0", "some other fabric - 1" );
private final String[] listOfSuitingFabrics;
Material( String... fabrics ) {
this.listOfSuitingFabrics = fabrics;
}
String[] getListOfSuitingFabrics() {
return Arrays.copyOf( listOfSuitingFabrics );
}
public String toString() {
return name().substring( 0, 1 ) + name().substring( 1 );
}
}
Let's test it:
public class TestMaterial {
for ( Material material : Material.values() ) {
System.out.println( material.toString() + " go well with " + material.getListOfSuitingFabrics() );
}
}
Probably the approach I'd use (because it involves the least amount of code and it's reasonably fast) is to "flatten" the hierarchical logic into a one-dimensional Set of allowed value combinations. Then when setting one of the fields, validate that the proposed new combination is valid. I'd probably just use a Set of concatenated Strings for simplicity. For the example you give above, something like this:
class Furniture {
private String wood;
private String upholstery;
/**
* Set of all acceptable values, with each combination as a String.
* Example value: "plywood:cotton"
*/
private static final Set<String> allowed = new HashSet<>();
/**
* Load allowed values in initializer.
*
* TODO: load allowed values from DB or config file
* instead of hard-wiring.
*/
static {
allowed.add("plywood:cotton");
...
}
public void setWood(String wood) {
if (!allowed.contains(wood + ":" + this.upholstery)) {
throw new IllegalArgumentException("bad combination of materials!");
}
this.wood = wood;
}
public void setUpholstery(String upholstery) {
if (!allowed.contains(this.wood + ":" + upholstery)) {
throw new IllegalArgumentException("bad combination of materials!");
}
this.upholstery = upholstery;
}
public void setMaterials(String wood, String upholstery) {
if (!allowed.contains(wood + ":" + upholstery)) {
throw new IllegalArgumentException("bad combination of materials!");
}
this.wood = wood;
this.upholstery = upholstery;
}
// getters
...
}
The disadvantage of this approach compared to other answers is that there is no compile-time type checking. For example, if you try to set the wood to plywoo instead of plywood you won’t know about your error until runtime. In practice this disadvantage is negligible since presumably the options will be chosen by a user through a UI (or through some other means), so you won’t know what they are until runtime anyway. Plus the big advantage is that the code will never have to be changed so long as you’re willing to maintain a list of allowed combinations externally. As someone with 30 years of development experience, take my word for it that this approach is far more maintainable.
With the above code, you'll need to use setMaterials before using setWood or setUpholstery, since the other field will still be null and therefore not an allowed combination. You can initialize the class's fields with default materials to avoid this if you want.

Transporting values in arrays within scopes in Swing - java

my goal is to be able to draw graphs and then save its values if required.
In the image above, I choose from the comboBox a specific type of graph to draw, except for Clear, which just clears the graph that is shown. This is already working correctly.
I have an arrayList of UserPattern(I created) objects that contains a string, a double and a double array.
I want to store the values from the graph in some variable (i'm currently using a double array) to use it later on.
With the double array I've had problems with the passing of values, since when I press "Save Pattern" it keeps the latest values of the graph (the last graph shown) and inputs it on every element of the UserPattern List that I have previously saved. So, even if I save multiple patterns, they all keep the value of the last save.
This is the code I use to store the values in the ArrayList:
private void readAndInsertPatternValues(List<UserPattern> patternLi, double[] graphValue) {
UserPattern tempUserPattern = new UserPattern(typePattern);
//extra code
tempUserPattern.setMonthlyConsump(consTemp);
tempUserPattern.setNameID(patternName);
tempUserPattern.setPatternValues(graphValue);
patternLi.add(tempUserPattern);
System.out.println("Inserted: ");
System.out.println(tempUserPattern);
}
}
I call this method within a mouse event on the button "Save Pattern":
JButton btnSaveUserPattern = new JButton("Save Pattern");
btnSaveUserPattern.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
if(!((comboBoxPattern.getSelectedItem()).equals(UserPattern.PatternType.CLEAR)))
readAndInsertPatternValues(patternList, patternValue);
for(UserPattern upTemp : patternList) {
System.out.println("Inside the Pattern List:");
System.out.println(upTemp.toString());
}
}
});
btnSaveUserPattern.setFont(new Font("Tahoma", Font.PLAIN, 13));
btnSaveUserPattern.setEnabled(false);
Also, I get the values that create the graph in the event from selecting an option of the combobox:
comboBoxPattern = new JComboBox<UserPattern.PatternType>();
comboBoxPattern.setFont(new Font("Tahoma", Font.PLAIN, 13));
comboBoxPattern.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
//this method creates the graphs, so I send the array "patternValue" to "get" the values.
printPatternGraph(comboBoxPattern, chartPanel, "User Pattern for Energy Consumption", "Hours", "Energy Consumption(Wh)", true, patternValue);
btnSaveUserPattern.setEnabled(true);
}
});
I initialized the patternValue array as a local variable in the method that calls and uses the above mentioned methods as such:
double[] patternValue = new double[1440];
With all this, happened the problem in transporting values. At the "insertion time", the console showed the correct values, however, when I clicked on the "Save Pattern" button, which shows all the objects in the UserPattern list, the graph values from the previously saved graphs were equal to the one I saved last. (The last one I saved corrupted all the others).
I tried to change the way of passing values and changed the method "printPatternGraph" to return a double[] array, and made like this:
patternValue=printPatternGraph(comboBoxPattern, chartPanel, "User Pattern for Energy Consumption", "Hours", "Energy Consumption(Wh)", true);
This, however, brought me the error,
Local variable patternValue defined in an enclosing scope must be final or effectively final.
Therefore, I tried making "patternValue" a global variable . This actually solved the problem, but I don't think that this is the best solution (I've read multiple times that global variables are "bad coding".
This way, which way do you think I should implement this?
Thanks for your attention and sorry for the long post,
nhekas
SOLUTION:
I understood what was wrong! Sorry to bother you guys.
My problem, was, that when I added the double array to the ArrayList, I did it like this:
patternLi.add(graphValues);
This, instead of storing the values inside the graphValue array, was storing the reference to it. Therefore, when I wanted to create a different graph, the reference to the graph was the same, but changed the values.
I had to do the following:
new double[] arrayTemp= new double[1440];
for ( int i=0; i<1440;i++) {
arrayTemp[i]=graphValues[i];
}
patternLi.add(arrayTemp);
This way, it worked! Because, since arrayTemp is local, is created everytime I call the method and passes the correct values.
Thanks for all your input guys !
nhekas

How to check Type Ranges with Java development tools (JDT)?

I want to parse a String, which contains a number, using JDT to find out whether the contained number is inside the valid Range of one of the Primitive Types.
Let's say i got a float value like this as String "1.7976931348623157e350" and want to see whether it is still inside the allowed range for primitive type 'double'. (In this case it would not be inside the valid range, because the maximum exponent of double is 308).
I don't want to use the standard methods like : Double.parseDouble("1.7976931348623157e350"), because I'm afraid it might be too slow if I have a big amount of primitive types, which I want to check .
If you know the Eclipse development environment you will know that inside a normal java file, eclipse is able to tell whether a variable is out of range or not, by underlining it red, in the the case of 'out of range'. So basically i want to use this functionality. But as you can guess - it's easier said then done!
I have started experimenting with the ASTParser from this library: org.eclipse.jdt.core.dom
But I must admit I was not very successful here.
First i tried calling some of those vistor methods using methods like:
resolveBinding() , but they always only returned me "Null".
I have found some interesting class called ASTSyntaxErrorPropagator , but i'm not sure how this is used correctly. It seems to propagate parsing problems or something like that and gets it's information delivered by some thing class called CodeSnippetParsingUtil I assume. Anyways, these are only speculations.
Does anyone know how to use this ASTParser correctly?
I would be really thankful for some advice.
Here is some basic code-snipped which I tried to debug:
public class DatatypesParser {
public static void main(String[] args) {
ASTParser parser = ASTParser.newParser(AST.JLS4);
Map options = JavaCore.getOptions();
JavaCore.setComplianceOptions(JavaCore.VERSION_1_7, options);
String statement = new String("int i = " + Long.MAX_VALUE + ";");
parser.setSource(statement.toCharArray());
parser.setKind(ASTParser.K_STATEMENTS);
parser.setResolveBindings(true);
parser.setBindingsRecovery(true);
ASTNode ast = parser.createAST(null);
ast.accept(new ASTVisitor() {
#Override
public boolean visit(VariableDeclarationStatement node) {
CodeSnippetParsingUtil util = new CodeSnippetParsingUtil();
return true;
}
});
}
I don't want to use the standard methods like :
Double.parseDouble("1.7976931348623157e350"), because i'm afraid it
might be too slow if i have a big amount of primitive types, which i
want to check .
Under the hood JDT is actually using the standard methods of Double to parse the value, and quite a bit more - so you should always use the standard methods if performance is a concern.
Here is how the double gets parsed by JDT.
From org.eclipse.jdt.internal.compiler.ast.DoubleLiteral:
public void computeConstant() {
Double computedValue;
[...]
try {
computedValue = Double.valueOf(String.valueOf(this.source));
} catch (NumberFormatException e) {
[...]
return;
}
final double doubleValue = computedValue.doubleValue();
if (doubleValue > Double.MAX_VALUE) {
// error: the number is too large to represent
return;
}
[...]
}

Restrict which values will be settleable in an javafx property?

What's the best way to keep the value of a javafx Property within specific bounds?
(Or - is this bad practice, existing any reason to never filter values wrapped by javafx properties?)
Example1: avoid negative values in an IntegerProperty
Example2: keep the value of an IntegerProperty within the bounds of a List
First idea: - override IntegerPropertyBase.set(int). It's safe? Actually setValue(int) only calls set(int), but - if this implementation one day changes - the control over the values set goes lost.
Second idea: - override IntegerPropertyBase.invalidate(). But at this point the value already was set.
Will it fit better to javafx properties throw an IllegalArgumentException (or an ArrayIndexOutOfBoundsException, if the wrapped value is the index of an array), or better refuse the value out of bounds, setting back the last value in bounds?
Maybe like this:
class BoundedIntegerProperty extends IntegerPropertyBase {
(...)
int oldValue = defaultValueInBounds;
boolean settingOldValue = false;
public void invalidated() {
if(!settingOldValue){
if(outOfBounds(get())){
settingOldValue = true;
set(oldValue);
} else {
oldValue = get();
}
} else
settingOldValue = false;
}
}
Only throw an Exception in invalidated() for values out of bounds may keep the value of the property out of bounds.
Have I overlooked anything in javafx properties provided to filter values?
(If necessary, please help me improving the possibly bad english of this text...)
In both your examples, there seemed to be a logical default value (eg. if it's required to be positive, negative numbers turn into 0). Assuming you document that well (what the defaults are if the value is invalid), I think your first approach seems like it's on the right path.
I'd recommend starting with a concrete class like SimpleIntegerProperty as the class you're extending (unless there's some reason you chose IntegerPropertyBase instead.
I would then overwrite both the set(int) method and the setValue(Number) method, wrapping the parent in your logic:
/**
* Explanation that values under 0 are set to 0
*/
#Override
public void set(int value){
super.set(value > 0 ? value : 0);
}
/**
* Explanation that values under 0 are set to 0
*/
#Override
public void setValue(Number value){
super.setValue(value.intValue() > 0 ? value : 0);
}
There may be a case where there isn't logical default values (or you just want to reject invalid values). That case makes it a bit harder - you'd actually want to use a method signature of like this so the caller knows if the value changed:
public boolean set(int value)
In order to do that, you'll have to go back quite a few classes - all the way back to ReadOnlyIntegerProperty and implement the setting / invalidating structure yourself.
I would hesitate to use Exceptions to handle the invalid input. It is a legitimate use of exceptions, but my fear is that the Exception would be relied on for validation. Exceptions are very resource intensive, and should only be hit if there's something that needs to be fixed. So it's really about your intentions and how much you trust people using your class to do the right thing (and validate before sending to you).
I believe I understand what you're shooting for better now. You're looking to perform user input validation.
When you're doing your user validation, there's really two ways to approach it:
Validate immediately after any change takes place and provide
feedback
Validate when the focus leaves the input area
With both, you'll be using property listeners - it's just a matter of what property listener you're dealing with.
In the first case you'll listen directly to the property you're validating:
TextField field = new TextField();
field.textProperty().addListener(new ChangeListener<String>(){
#Override
public void changed(ObservableValue<? extends String> value,
String oldValue, String newValue) {
//Do your validation or revert the value
}});
In the second case, you'll listen to the focused property, and validate when focus is lost (you can maintain the last validated value in this listener to help revert the value if necessary):
TextField field = new TextField();
field.focusedProperty().addListener(new ChangeListener<Boolean>(){
String lastValidatedValue = "";
#Override
public void changed(ObservableValue<? extends Boolean> value,
Boolean oldValue, Boolean newValue) {
if(newValue == false && oldValue == true){
//Do your validation and set `lastValidatedValue` if valid
}
}});
Note:
I was assuming you just wanted to put in a fail safe for system code updating the UI. I'll leave my previous answer as I believe it provides useful information as well.

Issue with default Sorting on Virtual Table & ViewerComparator

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.

Categories