2013-01-20 14 views
7

Ho un JTable che può avere le righe aggiunte dinamicamente dall'utente. Si trova in un JScrollPane, così come il numero di righe diventa abbastanza grande, lo scroller diventa attivo. Il mio desiderio è che quando l'utente aggiunge una nuova riga, lo scroller si sposta completamente verso il basso, in modo che la nuova riga sia visibile nello scorrimento. Sono al momento (SSCCE sotto) cercando di utilizzare un listener di modelli di tabelle per rilevare quando viene inserita la riga e forzare la barra di scorrimento fino in fondo quando viene effettuato il rilevamento. Tuttavia, sembra che questa rilevazione sia "troppo presto", poiché il modello è stato aggiornato ma la nuova riga non è ancora stata dipinta, quindi ciò che accade è lo scroller che si sposta fino in fondo solo prima dello la nuova riga è inserita , e quindi la nuova riga viene inserita appena sotto la fine del riquadro (non visibile).Il listener del modello JTable rileva le righe inserite troppo presto (prima che vengano disegnate)

Ovviamente questo approccio è sbagliato in qualche modo. Qual è l'approccio corretto?

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

EDIT: Aggiornato SSCCE di seguito in base alla richiesta di trashgod. Questa versione continua a non funzionare, tuttavia, se sposto la logica di scorrimento dal listener del modello di tabella al listener di pulsanti come ha fatto, allora funziona!

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

risposta

7

Questo example utilizza scrollRectToVisible() a (condizionale) di scorrimento per l'ultimo rettangolo di cella. Come caratteristica puoi cliccare sul pollice per sospendere lo scorrimento e rilasciare per riprendere.

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

Addendum: ho cercatoscrollRectToVisiblenel mio SSCCE, e presenta ancora lo stesso problema.

Questo Action fornisce sia il mouse e la tastiera di controllo:

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

Addendum: Ecco una variazione sul vostro esempio che illustra una strategia di layout rivista.

/** @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

Grazie, ma non sembra per risolvere direttamente il problema, che mi fa chiedere se è impossibile? Nel tuo esempio stai controllando costantemente una volta al secondo se devi o meno scorrere fino alla fine.Tuttavia, voglio che ciò accada non appena si fa clic su "aggiungi riga". Se faccio clic sul pulsante di un gruppo nella tua app, subisce lo stesso problema che fa il mio (ma "risolve" se stesso in meno di 1 secondo). E 'impossibile che lo scrolling avvenga non appena l'utente fa clic sul pulsante, perché il modello e la GUI sono su thread separati. Sto indovinando? – The111

+0

Dovrei aggiungere che ho provato 'scrollRectToVisible' nel mio SSCCE e presenta ancora lo stesso problema, che è forse più rilevante di quello che ho appena scritto sopra. – The111

+0

Ho elaborato sopra; per favore aggiorna la tua domanda con il tuo codice attuale. – trashgod

1

Tuttavia, sembra che questo rilevamento è "troppo presto",

Per enfasi (@trashgod già accennato in un commento): questo è vero - e previsto e un problema abbastanza generale in Swing :-)

La tabella - e qualsiasi altra vista con qualsiasi modello, non solo dati ma anche selezione, regolazione, ... - sta ascoltando il modello per aggiornarsi. Quindi un listener personalizzato è solo l'ennesimo listener nella riga (con sequenza di pubblicazione indefinita). Se vuole fare qualcosa che dipende dallo stato di visualizzazione deve assicurarsi di farlo dopo tutti gli aggiornamenti interni sono pronti (in questo contesto concreto, l'aggiornamento interno include l'aggiornamento del adjustmentModel dello scrollbar verticale) Posticipare il trattamento personalizzato fino a quando le parti interne sono fatte, si ottiene avvolgendo SwingUtilities.invokeLater:

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

Grazie per la risposta. Penso che potrebbe aver aiutato a capire qualcosa che non ho ricevuto quando @trashgod l'ha detto. Ha menzionato di aver avvolto la mia chiamata di scroll in un 'invokeLater' come hai fatto tu, e ho pensato che non avrebbe ottenuto nulla dal momento che il mio intero programma era già in tale wrapper. Ho notato ora che il wrapper crea un ** nuovo ** 'Runnable', che credo sia quello che realizza il" sequencing "di cui stava parlando, garantendo che questo nuovo runnable non funzioni fino alla riga che segue nell'altro eseguibile è completo. È corretto? – The111

+1

@ The111 esattamente :-) – kleopatra