【问题标题】:How to paint the Dropline of a RowHeader-JTable on the Main-JTable during a DragAndDrop?如何在 DragAndDrop 期间在 Main-JTable 上绘制 RowHeader-JTable 的 Dropline?
【发布时间】:2012-05-29 03:22:29
【问题描述】:

我在 JScrollPane 的视口中使用第二个 JTable 来为主表构建 RowHeader。 主表上的 DragAndDrop 被禁用。在行头表上启用了 DnD。

如果用户启动了对行标题的拖动,我想将绘制的行标题下拉线(图像中的黑线)延伸到主表上(如图像中的绿线)。

有人对我有什么建议吗?
这是 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

【问题讨论】:

    标签: java swing user-interface drag-and-drop jtable


    【解决方案1】:

    感谢 naugler、Xeon 和 Boro 的巨大贡献,我现在结合使用他们的 3 个示例。它看起来像这样:

    这是代码:

    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
    

    【讨论】:

    • 感谢您决定在此处发布。我非常喜欢这个解决方案。再次欢呼bobndrew,既然您将其添加为答案...到底是什么... +1 :)
    【解决方案2】:

    好的。忏悔时间。这是一些粗糙、生硬的仪器代码。对于无法产生更优雅或更干净的解决方案,我深表歉意。我是深邃黑暗的秋千地牢中的 1 级游客。

    此自定义代码属于快速而肮脏的类别。它不适用于列,我不确定所有边缘情况,并且我不知道在绘制例程中间窃取另一个组件图形上下文的规则。底线:这是一个示例,而不是完整的解决方案。

    因为您使用单独的表格进行拖放(您的 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 来劫持绘制下降线,因为涉及这些东西的方法都是私有的。

    编辑我忘了顺便提一下,我担心您可能试图通过拖动 rowTable 中的单元格来重新排列 mainTable 中的整行。该解决方案也没有考虑到这一点,它只是划清界限。为此,您确实需要一个更优雅的解决方案,或者一个使 mainTable 中的行与 rowTable 保持同步的检查。

    【讨论】:

    • @Xeon 和 naugler 感谢有趣的建议,这些建议在我解决问题时非常有用。 (+1) 给你们。
    • 感谢您的回答!我将为您提供最快,完整的工作示例。它帮助我正确计算了线条画,我很欣赏它是一个“单线和一类”的解决方案。但是...我也认为 BasicTableUI 的扩展太冒险了 ;-) 因此我会接受@Xeon 的回答。
    • 哦,我忘了回答你的编辑:这两个表已经在我们的程序中同步,并且在 DnDropping 之后重新排列行数据已经开始工作了。我只是没能划清界限。
    【解决方案3】:

    我的解决方案使用了稍微不同的方法,因为我们想在现有组件之上添加一些自定义绘画。因此,我选择使用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
    }
    

    【讨论】:

    • 很好的答案,也谢谢你。 +1 使用GlassPane,这样可以在两个表上绘制一个透明的 DropLine。但我不认为它必须是FinalGlassPane,我认为dropLocationPropertyChangeListener 更具体。请查看我的答案中的最新代码,以及我在 RowHeader-Table 中使用 getDropLocation() 计算 DropLine-Rectangle 的方式。
    • @bobndrew 谢谢。你说的对。因此,我想问您是否可以发布您使用的解决方案?例如,将其编辑到您的问题中,在最底部添加标题为 EDIT - SOLUTION 的部分,解决方案将在该部分下。
    【解决方案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 }
    

    现在您应该使用您的 TranserHandler 操作您的 mainTable(绘制这条“绿线”):

        @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。但这并不是什么大不了的事情。

    希望这会有所帮助。

    【讨论】:

    • 很好的答案!我会接受它,因为现在我在我的源代码中使用了与您类似的想法(不是DropTargetListener,而是dropLocation 上的PropertyChangeListener)。为什么你认为在canImport()TransferHandler 中进行绘画是个好主意?我正在使用从侦听器可见的 GlassPane。
    • 直接在TransferHandler中作画不是个好主意。我提到了类似的东西:添加组件/更改 L&F UI/使用图层/等。然后重新验证/重新绘制
    猜你喜欢
    • 2012-06-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多