2015-05-26 7 views
7

Ich versuche, einen Zeilenfilter für eine JTable zu erstellen, um die Anzahl der in der Tabelle angezeigten Zeilen zu begrenzen.Wie funktioniert JTable RowFilter?

Der RowFilter-Code ist einfach. Er wandelt die Modellreihennummer zu der Ansicht Zeilennummer (falls die Tabelle sortiert wird), und dann prüft, ob die Ansicht Zeilennummer kleiner ist, dass die Anzahl der Zeilen in der Tabelle angezeigt werden:

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

}; 

Das Problem

ist, dass die Modellreihennummer nicht immer in eine vernünftige Ansichtszeilennummer umgewandelt wird, so dass zu viele Zeilen in dem Filter enthalten sind. Um zu demonstrieren, führen Sie den Code unten:

1) Wählen Sie „1“ aus dem Kombinationsfeld ein und Sie erhalten eine Ausgabe wie:

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

Dieser Ausgang sagt mir, dass alle Modellreihen umgewandelt werden Zeile anzuzeigen 0. Da 0 kleiner als der Filterwert von 1 ist, sind alle Zeilen im Filter enthalten (was falsch ist).

Also die Frage ist, warum funktioniert die convertRowIndexToView(modelRow) nicht wie erwartet?

2) Wählen Sie nun „2“ aus dem Kombinationsfeld, und Sie werden eine Ausgabe wie erhalten:

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

Wie Sie die Modellreihen werden nun auf die richtige Ansicht Reihe, so dass nur 2 row Abbildung zu sehen sind im richtigen Filter enthalten.

3) Wählen Sie nun „3“ aus der Box Combo und Sie werden eine Ausgabe wie erhalten:

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

In diesem Fall sind die letzten drei Modellreihen auf -1 umgewandelt werden, die ich übernehmen bedeutet, dass die Zeile momentan nicht sichtbar in der Tabelle, was korrekt ist. Also in diesem Fall sind alle 5 Zeilen wieder im Filter enthalten, was falsch ist, da wir nur die ersten 3 wollen.

Also ist die Frage hier, wie man den Filter zurücksetzt, so dass alle Modellreihen der ursprünglichen Ansichtsreihe zugeordnet werden?

Ich habe versucht, zu verwenden:

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

den Filter zu löschen, bevor Sie den Filter zurückzusetzen, aber das ergab die gleichen Ergebnisse wie in Schritt 1. Das ist alles, jetzt die Modellreihen Zeile 0 sehen Karte (so alle 5 Zeilen werden noch angezeigt). Hier

ist der Testcode:

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

Jede Idee, wie ein Zeilenfilter erstellen, um die ersten „n“ Zeilen angezeigt werden?

Oh ja, noch ein frustrierender Punkt. Wenn Sie die beiden Zeilen System.out .. in der actionPeformed() -Methode auskommentieren, werden Sie bei der Auswahl von 1 aus dem Kombinationsfeld feststellen, dass in beiden Fällen der Modellindex 4 in Ansichtsindex 4 konvertiert wird und diese beiden Ausgaben umschlossen sind das falsche Modell zum Anzeigen von Conversions?

Edit:

Basierend auf MadProgrammers Vorschlag habe ich versucht:

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

Jetzt nichts in der Tabelle I erhalten.

+0

„Vermutung“ ist, dass es „einige Male“ die Arbeit mit den zuvor eingestellten gefilterten Daten. .. – MadProgrammer

+0

Ich habe am Ende einen neuen 'TableRowSorter' erstellt, den auf' JTable' angewendet, den 'rowFilter' auf diesen Sortierer gesetzt und dann' sort' auf dem 'TableRowSorter' aufgerufen ...: P – MadProgrammer

+0

@MadProgrammer,'. ..vorher gefilterter Datensatz ja und ich denke die Frage ist warum? Ich schätze, die meisten Filter basieren auf den tatsächlichen Daten im TableModel, also ist das kein Problem, da das Mapping der Indizes nach der Filterung noch einmal durchgeführt wird. 'Ich endete am Erstellen ...' - hat nicht für mich gearbeitet. Schätze, ich habe etwas anderes falsch gemacht? – camickr

Antwort

3

Mit den Tipps von @MadProgrammer kam ich zu der folgenden Lösung.

Nicht nur, dass die RowSorter ersetzt werden müssen, müssen Sie auch die Sortierschlüssel zu halten, damit die Methode sort() die Tabelle wieder in seinen aktuellen Sortierzustand zurücksetzt:

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

Ich bevorzuge deine Lösung über meine "gehackt, hack" ... – MadProgrammer

+0

danke dafür, sehr interessant, [bis ich mir das noch sicher war da muss man nie etwas zurücksetzen (http://stackoverflow.com/a/7132990/714968) weil diese logik zusammen mit filter und sorter funktioniert (beginnend mit filterung, dann wird der sorter verwendet) – mKorbel

+0

@MadProgrammer, selbst diese lösung hat ein potenzielles Problem, abhängig von der genauen Anforderung. Wenn Sie zwei Zeilen (1, 2) anzeigen und dann die Sortierung in absteigend ändern, sehen Sie nun (2, 1). In Wirklichkeit würden Sie wahrscheinlich (5, 4) sehen wollen, was bedeutet, dass wir sogar bei einer Änderung der Sortierreihenfolge den RowSorter und den Filter zurücksetzen müssen. Immer komplizierter werden. – camickr

5

So, nach einigen schweren Testen und Debuggen, kopiert ich den Code für DefaultRowSorter und TableRowSorter in Ihre FilterSSCCE und fügte etwas Ausgang Überwachung des modelToView Feld, das durch DefaultRowSorter#convertRowIndexToView verwendet wird zwischen dem Modell und Ansicht indicies zur Karte ...

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] 

Der interessante Teil ist hier am Ende, zwischen Change the Filter to: 1 und Change the Filter to: 2. Sie können sehen, dass initializeFilteredMapping die Modellindizes, die außerhalb des Bereichs liegen, auf -1 festgelegt hat, aber wenn wir zu Change the Filter to: 2 wechseln, werden dieselben Indizes immer noch gesetzt, und der Filter hat sie NICHT zurückgesetzt.

Dies scheint eine Design-Wahl zu sein, die Tabelle reaktionsfähig zu halten und sie haben wahrscheinlich nie gedacht, dass jemand versuchen könnte, die Ansicht aus dem Filter heraus aufzurufen, da Sie die Modelldaten verwenden sollen.

Wie komme ich dazu ...?

Sie könnten einen "Proxy" TableModel erstellen, aber das schließt die Möglichkeit aus, dass die Tabelle möglicherweise sortiert wird.

Sie könnten ein „Proxy“ TableModel schreiben, die über den sortierten Zustand der JTable „wussten“ (wahrscheinlich über die RowSorter) und die als Filter wirken könnte die sichtbare Zeilenanzahl zu bestimmen, aber diese überquert in trübes Wasser wie das Modell in die Welt der Ansicht wagen wird gestartet ...

Eine andere Wahl, dass die setFilter Werke Verfahren und die modelToView und viewToModel Variablen zurückgesetzt wäre es, die Art und Weise zu ändern, aber sie sind private, wie sie sein sollten OK, wir könnten die createModelToView, createViewToModel und setModelToViewFromViewToModel Methoden verwenden, die in.75572698786782018123 verfügbar sind 210 ... aber sie sind private zu ...

Es scheint fast jede nützliche Methode, die mit ernsthaften Änderungen an diesen Variablen handelt, sind private ... Geschichte meines Lebens ... (Holen Sie sich Ihre Fackeln und Mistgabeln werden wir auf einer Entwickler-Jagd)

nächste Wahl, schreibe alles selbst ... was für eine wunderbare Idee, erwartet, dass gegen die Grundprinzipien der OO geht ...

A „Arbeit um "(und ich benutze den Begriff sehr, sehr leicht), wäre es, Reflexion zu verwenden und einfach die Methoden zu nennen, die wir brauchen ...

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

} 

Nun, ich bin mir ziemlich sicher, dass Sie wissen, wie ich, das ist eine schreckliche, schreckliche Idee, die jederzeit brechen könnte. Es ist wahrscheinlich auch sehr, sehr ineffizient, da Sie die Indizes jedes Mal auf die bidirektionale Suche zurücksetzen.

Also, die Antwort, nicht auf die Ansicht vom Filter zugreifen.

Generell neige ich dazu, die RowSorter zu ersetzen, wann immer ich die RowFilter ersetzen, da es diese Art von Fragen vermeidet: P

+1

Danke für die vollständige Analyse (+1). 'Greifen Sie nicht auf die Ansicht vom Filter zu '- Scheint wie eine einfache Anforderung, die Anzahl der angezeigten Zeilen in der Ansicht zu beschränken. Aber wenn Sie darüber nachdenken, ist es wirklich wichtig, ob alle Zeilen angezeigt werden? Vielleicht wäre eine einfachere Lösung, den Hintergrund der ersten "n" Zeilen anders hervorzuheben? – camickr