3

Let say I have a initialization method where have many initializations. Here is a example:

public void initialize(URL url, ResourceBundle rb) {

    date.setCellValueFactory(new PropertyValueFactory("date"));
    site_address.setCellValueFactory(new PropertyValueFactory("site_address"));
    bill_no.setCellValueFactory(new PropertyValueFactory("bill_no"));
    product_name.setCellValueFactory(new PropertyValueFactory("product_name"));
    shade.setCellValueFactory(new PropertyValueFactory("shade"));
    size.setCellValueFactory(new PropertyValueFactory("size"));
    mrp.setCellValueFactory(new PropertyValueFactory("mrp"));
    qty.setCellValueFactory(new PropertyValueFactory("qty"));
    less.setCellValueFactory(new PropertyValueFactory("less"));
    amount.setCellValueFactory(new PropertyValueFactory("amount"));


}

Generally I write first line date.setCellValueFactory(new PropertyValueFactory("date")); and copy that line and modify for other lines. This seems odd to me and thinking about a better way to do that. My IDE is NetBeans.

Is there any other smarter way to handle this?

Mahmudur Rahman
  • 745
  • 5
  • 13

3 Answers3

4

There's no particularly elegant way to do this that I'm aware of (other than creating structures that are very likely overkill for the task at hand), and whatever you do, at some point you have to type in the properties and associate them with each column. I really don't recommend subclassing TableColumn or generating wrapper classes implementing custom interfaces defining functionality already in the standard API.

I usually just save excess code by writing a convenience method and invoking it. The basic idea is

private void configColumn(TableColumn<?,?> column, String property) {
    column.setCellValueFactory(new PropertyValueFactory<>(property));
}

and then your initialization becomes

configColumn(date, "date");
configColumn(site_address, "site_address");
// etc ...

The productivity trick is to initially name the column something very short:

private void c(TableColumn<?,?> column, String property) {
    column.setCellValueFactory(new PropertyValueFactory<>(property));
}

and now you have far less to type:

c(date, "date");
c(site_address, "site_address");
// ...

and then once you've put all that in, use your IDE to rename the method to something more readable (I use Eclipse, so you click on the method name in the definition, choose "Refactor" and "Rename" and then type the new name in. I assume NetBeans has similar functionality.) Don't omit this part, as otherwise your code will be very hard to read when you come back to it.

If you really want to do this with some kind of loop, then assuming your TableColumns all have the same names as the corresponding properties, you can use reflection, but again I think you lose more in readability than you gain with conciseness:

public void initialize() throws Exception {
    List<String> colNames = Arrays.asList("date", "site_address", "bill_no" /*, ...*/);

    for (String colName : colNames) {
        Field f = this.getClass().getDeclaredField(colName);
        TableColumn<?,?> column = (TableColumn<?,?>) f.get(this);
        column.setCellValueFactory(new PropertyValueFactory(colName));
    }
}

Note there's one other way, but (without some fairly ugly wiring) you can only use this trick where you have access to the FXMLLoader (so you can't do this in the controller, only in the code where you load the FXML file). The FXMLLoader gives access to a namespace, which is a map between all the fx:id attribute values and the objects created in the FXML (note that there are some other key-value pairs in here too, not just the ones defined with fx:id). So, again assuming all your TableColumns have fx:id attribute values matching the property value, you can do:

FXMLLoader loader = new FXMLLoader(getClass().getResource("path/to/fxml/file"));
Parent root = loader.load();
Map<String, Object> namespace = loader.getNamespace();
for (String fxid : namespace.keySet()) {
    Object value = namespace.get(fxid);
    if (value instanceof TableColumn) {
        ((TableColumn<?,?>)value).setCellValueFactory(new PropertyValueFactory(fxid));
    }
}
James_D
  • 201,275
  • 16
  • 291
  • 322
1

Since these all have a same method in common, and this method receives the same type, maybe a solution would be to create an interface with the setCellValueFactory(PropertyValueFactory pvf) as its only method.

Then add the parameters to an array and iterate through the array and set its parameter:

public interface CVF{

    public void setCellValueFactory(PropertyValueFactory pvf);
}

Then each of your objects, i.e. date,site_address, etc . . . should implement CVF and override setCellValueFactory(PropertyValueFactory pvf);

public void initialize(URL url, ResourceBundle rb) {

  ArrayList<CVF> objs = new ArrayList<>();
  objs.add(date);
  objs.add(site_address);
// the rest of them . . . 


  String[] params = {"date","site_address","etc . . ."};

// keeping in mind the ORDER of insertion for both objs and params

  for(int i=0; i< objs.size(); i++){
      objs.get(i).setCellValueFactory(new PropertyValueFactory(params[i]));
  }

}

or even add a second method to CVF

public interface CVF{

    public void setCellValueFactory(PropertyValueFactory pvf);
    public String getParam();
}

then for example lets take the name object:

public class Name implements CVF{

    @override
    public void setCellValueFactory(PropertyValueFactory pvf){
        // your code . . . 
    }

    @override
    public String getParam(){
      return "name"; // this would change for each CVF
   }
}

then:

public void initialize(URL url, ResourceBundle rb) {

  ArrayList<CVF> objs = new ArrayList<>();
  objs.add(date);
  objs.add(site_address);
// the rest of them . . .              

  for(CVF o : objs){
      o.setCellValueFactory(new PropertyValueFactory(o.getParam());
  }

}
Esteban Rincon
  • 2,040
  • 3
  • 27
  • 44
  • 1
    Maybe I have completely misunderstood the OP's question, but since these are standard API methods, I assume all the objects are just `TableColumn`s, in which case the additional interface is just redundant. – James_D Dec 27 '15 at 18:06
1

The first question is, are your variables (date, site_address, etc.) instances of the same type?

If yes, you can create the map with objects and values, and iterate on it afterwards (using guava's ImmutableMap):

ImmutableMap<SomeCellObject,String> initializationMap = ImmutableMap.of(
        date, "date", 
        site_address, "site_address"
        // more ...
        );
for (Map.Entry<SomeCellObject, String> entry : initializationMap.entrySet()) {
    entry.getKey().setCellValueFactory(new PropertyValueFactory(entry.getValue()));
}

If not, you have to try duck typing in Java. Since, Java is statically typed language this is harder than in dynamically typed languages (like Python). A good example is provided in the Wikipedia:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class DuckTyping {

    interface Walkable  { void walk(); }
    interface Swimmable { void swim(); }
    interface Quackable { void quack(); }

    public static void main(String[] args) {
        Duck d = new Duck();
        Person p = new Person();

        as(Walkable.class, d).walk();   //OK, duck has walk() method
        as(Swimmable.class, d).swim();  //OK, duck has swim() method
        as(Quackable.class, d).quack(); //OK, duck has quack() method

        as(Walkable.class, p).walk();   //OK, person has walk() method
        as(Swimmable.class, p).swim();  //OK, person has swim() method
        as(Quackable.class, p).quack(); //Runtime Error, person does not have quack() method
    }

    @SuppressWarnings("unchecked")
    static <T> T as(Class<T> t, final Object obj) {
        return (T) Proxy.newProxyInstance(t.getClassLoader(), new Class[] {t},
            new InvocationHandler() {
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    try {
                        return obj.getClass()
                            .getMethod(method.getName(), method.getParameterTypes())
                            .invoke(obj, args);
                    } catch (NoSuchMethodException nsme) {
                        throw new NoSuchMethodError(nsme.getMessage());
                    } catch (InvocationTargetException ite) {
                        throw ite.getTargetException();
                    }
                }
            });
    }
}

class Duck {
    public void walk()  {System.out.println("I'm Duck, I can walk...");}
    public void swim()  {System.out.println("I'm Duck, I can swim...");}
    public void quack() {System.out.println("I'm Duck, I can quack...");}
}

class Person {
    public void walk()  {System.out.println("I'm Person, I can walk...");}
    public void swim()  {System.out.println("I'm Person, I can swim...");}
    public void talk()  {System.out.println("I'm Person, I can talk...");}
}

In principle, esteban's answer is good, but this code is more transparent for your application (your classes do not have to implement the same interface).

carbolymer
  • 1,439
  • 1
  • 15
  • 30
  • I think you have perhaps misunderstood the OP's question. Presumably (since this is the initialize method from a JavaFX controller, and he is invoking a method defined in the `TableColumn` class), all the objects are instances of `TableColumn`. So I interpret the question as "can I invoke `setCellValueFactory(...)` on each column, passing in a parameter that is the same as the column's field name?". But maybe I have completely misunderstood the question. – James_D Dec 27 '15 at 18:04
  • @James_D, you're right. I've corrected my answer to include this scenario. – carbolymer Dec 27 '15 at 18:10