2012-05-22 33 views
13

我在JScrollPane的視口中使用第二個JTable爲主表構建RowHeader。 主表上的DragAndDrop被禁用。在rowheader表上DnD被啓用。如何在DragAndDrop期間在Main-JTable上繪製RowHeader-JTable的Dropline?

如果用戶啓動了rowheader上的拖動操作,我想要在主表格上(如圖像中的綠色線)擴大繪製的rowheader dropline(圖像中的黑線)。

dropline for the maintable

任何人不會有我的一個建議嗎?
這裏的SSCCE:

import java.awt.Component; 
import java.awt.EventQueue; 
import java.awt.Font; 
import java.awt.datatransfer.StringSelection; 
import java.awt.datatransfer.Transferable; 
import java.beans.PropertyChangeEvent; 
import java.beans.PropertyChangeListener; 
import javax.swing.DropMode; 
import javax.swing.JComponent; 
import javax.swing.JFrame; 
import javax.swing.JLabel; 
import javax.swing.JScrollPane; 
import javax.swing.JTable; 
import javax.swing.JViewport; 
import javax.swing.TransferHandler; 
import javax.swing.UIManager; 
import javax.swing.event.ChangeEvent; 
import javax.swing.event.ChangeListener; 
import javax.swing.table.DefaultTableCellRenderer; 
import javax.swing.table.JTableHeader; 
import javax.swing.table.TableColumn; 


public class DNDLinePainterExampleMain extends JFrame { 

    public DNDLinePainterExampleMain() { 
    JTable mainTable = new JTable(4, 3); 
    mainTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); 

    JTable rowTable = new RowHeaderTable(mainTable); 
    rowTable.setAutoscrolls(true); 
    rowTable.setDragEnabled(true); 
    rowTable.setTransferHandler(new RowHeaderTransferHandler()); 
    rowTable.setDropMode(DropMode.INSERT_ROWS); 

    JScrollPane scrollPane = new JScrollPane(mainTable); 
    scrollPane.setRowHeaderView(rowTable); 
    scrollPane.setCorner(JScrollPane.UPPER_LEFT_CORNER, 
     rowTable.getTableHeader()); 
    this.add(scrollPane); 
    } 

    public static void main(String[] args) { 
    EventQueue.invokeLater(new Runnable() { 
     @Override 
     public void run() { 
     JFrame f = new DNDLinePainterExampleMain(); 
     f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     f.pack(); 
     f.setVisible(true); 
     } 
    }); 
    } 


    /* 
    * Use a JTable as a renderer for row numbers of a given main table. This 
    * table must be added to the row header of the scrollpane that contains the 
    * main table. from: 
    * http://tips4java.wordpress.com/2008/11/18/row-number-table/ 
    */ 
    public class RowHeaderTable extends JTable implements ChangeListener, 
     PropertyChangeListener { 

    private final JTable table; 

    public RowHeaderTable(JTable table) { 
     this.table = table; 
     table.addPropertyChangeListener(this); 

     setFocusable(false); 
     setAutoCreateColumnsFromModel(false); 

     updateRowHeight(); 
     updateModel(); 
     updateSelectionModel(); 

     TableColumn column = new TableColumn(); 
     column.setHeaderValue(""); 
     addColumn(column); 
     column.setCellRenderer(new RowNumberRenderer()); 

     getColumnModel().getColumn(0).setPreferredWidth(50); 
     setPreferredScrollableViewportSize(getPreferredSize()); 

     getTableHeader().setReorderingAllowed(false); 
    } 

    @Override 
    public void addNotify() { 
     super.addNotify(); 
     Component c = getParent(); 
     // Keep scrolling of the row table in sync with the main table. 
     if (c instanceof JViewport) { 
     JViewport viewport = (JViewport) c; 
     viewport.addChangeListener(this); 
     } 
    } 

    /* 
    * Delegate method to main table 
    */ 
    @Override 
    public int getRowCount() { 
     return table.getRowCount(); 
    } 

    @Override 
    public int getRowHeight(int row) { 
     return table.getRowHeight(row); 
    } 

    /* 
    * This table does not use any data from the main TableModel, so just return 
    * a value based on the row parameter. 
    */ 
    @Override 
    public Object getValueAt(int row, int column) { 
     return Integer.toString(row + 1); 
    } 

    /* 
    * Don't edit data in the main TableModel by mistake 
    */ 
    @Override 
    public boolean isCellEditable(int row, int column) { 
     return false; 
    } 

    // implements ChangeListener 
    @Override 
    public void stateChanged(ChangeEvent e) { 
     // Keep the scrolling of the row table in sync with main table 
     JViewport viewport = (JViewport) e.getSource(); 
     JScrollPane scrollPane = (JScrollPane) viewport.getParent(); 
     scrollPane.getVerticalScrollBar().setValue(viewport.getViewPosition().y); 
    } 

    // implements PropertyChangeListener 
    @Override 
    public void propertyChange(PropertyChangeEvent e) { 
     // Keep the row table in sync with the main table 
     if ("rowHeight".equals(e.getPropertyName())) 
     updateRowHeight(); 

     if ("selectionModel".equals(e.getPropertyName())) 
     updateSelectionModel(); 

     if ("model".equals(e.getPropertyName())) 
     updateModel(); 
    } 

    private void updateRowHeight() { 
     setRowHeight(table.getRowHeight()); 
    } 

    private void updateModel() { 
     setModel(table.getModel()); 
    } 

    private void updateSelectionModel() { 
     setSelectionModel(table.getSelectionModel()); 
    } 


    /* 
    * Borrow the renderer from JDK1.4.2 table header 
    */ 
    private class RowNumberRenderer extends DefaultTableCellRenderer { 

     public RowNumberRenderer() { 
     setHorizontalAlignment(JLabel.CENTER); 
     } 

     @Override 
     public Component getTableCellRendererComponent(JTable table, 
      Object value, boolean isSelected, boolean hasFocus, int row, 
      int column) { 
     if (table != null) { 
      JTableHeader header = table.getTableHeader(); 

      if (header != null) { 
      setForeground(header.getForeground()); 
      setBackground(header.getBackground()); 
      setFont(header.getFont()); 
      } 
     } 

     if (isSelected) { 
      setFont(getFont().deriveFont(Font.BOLD)); 
     } 

     setText((value == null) ? "" : value.toString()); 
     setBorder(UIManager.getBorder("TableHeader.cellBorder")); 

     return this; 
     } 
    }//class RowNumberRenderer 

    }//class RowHeaderTable 


    public class RowHeaderTransferHandler extends TransferHandler { 

    @Override 
    public int getSourceActions(JComponent c) { 
     return COPY_OR_MOVE; 
    } 

    @Override 
    protected Transferable createTransferable(JComponent c) { 
     return new StringSelection(c.getName()); 
    } 

    @Override 
    public boolean canImport(TransferSupport supp) { 
     return true; 
    } 
    }//class RowHeaderTransferHandler 


}//class DNDLinePainterExampleMain 

回答

4

我建議你做這樣的(使用Swing的拖放&拖放功能):

RowHeaderTable rowTable = new RowHeaderTable(mainTable); 
    rowTable.setAutoscrolls(true); 
    rowTable.setDragEnabled(true); 
    rowTable.setTransferHandler(new RowHeaderTransferHandler(mainTable)); 
    rowTable.setDropMode(DropMode.INSERT_ROWS); 
    try { 
     DropTarget dropTarget = rowTable.getDropTarget(); 
     dropTarget.addDropTargetListener(rowTable); 
    } 
    catch(TooManyListenersException e) { 
     e.printStackTrace(); 
    } 

現在你必須實現DropTargetListener

public class RowHeaderTable extends JTable implements ChangeListener, PropertyChangeListener, DropTargetListener { 

這2種方法,你應該感興趣於:

@Override 
    public void dragEnter(DropTargetDragEvent dtde) { shouldPaint = true } 

    @Override 
    public void dragExit(DropTargetEvent dte) { shouldPaint = false } 

現在,你應該操縱你的mainTable使用TranserHandler(畫本「綠線」):

@Override 
    public boolean canImport(TransferSupport supp) { 
     DropLocation location = supp.getDropLocation(); 
     if(location instanceof javax.swing.JTable.DropLocation && shouldPaint) { 
      javax.swing.JTable.DropLocation tableLocation = (javax.swing.JTable.DropLocation) location; 
      int rowToInsert = tableLocation.getRow(); 
      //paint somehow the "green line" 
      //remember that canImport is invoked when you move your mouse 
     } 
     return true; 
    } 

基本上你應該實現自己的行渲染器或類似的東西(像@naugler答案)。你應該以某種方式向你的TransferHandler提供布爾值shouldPaint。但這不是一個大問題。

希望這會有所幫助。

+0

很好的回答!我會接受它,因爲現在我在源代碼中使用了一個類似於你的想法(不是'DropTargetListener',而是'dropLocation'上的'PropertyChangeListener')。你爲什麼認爲在'TransferHandler'的'canImport()'中做繪畫是一個好主意?我正在使用從偵聽器中可見的GlassPane。 – bobndrew

+0

直接在TransferHandler中進行繪畫並不是一個好主意。我修改如下:添加組件/更改L&F UI /使用圖層/等。然後重新驗證/重新打印 – Xeon

7

好吧。懺悔時間。這裏有一些粗糙的鈍器代碼。我很抱歉無法生產更優雅或乾淨的解決方案。我是深深的黑暗地下城的一級遊客。

此自定義代碼屬於快速和骯髒的類別。它不適用於列,我不確定所有的邊界情況,並且我不知道在繪製例程中偷取另一個組件圖形上下文的規則。底線:這是一個例子,而不是一個完整的解決方案。

因爲您正在使用單獨的表進行拖放(您的rowTable),所以沒有很好的方法讓該表繪製到mainTable。應該注意的是,使用PropertyChangeListeners或者將事件觸發到mainTable中可能會有一條光滑而閃亮的路徑,但我沒有管理它。這裏是BasicTableUI的自定義擴展:

public class ExtendedDropLineTableUI extends BasicTableUI { 

     private JTable drawTable; 
     private Integer oldRow; 

     //We give this UI instance a reference to a separate table for drawing 
     public ExtendedDropLineTableUI(JTable drawTable) { 
      this.drawTable = drawTable; 
     } 

     @Override 
     public void paint(Graphics g, JComponent c) { 
      super.paint(g, c); 
      paintExtendedDropLine(); 
     } 

     private void paintExtendedDropLine() { 
      JTable.DropLocation loc = table.getDropLocation(); 
      if (loc == null) { 
       drawTable.repaint(); 
       return; 
      } 

      //get the correct line color. no color? no line! 
      Color color = UIManager.getColor("Table.dropLineColor"); 
      if (color == null) { 
       return; 
      } 

      //try to repaint the draw table only if the row changes 
      if (oldRow != null && oldRow != loc.getRow()) { 
       drawTable.repaint(); 
      } 
      oldRow = loc.getRow(); 
      //get location of cell rectangle 
      int row = loc.getRow(); 
      int col = loc.getColumn(); 
      if (col >= table.getColumnCount()) { 
       col--; 
      } 
      Rectangle rect = table.getCellRect(row, col, true); 
      //adjust rectangle to fit between the cells 
      if (rect.y == 0) { 
       rect.y = -1; 
      } else { 
       rect.y -= 2; 
      } 
      //what's a line but a really thin rectangle? 
      rect.height = 3; 
      //extend the rectangle to the width of the drawing table 
      rect.width = drawTable.getWidth(); 
      //draw the rectangle 
      Graphics g = drawTable.getGraphics(); 
      g.setColor(color); 
      g.fillRect(rect.x, rect.y, rect.width, rect.height); 
     } 
    } 
} 

你需要給這個UI您rowTable所以它可以提請mainTable,就像這樣:

rowTable.setUI(new ExtendedDropLineTableUI(mainTable)); 

我靠在大量實際source code劫持拖放行的繪圖,因爲涉及這些東西的方法都是私有的。

編輯我忘了一旁提爲我擔心你們可能試圖重新安排整個行中的mainTable由rowTable拖動單元格。這個解決方案也沒有考慮到這一點,它只是繪製線。爲此,您將需要一個更優雅的解決方案,或者將mainTable中的行與rowTable同步排列的檢查。

+0

@Xeon和有趣的建議,這些當我走近這個問題是非常有用的naugler感謝。 (+1)給你們。 – Boro

+0

謝謝你的回答!我會爲您提供最快,最全面的工作示例。它幫助我獲得了線條繪畫的計算權利,我很欣賞它是「一班一班」的解決方案。但是......我也認爲BasicTableUI的擴展風險太大了;-)正因爲如此,我會接受@Xeon的答案。 – bobndrew

+0

哦,我忘了回答你的**編輯**:在我們的程序中已經同步了2個表格,並且在DnDroppping已經工作後重新排列行數據。我只是沒有設法劃清界限。 – bobndrew

6

我的解決方案使用了稍微不同的方法,因爲我們想在現有組件上添加一些自定義繪畫。因此,我選擇使用GlassPane並在那裏做繪畫。通過這種方式,突出顯示可以成爲您所能想象的一切,並且其大小不限於像元大小,因爲它可以使用例如單元格渲染器的方法。

要運行此示例,您需要下載the FinalGlassPane from this site。這是必需的,因爲我們使用它的能力捕獲事件(其中通常使用GlassPane)。如果您知道在拖動終於結束時捕獲事件的另一種方式,則可以一起避免所有這些事件。 如果你知道一個人,請分享。對我來說這個工作最好加上我喜歡有一個GlassPane它可以捕捉事件並不會消耗所有。

import java.awt.*; 
import java.awt.datatransfer.StringSelection; 
import java.awt.datatransfer.Transferable; 
import java.awt.dnd.*; 
import java.awt.event.AWTEventListener; 
import java.awt.event.MouseEvent; 
import java.beans.PropertyChangeEvent; 
import java.beans.PropertyChangeListener; 
import javax.swing.*; 
import javax.swing.event.ChangeEvent; 
import javax.swing.event.ChangeListener; 
import javax.swing.table.DefaultTableCellRenderer; 
import javax.swing.table.JTableHeader; 
import javax.swing.table.TableColumn; 

public class DNDLinePainterExampleMain extends JFrame { 

    public int x = -1; 
    public int y = -1; 
    private boolean isDragged = false; 
    public FinalGlassPane glassPane; 
    private boolean isOutsideTable = false; 

    public DNDLinePainterExampleMain() { 
     final JTable mainTable = new JTable(4, 3); 
     mainTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); 

     final JTable rowTable = new RowHeaderTable(mainTable); 
     rowTable.setAutoscrolls(true); 
     rowTable.setDragEnabled(true); 
     rowTable.setTransferHandler(new RowHeaderTransferHandler()); 
     rowTable.setDropMode(DropMode.INSERT_ROWS); 
     final JScrollPane scrollPane = new JScrollPane(mainTable); 
     scrollPane.setRowHeaderView(rowTable); 
     scrollPane.setCorner(JScrollPane.UPPER_LEFT_CORNER, 
       rowTable.getTableHeader()); 

     final JPanel panel = new JPanel(); 

     DragSourceMotionListener dsml = new DragSourceMotionListener() { 

      @Override 
      public void dragMouseMoved(DragSourceDragEvent dsde) { 
       isDragged = true; 
       isOutsideTable = true; 
       Component source = dsde.getDragSourceContext().getComponent(); 
       //the coordinates of the drag event in screen coords 
       Point toConvert = new Point(dsde.getX(), dsde.getY()); 
       //convert to source coords 
       SwingUtilities.convertPointFromScreen(toConvert, source); 
       int rowMargin = rowTable.getRowMargin(); 
       Point toTest = new Point(toConvert.x, toConvert.y); 
       for (int i = 0; i < rowTable.getRowCount(); i++) { 
        Rectangle bounds = rowTable.getCellRect(i, 0, true); 
        boolean isIn = bounds.contains(toTest); 
//     System.out.println("bounds = "+bounds+"; rowMargin = "+rowMargin+"; i = "+i+"; isIn = "+isIn); 
        if (isIn) { 
         isOutsideTable = false; 
         int hHalf = bounds.height/2; 
         int hIn = toTest.y - bounds.y; 
         boolean isTop = false; 
         if (hIn < hHalf) { 
          isTop = true; 
         } 
         x = bounds.width; 
         y = bounds.y - rowMargin; 
         if (!isTop) { 
          y += bounds.height; 
         } 
         //now convert the point to the glass pane coordinates 
         Point c = SwingUtilities.convertPoint(rowTable, x, y, glassPane); 
         x = c.x; 
         y = c.y; 
//      System.out.println("hIn = "+hIn+"; isTop = "+isTop + ""); 
        } 
       } 
       glassPane.repaint(); 
      } 
     }; 
     DragSource ds = new DragSource(); 
     ds.addDragSourceMotionListener(dsml); 


     this.setContentPane(panel); 
     panel.add(new JButton("Oi for testing")); 
     panel.add(scrollPane); 


     DragSource dragSource = DragSource.getDefaultDragSource(); 
     dragSource.addDragSourceMotionListener(dsml); 
     glassPane = new FinalGlassPane(this) { 

      @Override 
      public void eventDispatched(AWTEvent event) { 
       super.eventDispatched(event); 
       if (event instanceof MouseEvent) { 
        //after drag is relesed we are back here with mouse entered event 
        if (isDragged) { 
         isDragged = false; 
         repaint(); 
        } 
       } 
      } 

      @Override 
      protected void paintComponent(Graphics g) { 
       Graphics2D g2 = (Graphics2D) g; 
       if (isDragged && !isOutsideTable) { 
        g2.setPaint(Color.GREEN); 
        g2.drawLine(x + 2, y + 1, x + mainTable.getWidth() - 4, y + 1); 
        g2.drawLine(x, y, x + mainTable.getWidth(), y); 
        g2.drawLine(x + 2, y - 1, x + mainTable.getWidth() - 4, y - 1); 
       } 
      } 
     }; 
     AWTEventListener al = (AWTEventListener) glassPane; 
     Toolkit.getDefaultToolkit().addAWTEventListener(al, 
       AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK); 

     this.setGlassPane(glassPane); 
     glassPane.setVisible(true); 
    } 

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

      @Override 
      public void run() { 
       JFrame f = new DNDLinePainterExampleMain(); 
       f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
       f.pack(); 
       f.setVisible(true); 
      } 
     }); 
    } 


    /* 
    * Use a JTable as a renderer for row numbers of a given main table. This 
    * table must be added to the row header of the scrollpane that contains the 
    * main table. from: 
    * http://tips4java.wordpress.com/2008/11/18/row-number-table/ 
    */ 
    public class RowHeaderTable extends JTable implements ChangeListener, 
      PropertyChangeListener { 

     private final JTable table; 

     public RowHeaderTable(JTable table) { 
      this.table = table; 
      table.addPropertyChangeListener(this); 

      setFocusable(false); 
      setAutoCreateColumnsFromModel(false); 

      updateRowHeight(); 
      updateModel(); 
      updateSelectionModel(); 

      TableColumn column = new TableColumn(); 
      column.setHeaderValue(""); 
      addColumn(column); 
      column.setCellRenderer(new RowNumberRenderer()); 

      getColumnModel().getColumn(0).setPreferredWidth(50); 
      setPreferredScrollableViewportSize(getPreferredSize()); 

      getTableHeader().setReorderingAllowed(false); 
     } 

     @Override 
     public void addNotify() { 
      super.addNotify(); 
      Component c = getParent(); 
      // Keep scrolling of the row table in sync with the main table. 
      if (c instanceof JViewport) { 
       JViewport viewport = (JViewport) c; 
       viewport.addChangeListener(this); 
      } 
     } 

     /* 
     * Delegate method to main table 
     */ 
     @Override 
     public int getRowCount() { 
      return table.getRowCount(); 
     } 

     @Override 
     public int getRowHeight(int row) { 
      return table.getRowHeight(row); 
     } 

     /* 
     * This table does not use any data from the main TableModel, so just 
     * return a value based on the row parameter. 
     */ 
     @Override 
     public Object getValueAt(int row, int column) { 
      return Integer.toString(row + 1); 
     } 
     /* 
     * Don't edit data in the main TableModel by mistake 
     */ 

     @Override 
     public boolean isCellEditable(int row, int column) { 
      return false; 
     } 
     // implements ChangeListener 

     @Override 
     public void stateChanged(ChangeEvent e) { 
      // Keep the scrolling of the row table in sync with main table 
      JViewport viewport = (JViewport) e.getSource(); 
      JScrollPane scrollPane = (JScrollPane) viewport.getParent(); 
      scrollPane.getVerticalScrollBar().setValue(viewport.getViewPosition().y); 
     } 
     // implements PropertyChangeListener 

     @Override 
     public void propertyChange(PropertyChangeEvent e) { 
      // Keep the row table in sync with the main table 
      if ("rowHeight".equals(e.getPropertyName())) { 
       updateRowHeight(); 
      } 

      if ("selectionModel".equals(e.getPropertyName())) { 
       updateSelectionModel(); 
      } 

      if ("model".equals(e.getPropertyName())) { 
       updateModel(); 
      } 
     } 

     private void updateRowHeight() { 
      setRowHeight(table.getRowHeight()); 
     } 

     private void updateModel() { 
      setModel(table.getModel()); 
     } 

     private void updateSelectionModel() { 
      setSelectionModel(table.getSelectionModel()); 
     } 
     /* 
     * Borrow the renderer from JDK1.4.2 table header 
     */ 

     private class RowNumberRenderer extends DefaultTableCellRenderer { 

      public RowNumberRenderer() { 
       setHorizontalAlignment(JLabel.CENTER); 
      } 

      @Override 
      public Component getTableCellRendererComponent(JTable table, 
        Object value, boolean isSelected, boolean hasFocus, int row, 
        int column) { 
       if (table != null) { 
        JTableHeader header = table.getTableHeader(); 

        if (header != null) { 
         setForeground(header.getForeground()); 
         setBackground(header.getBackground()); 
         setFont(header.getFont()); 
        } 
       } 
       if (isSelected) { 
        setFont(getFont().deriveFont(Font.BOLD)); 
       } 
       setText((value == null) ? "" : value.toString()); 
       setBorder(UIManager.getBorder("TableHeader.cellBorder")); 
       return this; 
      } 
     }//class RowNumberRenderer 
    }//class RowHeaderTable 

    public class RowHeaderTransferHandler extends TransferHandler { 

     @Override 
     public int getSourceActions(JComponent c) { 
      return COPY_OR_MOVE; 
     } 

     @Override 
     protected Transferable createTransferable(JComponent c) { 
      return new StringSelection(c.getName()); 
     } 

     @Override 
     public boolean canImport(TransferSupport supp) { 
      return true; 
     } 
    }//class RowHeaderTransferHandler 
} 
+0

很好的答案,也謝謝。 +1使用'GlassPane',這樣就可以在兩個表上繪製一個透明的DropLine。但我不認爲它必須是'FinalGlassPane',我認爲'dropLocation'的'PropertyChangeListener'更具體。請查看我答案中的最新代碼,也是我在RowHeader-Table中使用getDropLocation()來計算DropLine-Rectangle的方式。 – bobndrew

+0

@bobndrew謝謝。你是對的。因此,我想問你是否可以發佈你使用的解決方案?例如,編輯它到您的問題,最底部添加一個標題爲「編輯 - 解決方案**」的部分,解決方案將在該部分。 – Boro

6

感謝來自naugler,Xeon和米堡的巨大貢獻,我現在用的自己的3個例子的組合。它看起來像這樣:

the final table dropline

而這裏的代碼:

import java.awt.AlphaComposite; 
import java.awt.Color; 
import java.awt.Component; 
import java.awt.EventQueue; 
import java.awt.Font; 
import java.awt.Graphics; 
import java.awt.Graphics2D; 
import java.awt.Rectangle; 
import java.awt.datatransfer.StringSelection; 
import java.awt.datatransfer.Transferable; 
import java.beans.PropertyChangeEvent; 
import java.beans.PropertyChangeListener; 
import javax.swing.DropMode; 
import javax.swing.JComponent; 
import javax.swing.JFrame; 
import javax.swing.JLabel; 
import javax.swing.JPanel; 
import javax.swing.JScrollPane; 
import javax.swing.JTable; 
import javax.swing.JTable.DropLocation; 
import javax.swing.JViewport; 
import javax.swing.RootPaneContainer; 
import javax.swing.SwingUtilities; 
import javax.swing.TransferHandler; 
import javax.swing.UIManager; 
import javax.swing.event.ChangeEvent; 
import javax.swing.event.ChangeListener; 
import javax.swing.table.DefaultTableCellRenderer; 
import javax.swing.table.JTableHeader; 
import javax.swing.table.TableColumn; 


public class DNDLinePainterSolutionMain 
{ 
    public static void main(String[] args) 
    { 
    EventQueue.invokeLater(new Runnable() 
    { 
     @Override 
     public void run() 
     { 
     JFrame f = new MainFrame(); 
     f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     f.pack(); 
     f.setVisible(true); 
     } 
    }); 
    } 
}//public class DNDLinePainterSolutionMain 


class MainFrame extends JFrame 
{ 
    public MainFrame() 
    { 
    JTable mainTable = new JTable(4, 3); 
    mainTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); 

    JTable rowTable = new RowHeaderTable(mainTable); 
    rowTable.setAutoscrolls(true); 
    rowTable.setDragEnabled(true); 
    rowTable.setTransferHandler(new RowHeaderTransferHandler()); 
    rowTable.setDropMode(DropMode.INSERT_ROWS); 

    //install the DropLocation-Extension: 
    rowTable.addPropertyChangeListener("dropLocation", 
     new DropLocationRepainter(this)); 

    JScrollPane scrollPane = new JScrollPane(mainTable); 
    scrollPane.setRowHeaderView(rowTable); 
    scrollPane.setCorner(JScrollPane.UPPER_LEFT_CORNER, 
     rowTable.getTableHeader()); 
    this.add(scrollPane); 
    } 
}//class MainFrame 


class RowHeaderTransferHandler extends TransferHandler 
{ 
    @Override 
    public int getSourceActions(JComponent c) 
    { 
    return COPY_OR_MOVE; 
    } 

    @Override 
    protected Transferable createTransferable(JComponent c) 
    { 
    return new StringSelection(c.getName()); 
    } 

    @Override 
    public boolean canImport(TransferSupport supp) 
    { 
    return true; 
    } 
}//class RowHeaderTransferHandler 


/** 
* Listens to a dropLocation-PropertyChange and repaints the DropLine. 
*/ 
class DropLocationRepainter implements PropertyChangeListener 
{ 
    private static RowHeaderDropLineGlassPane glassPane; 
    private final RootPaneContainer   rootPaneContainer; 

    public DropLocationRepainter(RootPaneContainer dndLinePainterSolutionMain) 
    { 
    this.rootPaneContainer = dndLinePainterSolutionMain; 
    } 

    @Override 
    public void propertyChange(PropertyChangeEvent pce) 
    { 
    String propertyName = pce.getPropertyName(); 
    if ("dropLocation".equals(propertyName) && pce.getNewValue() != null) 
    { 
     if (glassPane == null) 
     { 
     rootPaneContainer.getRootPane().setGlassPane(
      glassPane = new RowHeaderDropLineGlassPane((RowHeaderTable) pce 
       .getSource())); 
     } 
     rootPaneContainer.getRootPane().getGlassPane().setVisible(true); 

     repaintDropLocation(((JTable) pce.getSource()).getDropLocation()); 
    } 
    else 
     if ("dropLocation".equals(propertyName)) 
     rootPaneContainer.getRootPane().getGlassPane().setVisible(false); 
    } 

    private void repaintDropLocation(DropLocation loc) 
    { 
    Component c = rootPaneContainer.getRootPane().getGlassPane(); 
    if (c instanceof RowHeaderDropLineGlassPane) 
    { 
     RowHeaderDropLineGlassPane glassPane = (RowHeaderDropLineGlassPane) c; 
     glassPane.repaint(); 
    } 
    } 
}//class DropLocationRepainter 


class RowHeaderDropLineGlassPane extends JPanel 
{ 
    private RowHeaderTable table; 

    public RowHeaderDropLineGlassPane(RowHeaderTable table) 
    { 
    this.table = table; 
    setOpaque(false); 
    } 

    @Override 
    public void paintComponent(Graphics g) 
    { 
    Graphics2D g2 = (Graphics2D) g; 
    g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f)); 
    Rectangle rect = table.getDropLineRect(); 
    if (rect != null) 
    { 
     Rectangle r = SwingUtilities.convertRectangle(table, rect, this); 
     g2.setColor(new Color(40, 80, 0)); 
     g2.fill(r); 
    } 
    } 
}//class RowHeaderDropLineGlassPane 


/** 
* Use a JTable as a renderer for row numbers of a given main table. This table 
* must be added to the row header of the scrollpane that contains the main 
* table. From: http://tips4java.wordpress.com/2008/11/18/row-number-table/ 
* <p> 
* Added {@code getDropLineRect()} for DropLine extension over the maintable. 
* </p> 
*/ 
class RowHeaderTable extends JTable implements ChangeListener, 
    PropertyChangeListener 
{ 

    private final JTable mainTable; 

    public RowHeaderTable(JTable mainTable) 
    { 
    this.mainTable = mainTable; 
    mainTable.addPropertyChangeListener(this); 

    setFocusable(false); 
    setAutoCreateColumnsFromModel(false); 

    updateRowHeight(); 
    updateModel(); 
    updateSelectionModel(); 

    TableColumn column = new TableColumn(); 
    column.setHeaderValue(""); 
    addColumn(column); 
    column.setCellRenderer(new RowNumberRenderer()); 

    getColumnModel().getColumn(0).setPreferredWidth(50); 
    setPreferredScrollableViewportSize(getPreferredSize()); 

    getTableHeader().setReorderingAllowed(false); 
    } 

    /* 
    * called from the class RowHeaderDropLineGlassPane 
    */ 
    public Rectangle getDropLineRect() 
    { 
    DropLocation loc = getDropLocation(); 
    if (loc == null /* || !loc.isDropable() */) 
     return null; 

    final Rectangle lineRect = new Rectangle(); 

    int index = loc.getRow(); 
    if (index < 0) 
    { 
     lineRect.setRect(0, 0, 0, 0); 
     return null; 
    } 

    Rectangle r = getCellRect(index, 0, true); 
    if (index == getRowCount()) 
     r.height = getCellRect(index - 1, 0, true).height;//if the last line is the DropTarget a height of 0 (of a non-existing Cell after the last row) is returned. 

    lineRect.setRect(r.x, r.y - (r.height/4d), getVisibleRect().width 
     + mainTable.getWidth(), r.height/2d - 1); 
    return lineRect; 
    } 

    @Override 
    public void addNotify() 
    { 
    super.addNotify(); 
    Component c = getParent(); 
    // Keep scrolling of the row table in sync with the main table. 
    if (c instanceof JViewport) 
    { 
     JViewport viewport = (JViewport) c; 
     viewport.addChangeListener(this); 
    } 
    } 

    /* 
    * Delegate method to main table 
    */ 
    @Override 
    public int getRowCount() 
    { 
    return mainTable.getRowCount(); 
    } 

    @Override 
    public int getRowHeight(int row) 
    { 
    return mainTable.getRowHeight(row); 
    } 

    /* 
    * This table does not use any data from the main TableModel, so just return a 
    * value based on the row parameter. 
    */ 
    @Override 
    public Object getValueAt(int row, int column) 
    { 
    return Integer.toString(row + 1); 
    } 

    /* 
    * Don't edit data in the main TableModel by mistake 
    */ 
    @Override 
    public boolean isCellEditable(int row, int column) 
    { 
    return false; 
    } 

    // implements ChangeListener 
    @Override 
    public void stateChanged(ChangeEvent e) 
    { 
    // Keep the scrolling of the row table in sync with main table 
    JViewport viewport = (JViewport) e.getSource(); 
    JScrollPane scrollPane = (JScrollPane) viewport.getParent(); 
    scrollPane.getVerticalScrollBar().setValue(viewport.getViewPosition().y); 
    } 

    // implements PropertyChangeListener 
    @Override 
    public void propertyChange(PropertyChangeEvent e) 
    { 
    // Keep the row table in sync with the main table 
    if ("rowHeight".equals(e.getPropertyName())) 
     updateRowHeight(); 

    if ("selectionModel".equals(e.getPropertyName())) 
     updateSelectionModel(); 

    if ("model".equals(e.getPropertyName())) 
     updateModel(); 
    } 

    private void updateRowHeight() 
    { 
    setRowHeight(mainTable.getRowHeight()); 
    } 

    private void updateModel() 
    { 
    setModel(mainTable.getModel()); 
    } 

    private void updateSelectionModel() 
    { 
    setSelectionModel(mainTable.getSelectionModel()); 
    } 


    /* 
    * Borrow the renderer from JDK1.4.2 table header 
    */ 
    private class RowNumberRenderer extends DefaultTableCellRenderer 
    { 
    public RowNumberRenderer() 
    { 
     setHorizontalAlignment(JLabel.CENTER); 
    } 

    @Override 
    public Component getTableCellRendererComponent(JTable table, Object value, 
     boolean isSelected, boolean hasFocus, int row, int column) 
    { 
     if (table != null) 
     { 
     JTableHeader header = table.getTableHeader(); 

     if (header != null) 
     { 
      setForeground(header.getForeground()); 
      setBackground(header.getBackground()); 
      setFont(header.getFont()); 
     } 
     } 

     if (isSelected) 
     setFont(getFont().deriveFont(Font.BOLD)); 

     setText((value == null) ? "" : value.toString()); 
     setBorder(UIManager.getBorder("TableHeader.cellBorder")); 

     return this; 
    } 
    }//class RowNumberRenderer 

}//class RowHeaderTable 
+2

感謝您決定在此發佈。我非常喜歡這個解決方案。再次歡呼'bobndrew',並且因爲您將它添加爲答案......這是怎麼回事...... +1 :) – Boro

相關問題