La libreria JFace mette a disposizione una serie di widgets, chiamati viewers, in grado di rappresentare un modello di dati. A cosa servono? Supponiamo che vogliate mostrare una tabella, un albero o una combo per visualizzare una lista di oggetti o una struttura gerarchica. Sebbene sia possibile usare widgets elementari come Table, Tree o Combo, è comunque un approccio non ottimale. Immaginate di popolare una tabella, riga per riga, partendo da una lista di Ordini e di dover fare qualche operazione sull’Ordine corrispondente alla riga selezionata dall’utente. Con un widget Table, per reperire l’Ordine selezionato, dovreste ricorrere a qualche artificio, come ad esempio fare affidamento sul valore chiave di una determinata colonna, oppure mantenere una lista degli Ordini nella stessa sequenza mostrata in tabella ed estrarre quello con indice pari alla riga selezionata. Inoltre anche il popolamento stesso, riga per riga, cella per cella, risulterebbe laborioso.

Ebbene, grazie ai viewers di JFace è possibile ottenere una sorta di “binding” fra il widget e la struttura dati stessa, quindi ad esempio possiamo ottenere direttamente l’oggetto Ordine selezionato a fronte di un evento di selezione (invece dell’indice della riga selezionata). Per effettuare questo legame occorre impostare un paio di providers al viewer stesso che prendono il nome di:
– Label provider e
– Content provider

Di seguito vediamo come usare questi providers. In particolare si farà riferimento ad un TreeViewer.
Verrà utilizzato un Modello costituito da entità Company che hanno una collection di Department, che a loro volta hanno una collection di Employee. L’obiettivo è ottenere un risultato come il seguente

Il Viewer verrà alimentato con una lista di oggetti di classe Company. La parte di codice che realizza tale input e che imposta i providers è la seguente:

 viewer.setContentProvider(new MyTreeContentProvider());
 viewer.setLabelProvider(new MyTreeLabelProvider());
 ...
 viewer.setInput(listCompany);
 

Entriamo quindi nel merito del significato dei due providers:

  • LabelProvider: indica cosa deve essere mostrato per ogni oggetto del modello che viene visualizzato. In particolare il metodo getText() riceve l’oggetto da rappresentare e deve occuparsi di ritornare il testo da mostrare in corrispondenza di tale oggetto. Opzionalmente è possibile utilizzare anche il metodo getImage() per fornire una immagine corrispondente. Nel nostro esempio ecco un possibile utilizzo

  ...  
 import org.eclipse.jface.viewers.LabelProvider;

 public class MyTreeLabelProvider extends LabelProvider {

   @Override
   public String getText(Object element) {
     if (element instanceof Company) {
       return ((Company) element).getName();
     } else
     if (element instanceof Department) {
       return ((Department) element).getDescription();
     } else
     if (element instanceof Employee) {
       return ((Employee) element).getEmployeeName();
     }
     return null;
   }
 }
  • ContentProvider: indica come determinare i contenuti del tree. In pratica fornisce l’implementazione dei metodi che vengono chiamati:
    1. quando l’albero viene mostrato inizialmente (con i nodi di primo livello non espansi) (getElements)
    2. quando occorre decidere se mostrare o meno il simbolo di espansione del nodo esaminato (hasChildren)
    3. quando l’utente decide di espandere un determinato nodo e occorre mostrare i nodi figli (getChildren)

Si noti che hasChildren, ma soprattutto getChildren, vengono invocati in modo lazy, cioè solamente quando è strettamente indispensabile. Quindi la lista di Employee di un determinato Department viene caricata solamente quando l’utente decide di espandere il nodo relativo a quel Department, non prima, con evidente vantaggio in termini di prestazioni.

 ...  
 import org.eclipse.jface.viewers.ITreeContentProvider;

 public class MyTreeContentProvider implements ITreeContentProvider {

     private static final Object[] EMPTY_ARRAY = new Object[0];

     //Utilizzato solo per gli elementi di primo livello che arrivano dalla setInput del Viewer.
     //In questo caso si prevede di alimentare il viewer con una lista di oggetti
     @Override
     public Object[] getElements(Object inputElement) {
       if (inputElement instanceof List)
         return ((List) inputElement).toArray();
       else
         return EMPTY_ARRAY;
     }

     //Utilizzato per determinare se un nodo generico è espandibile
     @Override
     public boolean hasChildren(Object element) {
       if (element instanceof Company || element instanceof Department) {
         return true;
       }
       return false;
     }

     //Utilizzato ogni volta che si espande un nodo generico per determinarne i figli
     @Override
     public Object[] getChildren(Object parentElement) {
       if (parentElement instanceof Company) {
         Company company = (Company) parentElement;
         return company.getListDepartment().toArray();
       } else
       if (parentElement instanceof Department) {
         Department department = (Department) parentElement;
         return department.getListEmployee().toArray();
       }
       return EMPTY_ARRAY;
     }

     @Override
     public void dispose() {
     }

     @Override
     public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
     }

     @Override
     public Object getParent(Object element) {
       return null;
     }

   }
 

L’albero visualizza così tutti e tre i livelli di oggetti (Company, Deparment, Employee)

 

Nota: nel caso il viewer venga alimentato con un oggetto di tipo Company invece che con una lista, il metodo getElements() dovrà conseguentemente essere modificato così:

   if (inputElement instanceof Company)
       return ((Company) inputElement).getListDepartment().toArray();

In questo caso il tree non visualizzerà l’oggetto Company, bensì direttamente i suoi figli Department al primo livello e Employee al secondo.