2015-05-26 20 views
7

Sto provando a creare un filtro Row per un JTable per limitare il numero di righe visualizzate nella tabella.Come funziona JTable RowFilter?

Il codice RowFilter è semplice. Si converte il numero di modello fila al numero fila vista (nel caso la tabella è ordinata) e quindi controlla se il numero di vista fila è meno che il numero di linee da visualizzare nella tabella:

RowFilter<TableModel, Integer> filter = new RowFilter<TableModel, Integer>() 
{ 
    @Override 
    public boolean include(RowFilter.Entry<? extends TableModel, ? extends Integer> entry) 
    { 
     int modelRow = entry.getIdentifier(); 
     int viewRow = table.convertRowIndexToView(modelRow); 

     return viewRow < numberOfRows; 
    } 

}; 

Il problema è che il numero di riga del modello non viene sempre convertito in un numero di riga di vista ragionevole in modo che troppe righe vengano incluse nel filtro. Per dimostrare eseguire il codice qui sotto:

1) Selezionare "1" dalla casella combinata e si otterrà un output simile:

Change the Filter to: 1 
m0 : v0 
m1 : v0 
m2 : v0 
m3 : v0 
m4 : v0 

Questa uscita mi sta dicendo che tutte le righe del modello vengono convertiti per visualizzare fila 0. Poiché 0 è inferiore al valore del filtro 1, tutte le righe sono incluse nel filtro (che è errato).

Quindi la domanda qui è perché lo convertRowIndexToView(modelRow) non funziona come previsto?

2) Ora selezionate "2" dalla casella combinata e si otterrà un output simile:

Change the Filter to: 2 
m0 : v0 
m1 : v1 
m2 : v2 
m3 : v3 
m4 : v4 

Come si può vedere le righe del modello sono ora mappatura per la corretta fila di vista, in modo che solo 2 di fila sono incluso nel filtro che è corretto.

3) Ora selezionate "3" dalla casella combinata e si otterrà un output simile:

Change the Filter to: 3 
m0 : v0 
m1 : v1 
m2 : v-1 
m3 : v-1 
m4 : v-1 

In questo caso le ultime 3 file modello vengono convertiti in -1, che presumo significa che la riga è non attualmente visibile nella tabella, che è corretto. Quindi in questo caso tutte le 5 righe sono di nuovo incluse nel filtro che non è corretto poiché vogliamo solo il primo 3.

Quindi la domanda qui è come reimpostare il filtro in modo che tutte le righe del modello siano mappate alla riga di visualizzazione originale?

Ho cercato di utilizzare:

((TableRowSorter) table.getRowSorter()).setRowFilter(null); 
((TableRowSorter) table.getRowSorter()).setRowFilter(filter); 

per cancellare il filtro, prima di ripristinare il filtro, ma questo ha dato gli stessi risultati come passaggio 1. Cioè, adesso tutti righe modello mappa per visualizzare riga 0 (così tutte e 5 le righe sono ancora visualizzate).

Ecco il codice di prova:

import java.awt.*; 
import java.awt.event.*; 
import javax.swing.*; 
import javax.swing.table.*; 

public class FilterSSCCE extends JPanel 
{ 
    private JTable table; 

    public FilterSSCCE() 
    { 
     setLayout(new BorderLayout()); 

     JComboBox<Integer> comboBox = new JComboBox<Integer>(); 
     comboBox.addItem(new Integer(1)); 
     comboBox.addItem(new Integer(2)); 
     comboBox.addItem(new Integer(3)); 
     comboBox.addItem(new Integer(4)); 
     comboBox.addItem(new Integer(5)); 
     comboBox.setSelectedIndex(4); 

     comboBox.addActionListener(new ActionListener() 
     { 
      @Override 
      public void actionPerformed(ActionEvent e) 
      { 
       //System.out.println(table.convertRowIndexToView(4)); 
       Integer value = (Integer)comboBox.getSelectedItem(); 
       newFilter(value); 
       //System.out.println(table.convertRowIndexToView(4)); 
      } 
     }); 
     add(comboBox, BorderLayout.NORTH); 

     table = new JTable(5, 1); 

     for (int i = 0; i < table.getRowCount(); i++) 
      table.setValueAt(String.valueOf(i+1), i, 0); 

     table.setAutoCreateRowSorter(true); 
     JScrollPane scrollPane = new JScrollPane(table); 
     add(scrollPane, BorderLayout.CENTER); 
    } 

    private void newFilter(int numberOfRows) 
    { 
     System.out.println("Change the Filter to: " + numberOfRows); 

     RowFilter<TableModel, Integer> filter = new RowFilter<TableModel, Integer>() 
     { 
      @Override 
      public boolean include(RowFilter.Entry<? extends TableModel, ? extends Integer> entry) 
      { 
       int modelRow = entry.getIdentifier(); 
       int viewRow = table.convertRowIndexToView(modelRow); 

       System.out.println("m" + modelRow + " : v" + viewRow); 

       return viewRow < numberOfRows; 
      } 

     }; 

     ((TableRowSorter) table.getRowSorter()).setRowFilter(filter); 
    } 

    private static void createAndShowGUI() 
    { 
     JPanel panel = new JPanel(); 

     JFrame frame = new JFrame("FilterSSCCE"); 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     frame.add(new FilterSSCCE()); 
     frame.setLocationByPlatform(true); 
     frame.pack(); 
     frame.setVisible(true); 
    } 

    public static void main(String[] args) 
    { 
     EventQueue.invokeLater(new Runnable() 
     { 
      public void run() 
      { 
       createAndShowGUI(); 
      } 
     }); 
    } 
} 

Qualsiasi idea di come creare un filtro di riga per visualizzare le prime file "n"?

Oh sì, un altro punto frustrante. Se rimuovi il commento dalle due righe System.out .. nel metodo actionPeformed(), quando selezioni 1 dalla casella combinata noterai che in entrambi i casi l'indice del modello 4 viene convertito per visualizzare l'indice 4 e queste due uscite sono racchiuse tra loro il modello errato per visualizzare le conversioni ???

Edit:

Sulla base di MadProgrammers suggerimento che ho provato:

//((TableRowSorter) table.getRowSorter()).setRowFilter(filter); 
TableRowSorter sorter = new TableRowSorter(); 
table.setRowSorter(sorter); 
sorter.setRowFilter(filter); 
sorter.sort(); 

Ora ho nulla nella tabella.

+0

"Guess" è che si tratta di "alcune volte" che lavorano con i dati precedentemente filtrati set. .. – MadProgrammer

+0

Ho finito per creare un nuovo 'TableRowSorter', applicandolo a' JTable' impostando 'rowFilter' su quel sorter e poi chiamando' sort' su 'TableRowSorter' ...: P – MadProgrammer

+0

@MadProgrammer,'. ... set di dati precedentemente filtrati sì e immagino che la domanda sia: perché? Immagino che la maggior parte dei filtri si basino sui dati effettivi nel TableModel, quindi questo non è un problema dato che la mappatura degli indici sarà rifatta dopo il filtro? 'Ho finito per creare ...' - non ha funzionato per me. Suppongo che ho fatto qualcos'altro di sbagliato? – camickr

risposta

3

Utilizzando i suggerimenti di @MadProgrammer, ho trovato la seguente soluzione.

Non solo ha bisogno il RowSorter di essere sostituito, è necessario anche per mantenere le chiavi di ordinamento in modo che il metodo sort() ripristina la tabella indietro al suo attuale stato di sorta:

import java.awt.*; 
import java.awt.event.*; 
import javax.swing.*; 
import javax.swing.table.*; 

public class FilterSSCCE extends JPanel 
{ 
    private JTable table; 

    public FilterSSCCE() 
    { 
     setLayout(new BorderLayout()); 

     JComboBox<Integer> comboBox = new JComboBox<Integer>(); 
     comboBox.addItem(new Integer(1)); 
     comboBox.addItem(new Integer(2)); 
     comboBox.addItem(new Integer(3)); 
     comboBox.addItem(new Integer(4)); 
     comboBox.addItem(new Integer(5)); 
     comboBox.setSelectedIndex(4); 

     comboBox.addActionListener(new ActionListener() 
     { 
      @Override 
      public void actionPerformed(ActionEvent e) 
      { 
       Integer value = (Integer)comboBox.getSelectedItem(); 
       newFilter(value); 
      } 
     }); 
     add(comboBox, BorderLayout.NORTH); 

     table = new JTable(5, 1); 

     for (int i = 0; i < table.getRowCount(); i++) 
      table.setValueAt(String.valueOf(i+1), i, 0); 

     table.setAutoCreateRowSorter(true); 
     JScrollPane scrollPane = new JScrollPane(table); 
     add(scrollPane, BorderLayout.CENTER); 
    } 

    private void newFilter(int numberOfRows) 
    { 
     RowFilter<TableModel, Integer> filter = new RowFilter<TableModel, Integer>() 
     { 
      @Override 
      public boolean include(RowFilter.Entry<? extends TableModel, ? extends Integer> entry) 
      { 
       int modelRow = entry.getIdentifier(); 
       int viewRow = table.convertRowIndexToView(modelRow); 

       return viewRow < numberOfRows; 
      } 

     }; 

     TableRowSorter oldSorter = (TableRowSorter)table.getRowSorter(); 
     TableRowSorter<TableModel> sorter = new TableRowSorter<TableModel>(table.getModel()); 
     table.setRowSorter(sorter); 
     sorter.setRowFilter(filter); 
     sorter.setSortKeys(oldSorter.getSortKeys()); 
     sorter.sort(); 
    } 

    private static void createAndShowGUI() 
    { 
     JPanel panel = new JPanel(); 

     JFrame frame = new JFrame("FilterSSCCE"); 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     frame.add(new FilterSSCCE()); 
     frame.setLocationByPlatform(true); 
     frame.pack(); 
     frame.setVisible(true); 
    } 

    public static void main(String[] args) 
    { 
     EventQueue.invokeLater(new Runnable() 
     { 
      public void run() 
      { 
       createAndShowGUI(); 
      } 
     }); 
    } 
} 
+0

Preferisco la tua soluzione al mio "hacked, hack" ... – MadProgrammer

+0

grazie per questo, molto interessante, [fino a quando ero sicuro che non c'è mai bisogno di resettare qualcosa] (http://stackoverflow.com/a/7132990/714968) perché questa logica funziona con filtro e ordinatore insieme (iniziando con il filtraggio, quindi viene utilizzato il sorter) – mKorbel

+0

@MadProgrammer, anche questa soluzione ha un potenziale problema a seconda dell'esigenza esatta. Se visualizzi due righe (1, 2) e poi cambi l'ordinamento in discendente, vedi ora (2, 1). In realtà probabilmente vorrai vedere (5, 4), il che significa che anche in caso di un cambio di ordinamento dovremmo reimpostare RowSorter e filtrare. Diventando sempre più complicato – camickr

5

Così, dopo alcuni gravi test e debug, ho copiato il codice per DefaultRowSorter e TableRowSorter nella vostra FilterSSCCE e aggiunto qualche uscita monitoraggio del campo modelToView, che viene utilizzato da DefaultRowSorter#convertRowIndexToView per mappare tra il modello e visualizzare indicies ...

Change the Filter to: 1 
createModelToView = [0, 0, 0, 0, 0] 
m0 : v0 
m1 : v0 
m2 : v0 
m3 : v0 
m4 : v0 
initializeFilteredMapping.1 = [0, 1, 2, 3, 4] 
initializeFilteredMapping.2 = [0, 1, 2, 3, 4] 
Change the Filter to: 5 
m0 : v0 
m1 : v1 
m2 : v2 
m3 : v3 
m4 : v4 
initializeFilteredMapping.1 = [0, 1, 2, 3, 4] 
initializeFilteredMapping.2 = [0, 1, 2, 3, 4] 
Change the Filter to: 1 
m0 : v0 
m1 : v1 
m2 : v2 
m3 : v3 
m4 : v4 
initializeFilteredMapping.1 = [0, -1, -1, -1, -1] 
initializeFilteredMapping.2 = [0, -1, -1, -1, -1] 
Change the Filter to: 2 
m0 : v0 
m1 : v-1 
m2 : v-1 
m3 : v-1 
m4 : v-1 
initializeFilteredMapping.1 = [0, 1, 2, 3, 4] 
initializeFilteredMapping.2 = [0, 1, 2, 3, 4] 

La parte interessante è qui alla fine, tra Change the Filter to: 1 e Change the Filter to: 2. È possibile vedere che initializeFilteredMapping ha impostato gli indici del modello che sono fuori intervallo su -1, ma quando si passa a Change the Filter to: 2, quegli stessi indici sono ancora impostati, la modifica del filtro NON li ha ripristinati.

Questo sembra essere una scelta di progettazione per mantenere la tabella reattivo e probabilmente mai pensato qualcuno potrebbe cercare di accedere alla vista dal di dentro il filtro, come si sta supponiamo di utilizzare i dati del modello ...

Come aggirarlo ...?

È possibile creare un "proxy" TableModel, ma questo preclude la possibilità che la tabella possa essere ordinata.

si potrebbe scrivere un "proxy" TableModel che "sapeva" sullo stato ordinato della JTable (probabilmente attraverso la RowSorter) e che potrebbero fungere da filtro per determinare il conteggio delle righe visibili, ma questo fa un cross nell'area di acqua torbida come il modello sta cominciando ad avventurarsi nel mondo della vista ...

Un'altra scelta sarebbe quello di cambiare il modo in cui il metodo setFilter opere e ripristinare le variabili modelToView e viewToModel, ma sono private, come dovrebbero essere , okay, potremmo utilizzare i metodi createModelToView, createViewToModel e setModelToViewFromViewToModel disponibili nello DefaultRowSorter ... ma sono ...

Sembrerebbe quasi un metodo utile che riguarda le modifiche serie di queste variabili sono private ... storia della mia vita ... (prendi le tue torce e forconi , stiamo andando su un dev-caccia)

scelta successiva, scrivere tutto da soli ... che splendida idea, si aspettano che va contro i principi fondamentali della OO ...

a "lavoro in giro "(e uso il termine molto, molto leggermente), sarebbe usare la riflessione e chiamare solo i metodi di cui abbiamo bisogno ...

public class TestRowSorter<M extends TableModel> extends TableRowSorter<M> { 

    public TestRowSorter() { 
    } 

    public TestRowSorter(M model) { 
     super(model); 
    } 

    public Method findMethod(String name, Class... lstTypes) { 

     return findMethod(getClass(), name, lstTypes); 

    } 

    public Method findMethod(Class parent, String name, Class... lstTypes) { 

     Method method = null; 
     try { 
      method = parent.getDeclaredMethod(name, lstTypes); 
     } catch (NoSuchMethodException noSuchMethodException) { 
      try { 
       method = parent.getMethod(name, lstTypes); 
      } catch (NoSuchMethodException nsm) { 
       if (parent.getSuperclass() != null) { 
        method = findMethod(parent.getSuperclass(), name, lstTypes); 
       } 
      } 
     } 

     return method; 

    } 

    @Override 
    public void setRowFilter(RowFilter<? super M, ? super Integer> filter) { 

     try { 
      Method method = findMethod("createModelToView", int.class); 
      method.setAccessible(true); 
      method.invoke(this, getModelWrapper().getRowCount()); 

      method = findMethod("createViewToModel", int.class); 
      method.setAccessible(true); 
      method.invoke(this, getModelWrapper().getRowCount()); 

      method = findMethod("setModelToViewFromViewToModel", boolean.class); 
      method.setAccessible(true); 
      method.invoke(this, true); 
     } catch (SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException exp) { 
      exp.printStackTrace(); 
     } 

     super.setRowFilter(filter); 
    } 

} 

Ora, io sono abbastanza sicuro di sapere, come faccio io, questo è un orribile, orribile idea che potrebbe rompersi in qualsiasi momento. Probabilmente è anche molto, molto inefficiente, dal momento che stai reimpostando gli indici al look bidirezionale ogni volta.

Quindi, la risposta, non accedere alla vista dal filtro.

In generale, tendo a sostituire il RowSorter quando mai si sostituisce la RowFilter in quanto evita questo tipo di problemi: P

+1

Grazie per l'analisi completa (+1). 'Non accedere alla vista dal filtro' - Sembrava un semplice requisito limitare il numero di righe visualizzate nella vista. Ma se ci pensi, importa davvero se vengono visualizzate tutte le righe? Forse una soluzione più semplice sarebbe quella di evidenziare lo sfondo delle prime "n" righe in modo diverso? – camickr