如何在 DragAndDrop 期间在 Main-JTable 上绘制 RowHeader-JTable 的 Dropline?
Posted
技术标签:
【中文标题】如何在 DragAndDrop 期间在 Main-JTable 上绘制 RowHeader-JTable 的 Dropline?【英文标题】:How to paint the Dropline of a RowHeader-JTable on the Main-JTable during a DragAndDrop? 【发布时间】: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
【问题讨论】:
【参考方案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
,我认为dropLocation
的PropertyChangeListener
更具体。请查看我的答案中的最新代码,以及我在 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/使用图层/等。然后重新验证/重新绘制以上是关于如何在 DragAndDrop 期间在 Main-JTable 上绘制 RowHeader-JTable 的 Dropline?的主要内容,如果未能解决你的问题,请参考以下文章
Kendo UI TreeView动态启用/禁用dragAndDrop事件