2013-01-20 5 views
7

Ich habe eine JTable, die Zeilen dynamisch vom Benutzer hinzugefügt haben können. Es sitzt in einem JScrollPane, so wie die Anzahl der Zeilen groß genug wird, wird der Scroller aktiv. Mein Wunsch ist, dass wenn der Benutzer eine neue Zeile hinzufügt, der Scroller den ganzen Weg nach unten bewegt, so dass die neue Zeile in der Scroll-Palette sichtbar ist. Ich versuche derzeit (SSCCE), einen Listener für Tabellenmodelle zu verwenden, um zu erkennen, wann die Zeile eingefügt wurde, und zwinge die Bildlaufleiste ganz nach unten, wenn die Erkennung durchgeführt wird. Es scheint jedoch, dass diese Erkennung "zu früh" ist, da das Modell aktualisiert wurde, aber die neue Zeile wurde noch nicht gemalt, also was passiert, ist der Scroller bewegt sich ganz nach unten vor die neue Zeile eingefügt wird und dann wird die neue Zeile direkt unter dem Ende des Bereichs eingefügt (außerhalb der Sichtbarkeit).JTable Modell Listener erkennt eingefügte Zeilen zu früh (bevor sie gezeichnet werden)

Offensichtlich ist dieser Ansatz irgendwie falsch. Was ist der richtige Ansatz?

import java.awt.Dimension; 
import java.awt.EventQueue; 
import java.awt.event.MouseAdapter; 
import java.awt.event.MouseEvent; 

import javax.swing.BoxLayout; 
import javax.swing.JButton; 
import javax.swing.JFrame; 
import javax.swing.JScrollBar; 
import javax.swing.JScrollPane; 
import javax.swing.JSplitPane; 
import javax.swing.JTable; 
import javax.swing.event.TableModelEvent; 
import javax.swing.event.TableModelListener; 
import javax.swing.table.DefaultTableModel; 

public class TableListenerTest { 

    private JFrame frame; 
    private JScrollPane scrollPane; 
    private JTable table; 
    private DefaultTableModel tableModel; 

    public static void main(String[] args) { 
     EventQueue.invokeLater(new Runnable() { 
      public void run() { 
       try { 
        TableListenerTest window = new TableListenerTest(); 
        window.frame.setVisible(true); 
       } catch (Exception e) { 
        e.printStackTrace(); 
       } 
      } 
     }); 
    } 

    public TableListenerTest() { 
     initialize(); 
    } 

    private void initialize() { 
     frame = new JFrame(); 
     frame.setBounds(100, 100, 450, 200); 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     frame.getContentPane().setLayout(new BoxLayout(frame.getContentPane(), BoxLayout.Y_AXIS)); 

     JSplitPane splitPane = new JSplitPane(); 
     frame.getContentPane().add(splitPane); 

     scrollPane = new JScrollPane(); 
     scrollPane.setPreferredSize(new Dimension(100, 2)); 
     splitPane.setLeftComponent(scrollPane); 

     tableModel = new DefaultTableModel(new Object[]{"Stuff"},0); 
     table = new JTable(tableModel); 
     scrollPane.setViewportView(table); 
     table.getModel().addTableModelListener(new TableModelListener() { 
      public void tableChanged(TableModelEvent e) { 
       if (e.getType() == TableModelEvent.INSERT) { 
        JScrollBar scrollBar = scrollPane.getVerticalScrollBar(); 
        scrollBar.setValue(scrollBar.getMaximum()); 
       } 
      } 
     }); 

     JButton btnAddRow = new JButton("Add Row"); 
     btnAddRow.addMouseListener(new MouseAdapter() { 
      @Override 
      public void mouseClicked(MouseEvent e) { 
       tableModel.addRow(new Object[]{"new row"}); 
      } 
     }); 
     splitPane.setRightComponent(btnAddRow); 
    } 
} 

BEARBEITEN: Aktualisiert SSCCE unten basierend auf der Anfrage von trashgod. Diese Version funktioniert jedoch immer noch nicht, wenn ich die Bildlauflogik vom Tabellenmodell-Listener zum Button-Listener verschiebe, dann funktioniert es!

import java.awt.Dimension; 
import java.awt.EventQueue; 
import java.awt.Rectangle; 
import java.awt.event.MouseAdapter; 
import java.awt.event.MouseEvent; 

import javax.swing.BoxLayout; 
import javax.swing.JButton; 
import javax.swing.JFrame; 
import javax.swing.JScrollPane; 
import javax.swing.JSplitPane; 
import javax.swing.JTable; 
import javax.swing.event.TableModelEvent; 
import javax.swing.event.TableModelListener; 
import javax.swing.table.DefaultTableModel; 

    public class TableListenerTest { 

     private JFrame frame; 
     private JScrollPane scrollPane; 
     private JTable table; 
     private DefaultTableModel tableModel; 

     public static void main(String[] args) { 
      EventQueue.invokeLater(new Runnable() { 
       public void run() { 
        try { 
         TableListenerTest window = new TableListenerTest(); 
         window.frame.setVisible(true); 
        } catch (Exception e) { 
         e.printStackTrace(); 
        } 
       } 
      }); 
     } 

     public TableListenerTest() { 
      initialize(); 
     } 

     private void initialize() { 
      frame = new JFrame(); 
      frame.setBounds(100, 100, 450, 200); 
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
      frame.getContentPane().setLayout(new BoxLayout(frame.getContentPane(), BoxLayout.Y_AXIS)); 

      JSplitPane splitPane = new JSplitPane(); 
      frame.getContentPane().add(splitPane); 

      scrollPane = new JScrollPane(); 
      scrollPane.setPreferredSize(new Dimension(100, 2)); 
      splitPane.setLeftComponent(scrollPane); 

      tableModel = new DefaultTableModel(new Object[]{"Stuff"},0); 
      table = new JTable(tableModel); 
      scrollPane.setViewportView(table); 
      table.getModel().addTableModelListener(new TableModelListener() { 
       public void tableChanged(TableModelEvent e) { 
        if (e.getType() == TableModelEvent.INSERT) { 
         int last = table.getModel().getRowCount() - 1; 
         Rectangle r = table.getCellRect(last, 0, true); 
         table.scrollRectToVisible(r); 
        } 
       } 
      }); 

      JButton btnAddRow = new JButton("Add Row"); 
      btnAddRow.addMouseListener(new MouseAdapter() { 
       @Override 
       public void mouseClicked(MouseEvent e) { 
        tableModel.addRow(new Object[]{"new row"}); 
       } 
      }); 
      splitPane.setRightComponent(btnAddRow); 
     } 
    } 

Antwort

7

Diese example verwendet scrollRectToVisible() bis (konditional) Blättern bis zur letzten Zelle Rechtecks. Als eine Funktion können Sie auf den Daumen klicken, um den Bildlauf zu unterbrechen und ihn wieder zu starten.

private void scrollToLast() { 
    if (isAutoScroll) { 
     int last = table.getModel().getRowCount() - 1; 
     Rectangle r = table.getCellRect(last, 0, true); 
     table.scrollRectToVisible(r); 
    } 
} 

Nachtrag: Ich versuchtescrollRectToVisiblein meinem SSCCE, und es zeigt immer noch das gleiche Problem.

Diese Action bietet sowohl Maus- und Tastatursteuerung:

JButton btnAddRow = new JButton(new AbstractAction("Add Row") { 

    @Override 
    public void actionPerformed(ActionEvent e) { 
     tableModel.addRow(new Object[]{"new row"}); 
     int last = table.getModel().getRowCount() - 1; 
     Rectangle r = table.getCellRect(last, 0, true); 
     table.scrollRectToVisible(r); 
    } 
}); 

enter image description here

Nachtrag: Hier ist eine Variation auf Ihrem Beispiel, das eine überarbeitete Layout-Strategie darstellt.

/** @see https://stackoverflow.com/a/14429388/230513 */ 
public class TableListenerTest { 

    private static final int N = 8; 
    private JFrame frame; 
    private JScrollPane scrollPane; 
    private JTable table; 
    private DefaultTableModel tableModel; 

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

      @Override 
      public void run() { 
       TableListenerTest window = new TableListenerTest(); 
       window.frame.setVisible(true); 
      } 
     }); 
    } 

    public TableListenerTest() { 
     initialize(); 
    } 

    private void initialize() { 
     frame = new JFrame(); 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     frame.setLayout(new BoxLayout(frame.getContentPane(), BoxLayout.X_AXIS)); 
     tableModel = new DefaultTableModel(new Object[]{"Stuff"}, 0); 
     for (int i = 0; i < N; i++) { 
      tableModel.addRow(new Object[]{"new row"}); 
     } 
     table = new JTable(tableModel) { 

      @Override 
      public Dimension getPreferredScrollableViewportSize() { 
       return new Dimension(200, table.getRowHeight() * N); 
      } 
     }; 
     scrollPane = new JScrollPane(); 
     scrollPane.setViewportView(table); 
     JButton btnAddRow = new JButton(new AbstractAction("Add Row") { 

      @Override 
      public void actionPerformed(ActionEvent e) { 
       tableModel.addRow(new Object[]{"new row"}); 
       int last = table.getModel().getRowCount() - 1; 
       Rectangle r = table.getCellRect(last, 0, true); 
       table.scrollRectToVisible(r); 
      } 
     }); 
     frame.add(scrollPane); 
     frame.add(btnAddRow); 
     frame.pack(); 
    } 
} 
+0

Danke, aber es scheint nicht direkt mein Problem zu lösen, das macht mich fragen, ob es nicht möglich ist? In Ihrem Beispiel überprüfen Sie ständig einmal pro Sekunde, ob Sie bis zum Ende scrollen sollen oder nicht.Das möchte ich jedoch, sobald "Zeile hinzufügen" angeklickt wird. Wenn ich auf den Knopf einen Haufen in deiner App klicke, erleidet er das gleiche Problem wie mein (aber "repariert" sich selbst in weniger als 1 Sekunde). Ist es unmöglich, das Scrollen zu ermöglichen, sobald der Benutzer auf die Schaltfläche klickt, weil das Modell und die GUI sich auf separaten Threads befinden, die ich vermute? – The111

+0

Ich sollte hinzufügen, dass ich 'scrollRectToVisible' in meinem SSCCE versuchte und es immer noch das gleiche Problem aufweist, das vielleicht relevanter ist als das, was ich gerade oben geschrieben habe. – The111

+0

Ich habe oben ausgearbeitet; Bitte aktualisieren Sie Ihre Frage mit Ihrem aktuellen Code. – trashgod

1

Allerdings scheint es diese Erfassung ist „zu früh“,

Für Betonung (@trashgod bereits erwähnte es in einem Kommentar): das ist wahr - und erwartet und eine ziemlich allgemeine Frage in Swing :-)

Die Tabelle - und jede andere Ansicht mit jedem Modell, nicht nur Daten, sondern auch Auswahl, Anpassung, ... - hört dem Modell zu, sich zu aktualisieren. Ein benutzerdefinierter Listener ist also nur ein weiterer Listener in der Zeile (mit der Serving-Sequenz undefiniert). Wenn es etwas tun will, das von dem Ansichtszustand abhängt, muss es sicherstellen, dass dies getan wird nach alle interne Aktualisierung ist bereit (in diesem konkreten Kontext beinhaltet das interne Update die Aktualisierung des Anpassungsmodells der vertikalen scrollBar) benutzerdefinierte Verarbeitung, bis die Einbauten fertig sind, wird durch Umwickeln SwingUtilities.invokeLater erreicht:

TableModelListener l = new TableModelListener() { 
    @Override 
    public void tableChanged(TableModelEvent e) { 
     if (e.getType() == TableModelEvent.INSERT) { 
      invokeScroll(); 
     } 
    } 

    protected void invokeScroll() { 
     SwingUtilities.invokeLater(new Runnable() { 
      public void run() { 
       int last = table.getModel().getRowCount() - 1; 
       Rectangle r = table.getCellRect(last, 0, true); 
       table.scrollRectToVisible(r); 
      } 
     }); 
    } 
}; 
table.getModel().addTableModelListener(l); 
+0

Danke für die Antwort. Ich denke, es hat mir vielleicht geholfen, etwas zu verstehen, das ich nicht bekommen habe, als @trashgod es gesagt hat. Er erwähnte, wie ich meinen Scroll-Aufruf in einen 'invokeLater' verpackte, und ich dachte, das würde nichts bewirken, da mein gesamtes Programm bereits in einem solchen Wrapper war. Mir ist jetzt aufgefallen, dass der Wrapper ein ** neues ** 'Runnable' erzeugt, was, glaube ich, das" Sequencing "erreicht, von dem er gesprochen hat, und garantiert, dass dieses neue Runnable erst ausgeführt wird, wenn die Zeile in der anderen ausgeführt wird ist komplett. Ist das überhaupt richtig? – The111

+1

@ The111 genau :-) – kleopatra

Verwandte Themen