2011-02-03 40 views
11

Tôi đang cố gắng sử dụng JTable theo cách mà bản ghi dữ liệu mới được thêm vào cuối. Điều kỳ lạ là thanh cuộn không đi đến cuối bảng; thay vào đó, nó luôn hiển thị giây từ lần cuối cùng. Bất kỳ cách nào để cho thanh cuộn luôn đi đến cuối bảng?Cách cuộn đến hàng cuối cùng trong JTable

Dưới đây là một phần của mã của tôi:

table.scrollRectToVisible(table.getCellRect(table.getRowCount()-1, 0, true)); 

Trả lời

16

Tôi chỉ chạy vào vấn đề này - có thực sự là không có gì sai với điều đó dòng mã; vấn đề nằm trong khi bạn thực hiện nó.

Nếu bạn, như tôi, cố gắng để thực hiện nó ngay lập tức sau khi thao tác TableModel (thậm chí qua invokeLater) hoặc bằng cách sử dụng một TableModelListener, bạn sẽ nhận được vấn đề bạn đang mô tả. Vấn đề là trong khi các mô hình đã được cập nhật với các dữ liệu mới (table.getRowCount() chỉ đơn giản là một pass-through cho getRowCount() phương pháp trên TableModel của bạn), các JTable thành phần có thị không phải.

Khi bạn thực hiện điều đó dòng mã ở các vị trí mô tả trước đây, bạn đang thực sự cố gắng để nói với JScrollPane (JTable.scrollRectToVisible trì hoãn bất kỳ hành động để cha mẹ có thể cung cấp hành vi di chuyển, ví dụ như JScrollPane) để cuộn qua phần cuối của thành phần JTable đính kèm. Nó từ chối làm điều đó và thay vào đó, hãy cuộn đến đầu hiện tại của thành phần JTable.

Tại một số thời điểm sau, thành phần JTable tự cập nhật trực quan và thêm hàng mới được thêm vào bên dưới hàng được cuộn đến trước đó. Bạn có thể xác minh rằng dòng mã hoạt động bằng cách thêm nút thực thi mã độc lập với mã bổ sung các hàng mới, ví dụ:

private JTable _table = new JTable(); 
... 
JButton b = new JButton("Force scroll to bottom"); 
b.addActionListener(new ActionListener() { 
    public void actionPerformed(ActionEvent e) { 
     _table.scrollRectToVisible(_table.getCellRect(_table.getRowCount()-1, 0, true)); 
    } 
}); 
this.add(b); 

Giải pháp cho vấn đề này là một chút gián tiếp, nhưng không hoạt động đáng tin cậy trong thử nghiệm của tôi. Do vấn đề nằm ở khía cạnh trực quan của mọi thứ, nên tôi quyết định móc vào ComponentListener thay vào đó, trong số những thứ khác, một phương thức thành phầnRố hoá. Bất cứ khi nào một hàng được thêm vào hoặc bị xóa, thì JTable sẽ đổi kích thước, ngay cả khi nó không thể được nhìn thấy trực quan do chế độ xem JScrollPane's. Vì vậy, chỉ cần chạy dòng mã đó trong phương thức nghe đó, và mọi thứ sẽ hoạt động như mong đợi.

private JTable _table = new JTable(); 
... 
_table.addComponentListener(new ComponentAdapter() { 
    public void componentResized(ComponentEvent e) { 
     _table.scrollRectToVisible(_table.getCellRect(_table.getRowCount()-1, 0, true)); 
    } 
}); 
+0

vui mừng bạn đã tìm thấy một giải pháp :-) Tò mò mặc dù: chưa bao giờ thấy một bối cảnh nơi gói các scrollRectToVisible vào invokeLater đã không làm việc - bạn sẽ xem xét để hiển thị một sscce nơi này đã xảy ra? – kleopatra

+0

thực sự cảm ơn bạn, nó rõ ràng là một thiết kế xấu trong swing, họ nên sửa chữa nó – AvrDragon

+0

@AvrDragon không có gì sai với thiết kế ... nó chỉ là chờ đợi cho đến khi-the-internals thông thường được cập nhật. Điều thường được thực hiện trong một invokeLater, như đã được đề cập trong bình luận cuối cùng của tôi. – kleopatra

1

Nhờ câu trả lời của Sam và trang khác tôi tìm thấy ở nơi khác, tôi đã có thể giải quyết vấn đề này.

Tôi figured tôi chia sẻ giải pháp của tôi để các chàng tiếp theo không phải mảnh tất cả lại với nhau.

Tận hưởng!

import java.awt.Rectangle; 
import java.awt.event.ComponentAdapter; 
import java.awt.event.ComponentEvent; 
import java.security.NoSuchAlgorithmException; 
import java.security.SecureRandom; 
import java.text.SimpleDateFormat; 
import java.util.Date; 

import javax.swing.JFrame; 
import javax.swing.JScrollPane; 
import javax.swing.JTable; 
import javax.swing.JViewport; 
import javax.swing.ScrollPaneConstants; 
import javax.swing.SwingUtilities; 
import javax.swing.table.DefaultTableModel; 

/** 
* Demonstrate displaying a specific cell in a JTable when a row is added. 
* <p> 
* The Table Row Index is displayed in one of the table's columns. 
* <p> 
* The cell containing the Value will be selected for displaying. 
* <p> 
* The specified cell will be made visible and, if possible, positioned in the center of the Viewport. 
* <p> 
* The code works regardless of: 
* <ul> 
* <li>Whether or not the table data is sorted</li> 
* <li>The position/visibility of the "Value" column</li> 
* </ul> 
*/ 
public class JTableScrollToRow 
{ 
    static SecureRandom   random; 
    private DefaultTableModel dtm; 

    static 
    { 
     try 
     { 
      random = SecureRandom.getInstance("SHA1PRNG"); 
      int seed = Integer.parseInt((new SimpleDateFormat("SSS")).format(new Date())); 
      random.setSeed(random.generateSeed(seed)); 
     } 
     catch (NoSuchAlgorithmException e) 
     { 
      e.printStackTrace(); 
     } 
    } 

    public void buildGUI() 
    { 
     Object[][] data = {}; 
     Object colNames[] = { 
       "Value", 
       "TableRowIx", 
       "Column A", 
       "Column B", 
       "Column C", 
       "Column D", 
       "Column E", 
       "Column F" }; 

     dtm = new DefaultTableModel(data, colNames); 
     final JTable sampleTable = new JTable(dtm); 
     sampleTable.setDragEnabled(false); 
     sampleTable.setAutoCreateRowSorter(true); 

     // Turn off auto-resizing to allow for columns moved out of the Viewport 
     sampleTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); 

     // Populate the table with some data 
     for (int x = 0; x < 200; x++) 
     { 
      addRow(x); 
     } 

     // Create a ScrollPane 
     JScrollPane sp = new JScrollPane(sampleTable); 

     // Provide a horizontal scroll bar so that columns can be scrolled out of the Viewport 
     sp.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); 

     final JFrame f = new JFrame(); 
     f.getContentPane().add(sp); 
     f.setTitle("JTable cell display example"); 
     f.pack(); 
     f.setLocationRelativeTo(null); 
     f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     f.setVisible(true); 

     // Create a thread that periodically adds a row to the table 
     Thread rowAdder = new Thread(new Runnable() 
     { 
      @Override 
      public void run() 
      { 
       do 
       { 
        try 
        { 
         int secs = 5; 
         Thread.sleep(secs * 1000); 
        } 
        catch (InterruptedException e) 
        { 
         e.printStackTrace(); 
        } 

        // Add a row 
        addRow(dtm.getRowCount()); 
       } while (true); 
      } 
     }); 
     rowAdder.start(); 

     // Add the custom ComponentListener 
     sampleTable.addComponentListener(new JTableCellDisplayer(sampleTable)); 
    } 

    /** 
    * Display a table row when it is added to a JTable.<br> 
    * Details available at <a 
    * href="http://stackoverflow.com/questions/4890282/howto-to-scroll-to-last-row-on-jtable">StackOverflow</a>. 
    * <p> 
    * <b>Key information:</b> Whenever a row is added or removed the JTable resizes. This occurs even if the row is 
    * outside of the JScrollPane's Viewport (i.e., the row is not visible). 
    */ 
    class JTableCellDisplayer extends ComponentAdapter 
    { 
     boolean selRow  = false; 
     boolean selCol  = false; 
     boolean firstTime = true; 
     boolean selectData = false; 
     JTable table; 

     public JTableCellDisplayer(JTable jTable) 
     { 
      table = jTable; 
     } 

     @Override 
     public void componentResized(ComponentEvent e) 
     { 
      if (firstTime) 
      { 
       firstTime = false; 
       return; 
      } 

      int viewIx = table.convertRowIndexToView(table.getRowCount() - 1); 

      if (!selRow 
        && !selCol) 
      { 
       System.out.println(" - Select nothing - selectData=" 
         + selectData); 
      } 
      else if (selRow 
        && !selCol) 
      { 
       System.out.println(" - Select row only - selectData=" 
         + selectData); 
      } 
      else if (!selRow 
        && selCol) 
      { 
       System.out.println(" - Select column only - selectData=" 
         + selectData); 
      } 
      else 
      { 
       System.out.println(" - Select cell - selectData=" 
         + selectData); 
      } 

      // If data should be selected, set the selection policies on the table. 
      if (selectData) 
      { 
       table.setRowSelectionAllowed(selRow); 
       table.setColumnSelectionAllowed(selCol); 
      } 

      // Scroll to the VALUE cell (columnIndex=0) that was added 
      displayTableCell(table, viewIx, table.convertColumnIndexToView(0), selectData); 

      // Cycle through all possibilities 
      if (!selRow 
        && !selCol) 
      { 
       selRow = true; 
      } 
      else if (selRow 
        && !selCol) 
      { 
       selRow = false; 
       selCol = true; 
      } 
      else if (!selRow 
        && selCol) 
      { 
       selRow = true; 
       selCol = true; 
      } 
      else 
      { 
       selRow = false; 
       selCol = false; 
       selectData = !selectData; 
      } 

     } 
    } 

    /** 
    * Assuming the table is contained in a JScrollPane, scroll to the cell (vRowIndex, vColIndex). <br> 
    * The specified cell is guaranteed to be made visible.<br> 
    * Every attempt will be made to position the cell in the center of the Viewport. <b>Note:</b> This may not be 
    * possible if the row is too close to the top or bottom of the Viewport. 
    * <p> 
    * It is possible to select the specified cell. The amount of data selected (none, entire row, entire column or a 
    * single cell) is dependent on the settings specified by {@link JTable#setColumnSelectionAllowed(boolean)} and 
    * {@link JTable#setRowSelectionAllowed(boolean)}. 
    * <p> 
    * Original code found <a href="http://www.exampledepot.com/egs/javax.swing.table/VisCenter.html">here</a>. 
    * <p> 
    * 
    * @param table 
    *   - The table 
    * @param vRowIndex 
    *   - The view row index 
    * @param vColIndex 
    *   - The view column index 
    * @param selectCell 
    *   - If <code>true</code>, the cell will be selected in accordance with the table's selection policy; 
    *   otherwise the selected data will not be changed. 
    * @see JTable#convertRowIndexToView(int) 
    * @see JTable#convertColumnIndexToView(int) 
    */ 
    public static void displayTableCell(JTable table, int vRowIndex, int vColIndex, boolean selectCell) 
    { 
     if (!(table.getParent() instanceof JViewport)) 
     { 
      return; 
     } 

     JViewport viewport = (JViewport) table.getParent(); 

     /* This rectangle is relative to the table where the 
     * northwest corner of cell (0,0) is always (0,0). 
     */ 
     Rectangle rect = table.getCellRect(vRowIndex, vColIndex, true); 

     // The location of the view relative to the table 
     Rectangle viewRect = viewport.getViewRect(); 

     /* 
     * Translate the cell location so that it is relative 
     * to the view, assuming the northwest corner of the 
     * view is (0,0). 
     */ 
     rect.setLocation(rect.x 
       - viewRect.x, rect.y 
       - viewRect.y); 

     // Calculate location of rectangle if it were at the center of view 
     int centerX = (viewRect.width - rect.width)/2; 
     int centerY = (viewRect.height - rect.height)/2; 

     /* 
     * Fake the location of the cell so that scrollRectToVisible 
     * will move the cell to the center 
     */ 
     if (rect.x < centerX) 
     { 
      centerX = -centerX; 
     } 
     if (rect.y < centerY) 
     { 
      centerY = -centerY; 
     } 
     rect.translate(centerX, centerY); 

     // If desired and allowed, select the appropriate cell 
     if (selectCell 
       && (table.getRowSelectionAllowed() || table.getColumnSelectionAllowed())) 
     { 
      // Clear any previous selection 
      table.clearSelection(); 

      table.setRowSelectionInterval(vRowIndex, vRowIndex); 
      table.setColumnSelectionInterval(vColIndex, vColIndex); 
     } 

     // Scroll the area into view. 
     viewport.scrollRectToVisible(rect); 
    } 

    private String addRow(int tableRowIndex) 
    { 
     String retVal; 

     int value = random.nextInt(99999999); 
     dtm.addRow(new Object[] { 
       value, 
       tableRowIndex, 
       random.nextInt(99999999), 
       random.nextInt(99999999), 
       random.nextInt(99999999), 
       random.nextInt(99999999), 
       random.nextInt(99999999), 
       random.nextInt(99999999), }); 

     retVal = "Row added - value=" 
       + value + " & tableRowIx=" + tableRowIndex; 

     System.out.println(retVal); 
     return retVal; 
    } 

    public static void main(String[] args) 
    { 
     SwingUtilities.invokeLater(new Runnable() 
     { 
      @Override 
      public void run() 
      { 
       new JTableScrollToRow().buildGUI(); 
      } 
     }); 
    } 

} 
+2

a) không ngủ EDT b) không truy cập các thành phần Swing ngoài EDT – kleopatra

1

Tại sao không gọi fireTableRowsInserted khi cập nhật trong việc thực hiện TableModel của bạn?

tôi usally có một cái gì đó như dưới đây trong việc thực hiện TableModel của tôi:

public void addRow (MyDataType valToAdd){ 
rows.add(valToAdd); 
fireTableRowsInserted(rows.size()-1,rows.size()-1); 
} 
2

gọi phương pháp này bất cứ khi nào bạn muốn di chuyển xuống bot của bảng. Và vấn đề trên được giải quyết bằng cách sử dụng phương pháp này.

public void scrolltable() 
{ 
    table.addComponentListener(new ComponentAdapter() { 
     public void componentResized(ComponentEvent e) { 
      int lastIndex =table.getCellRect(table.getRowCount()-1; 
      table.changeSelection(lastIndex, 0,false,false); 
     } 
    }); 
} 
+1

vui lòng chỉ chỉnh sửa một dòng: - int lastIndex = table.getRowCount() - 1; Công trình này ..! –

Các vấn đề liên quan