JDK-4817070 : Must select in JTable then release mouse button before able to drag and drop.
  • Type: Enhancement
  • Component: client-libs
  • Sub-Component: javax.swing
  • Affected Version: 1.4.1
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • OS: windows_xp
  • CPU: x86
  • Submitted: 2003-02-12
  • Updated: 2003-02-12
  • Resolved: 2003-02-12
Related Reports
Duplicate :  
Relates :  
Description
n has terminated with a drop on
	 * the operable part of the drop site for the <code>DropTarget</code>
	 * registered with this listener.
	 * <p>
	 * This method is responsible for undertaking
	 * the transfer of the data associated with the
	 * gesture. The <code>DropTargetDropEvent</code>
	 * provides a means to obtain a <code>Transferable</code>
	 * object that represents the data object(s) to
	 * be transfered.<P>
	 * From this method, the <code>DropTargetListener</code>
	 * shall accept or reject the drop via the
	 * acceptDrop(int dropAction) or rejectDrop() methods of the
	 * <code>DropTargetDropEvent</code> parameter.
	 * <P>
	 * Subsequent to acceptDrop(), but not before,
	 * <code>DropTargetDropEvent</code>'s getTransferable()
	 * method may be invoked, and data transfer may be
	 * performed via the returned <code>Transferable</code>'s
	 * getTransferData() method.
	 * <P>
	 * At the completion of a drop, an implementation
	 * of this method is required to signal the success/failure
	 * of the drop by passing an appropriate
	 * <code>boolean</code> to the <code>DropTargetDropEvent</code>'s
	 * dropComplete(boolean success) method.
	 * <P>
	 * Note: The data transfer should be completed before the call  to the
	 * <code>DropTargetDropEvent</code>'s dropComplete(boolean success)
method.
	 * After that, a call to the getTransferData() method of the
	 * <code>Transferable</code> returned by
	 * <code>DropTargetDropEvent.getTransferable()</code> is guaranteed to
	 * succeed only if the data transfer is local; that is, only if
	 * <code>DropTargetDropEvent.isLocalTransfer()</code> returns
	 * <code>true</code>. Otherwise, the behavior of the call is
	 * implementation-dependent.
	 * <P>
	 * @param dtde the <code>DropTargetDropEvent</code>
	 *
	 */
	public void drop(DropTargetDropEvent dtde)
	{
		System.err.println( "CustomTransferHandler::drop" );
		Component c = dtde.getDropTargetContext().getDropTarget
().getComponent();
		if ( ! ( c instanceof JComponent ) )
		{
			dtde.rejectDrop();
			return;
		}
		JComponent comp = ( JComponent ) c;
		if ( ! ( c instanceof JTable ) || ! ( ( ( JTable ) c ).getModel
() instanceof MyTableModel ) )
		{
			dtde.rejectDrop();
			return;
		}
		dtde.acceptDrop( TransferHandler.MOVE );
		
		//	THIS is such a mess -- you can't do the following
because
		//	getTransferable() throws an (undocumented) exception -
what's that
		//	all about.
//		Transferable t = dtde.getTransferable();
//			if ( !t.isDataFlavourSupported( ROW_ARRAY_FLAVOR ) )
//			{
//				dtde.rejectDrop();
//				return false;
//			}
//		TransferHandler handler = comp.getTransferHandler();
//		if ( null == handler || ! handler.importData( comp, t ) )
//		{
//			dtde.rejectDrop();
//			return;
//		}
		
		Point p = dtde.getLocation();
		JTable table = ( JTable ) comp;
		rowIndex = table.rowAtPoint( p );
		
		//	So you have to do this instead and use the data that's
been
		//	stored in the data member via import data.  Total mess.
		if ( null == data )
		{
			dtde.rejectDrop();
			return;
		}
		MyTableModel model = ( MyTableModel ) table.getModel();
		if ( rowIndex == -1 )
		{
			model.addRows( data );
		}
		else
		{
			model.insertRows( rowIndex, data );
		}
		dtde.acceptDrop( TransferHandler.MOVE );
	}
	
	/** Called if the user has modified
	 * the current drop gesture.
	 * <P>
	 * @param dtde the <code>DropTargetDragEvent</code>
	 *
	 */
	public void dropActionChanged(DropTargetDragEvent dtde)
	{
	}
	
}

---------- END SOURCE ----------

CUSTOMER WORKAROUND :
None yet.
(Review ID: 166235) 
======================================================================


Name: jl125535			Date: 02/11/2003


FULL PRODUCT VERSION :
java version "1.4.1"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.1-b21)
Java HotSpot(TM) Client VM (build 1.4.1-b21, mixed mode)


FULL OPERATING SYSTEM VERSION :
Microsoft Windows XP [Version 5.1.2600]

ADDITIONAL OPERATING SYSTEMS :
Microsoft Windows 2000 [Version 5.00.2195]


A DESCRIPTION OF THE PROBLEM :
If you want to drag and drop between JTables it is first
necessary to make a selection in the first JTable, then
release the mouse button and press again before dragging.
This feels clunky and amateurish.  It would be much better
if you could simply press and drag in one motion a la
Windows Explorer without needing to press then release
then press and drag.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1.  Compile the four supplied source files:
TestFrame.java, MyTableModel.java, DataRow.java,
CustomTransferHandler.java
2.  Change to the directory containing the compiled class
files and execute using:

java -cp . TestFrame

3.  Attempt to drag data from either table to the other.
You will find that it is first necessary to make a
selection, you can't just simply drag a row straight away.

EXPECTED VERSUS ACTUAL BEHAVIOR :
I expected to be able to simply press and drag an item,
just as you can using Windows Explorer.  However I found
that it was first necessary to make a selection, then
release the mouse button, before being able to make the
drag.  Also even under these circumstances recognition of
the drag gesture is somewhat flaky.

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
==============================================================================
TestFrame.java
==============================================================================

/*
 * TestFrame.java
 *
 * Created on October 21, 2002, 4:59 PM
 */

import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.dnd.*;
import java.awt.event.*;

import java.util.TooManyListenersException;

import javax.swing.*;

/**
 *
 * @author  readb
 */
public class TestFrame extends javax.swing.JFrame
{
	
	private static final String [] NAMES	= {
		"John", "Geoff", "Madeleine", "Maria", "Flanders",
		"Homer", "Marge", "Bart", "Lisa", "Weird Baby" };
	
	private JTable source;
	private JTable dest;
	
	private MyTableModel	sourceModel;
	private MyTableModel	destModel;
	
	private Clipboard		clipboard;
	
	/** Creates a new instance of TestFrame */
	public TestFrame()
	{
		clipboard = getToolkit().getSystemClipboard();
		Container c = getContentPane();
		c.setLayout( new BorderLayout( 40, 40 ) );
		
		source = new MyJTable();
		sourceModel = new MyTableModel();
		source.setModel( sourceModel );
		source.setDragEnabled( true );
		CustomTransferHandler handler = new CustomTransferHandler
( "Source handler" );
		source.setTransferHandler( handler );
		try
		{
			source.getDropTarget().addDropTargetListener(
handler );
		}
		catch ( TooManyListenersException tmle )
		{
			tmle.printStackTrace();
		}
		
		dest = new MyJTable();
		destModel = new MyTableModel();
		dest.setModel( destModel );
		dest.setDragEnabled( true );
		handler = new CustomTransferHandler( "Dest handler" );
		dest.setTransferHandler( handler );
		try
		{
			dest.getDropTarget().addDropTargetListener( handler );
		}
		catch ( TooManyListenersException tmle )
		{
			tmle.printStackTrace();
		}
		
		c.add( new JScrollPane( source ), BorderLayout.WEST );
		c.add( new JScrollPane( dest ), BorderLayout.EAST );
		
		populate();
		
	}
	
	private void populate( MyTableModel model )
	{
		for ( int index = 0; index < NAMES.length; ++index )
		{
			model.setRow( index, new DataRow( index + 1, NAMES[
index ] ) );
		}
	}
	
	private void populate()
	{
		populate( sourceModel );
		populate( destModel );
	}
	
	public static void main( String [] args )
	{
		TestFrame app = new TestFrame();
		app.addWindowListener(
			new WindowAdapter() {
				public void windowClosing( WindowEvent we )
				{
					System.exit( 0 );
				}
			}
		);
		app.pack();
		app.setSize( 1000, 600 );
		app.show();
	}
	
	private class MyJTable extends JTable
	{
		public boolean getScrollableTracksViewportHeight()
		{
			Component parent = getParent();

			if (parent instanceof JViewport)
				return parent.getHeight() > getPreferredSize
().height;

			return false;
		}
	}
	
}

==============================================================================
MyTableModel.java
==============================================================================

/*
 * MyTableModel.java
 *
 * Created on October 21, 2002, 4:43 PM
 */

import java.util.ArrayList;

/**
 *
 * @author  readb
 */
public class MyTableModel extends javax.swing.table.AbstractTableModel
{
	
	private static final int		NUMBER			= 0;
	private static final int		NAME			= 1;
	
	private static final String []	COLUMN_HEADINGS	= { "Number", "Name" };
	
	private ArrayList data;
	
	/** Creates a new instance of MyTableModel */
	public MyTableModel()
	{
		super();
		data = new ArrayList();
	}
	
	public int getColumnCount()
	{
		return COLUMN_HEADINGS.length;
	}
	
	public String getColumnName( int index )
	{
		return COLUMN_HEADINGS[ index ];
	}
	
	public Class getColumnClass( int index )
	{
		switch ( index )
		{
			case NUMBER:
				return Integer.class;
				
			case NAME:
				return String.class;
				
			default:
				throw new IllegalArgumentException( "Illegal
column index: " + index );
		}
	}
	
	public int getRowCount()
	{
		return ( null == data ? 0 : data.size() );
	}
	
	public Object getValueAt( int row, int column )
	{
		DataRow dataRow = ( DataRow ) data.get( row );
		switch ( column )
		{
			case NUMBER:
				return new Integer( dataRow.getNumber() );
				
			case NAME:
				return dataRow.getName();
				
			default:
				throw new IllegalArgumentException( "Illegal
column index: " + column );
		}
	}
	
	public void addRow( DataRow row )
	{
		int rowIndex = data.size();
		data.add( row );
		fireTableRowsInserted( rowIndex, rowIndex );
	}
	
	public void addRows( DataRow [] rows )
	{
		int firstRow = data.size();
		for ( int index = 0; index < rows.length; ++index )
		{
			data.add( rows[ index ] );
		}
		fireTableRowsInserted( firstRow, data.size() - 1 );
	}
	
	public void setRow( int index, DataRow row )
	{
		if ( index == data.size() )
		{
			data.add( row );
		}
		else
		{
			data.set( index, row );
		}
		fireTableRowsUpdated( index, index );
	}
	
	public void insertRows( int index, DataRow [] rows )
	{
		for ( int rowIndex = rows.length - 1; rowIndex >= 0; --
rowIndex )
		{
			data.add( index, rows[ rowIndex ] );
		}
		fireTableRowsInserted( index, index + rows.length - 1 );
	}
	
	public DataRow getRow( int index )
	{
		return ( DataRow ) data.get( index );
	}
	
	public DataRow removeRow( int index )
	{
		DataRow retVal = ( DataRow ) data.remove( index );
		fireTableRowsDeleted( index, index );
		return retVal;
	}
	
	public boolean removeRow( DataRow row )
	{
		int index = data.indexOf( row );
		boolean retVal = data.remove( row );
		fireTableRowsDeleted( index, index );
		return retVal;
	}
	
	public void removeRows( DataRow [] rows )
	{
		for ( int index = 0; index < rows.length; ++index )
		{
			data.remove( rows[ index ] );
		}
		fireTableDataChanged();
	}
	
}

==============================================================================
DataRow.java
==============================================================================

/*
 * DataRow.java
 *
 * Created on October 21, 2002, 4:41 PM
 */

import java.io.Serializable;

/**
 *
 * @author  readb
 */
public class DataRow implements Serializable
{
	
	private int		number;
	private String	name;
	
	/** Creates a new instance of DataRow */
	public DataRow( int number, String name )
	{
		this.number = number;
		this.name = name;
	}
	
	public int getNumber()
	{
		return number;
	}
	
	public String getName()
	{
		return name;
	}
	
	public String toString()
	{
		return String.valueOf( number ) + ": " + name;
	}
	
}

==============================================================================
CustomTransferHandler.java
==============================================================================

/*
 * CustomTransferHandler.java
 *
 * Created on October 22, 2002, 8:36 AM
 */

import java.awt.*;

import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;

import java.awt.dnd.*;

import java.awt.event.InputEvent;

import java.io.IOException;

import java.util.Arrays;

import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JTable;
import javax.swing.TransferHandler;

/**
 *
 * @author  readb
 */
public class CustomTransferHandler
			extends TransferHandler
			implements Transferable, ClipboardOwner,
DropTargetListener
{
	
	public static final DataFlavor	ROW_ARRAY_FLAVOR	= new
DataFlavor( DataRow[].class, "Multiple rows of data" );
		
	private String			name;
	private ImageIcon		myIcon;
	private	DataRow []		data;
	private boolean			clipboardOwner
	= false;
	private int				rowIndex
			= -1;
	
	/** Creates a new instance of CustomTransferHandler */
	public CustomTransferHandler( String name )
	{
		this.name = name;
	}
	
	public boolean canImport( JComponent comp, DataFlavor []
transferFlavors )
	{
		System.err.println( "CustomTransferHandler::canImport" );
		if ( comp instanceof JTable && ( ( JTable ) comp ).getModel()
instanceof MyTableModel )
		{
			for ( int index = 0; index < transferFlavors.length;
++index )
			{
				if ( ! transferFlavors[ index ].equals(
ROW_ARRAY_FLAVOR ) )
				{
					return false;
				}
			}
			return true;
		}
		else
		{
			return false;
		}
	}
	
	protected Transferable createTransferable( JComponent c )
	{
		System.err.println
( "CustomTransferHandler::createTransferable" );
		if ( ! ( c instanceof JTable ) || ! ( ( ( JTable ) c ).getModel
() instanceof MyTableModel ) )
		{
			return null;
		}
		this.data = null;
		
		JTable			table	= ( JTable ) c;
		MyTableModel	model	= ( MyTableModel ) table.getModel();
		Clipboard		cb		= table.getToolkit
().getSystemClipboard();
		cb.setContents( this, this );
		clipboardOwner = true;
		
		int [] selectedRows = table.getSelectedRows();
		Arrays.sort( selectedRows );
		data = new DataRow[ selectedRows.length ];
		for ( int index = 0; index < data.length; ++index )
		{
			data[ index ] = model.getRow( selectedRows[ index ] );
		}
		
		return this;
	}
	
	public void exportAsDrag( JComponent comp, InputEvent e, int action )
	{
		super.exportAsDrag( comp, e, action );
		Clipboard		cb		= comp.getToolkit
().getSystemClipboard();
		cb.setContents( this, this );
	}
	
	protected void exportDone( JComponent source, Transferable data, int
action )
	{
		System.err.println( "CustomTransferHandler::exportDone" );
		if ( TransferHandler.MOVE == action && source instanceof
JTable && ( ( JTable ) source ).getModel() instanceof MyTableModel )
		{
			JTable table = ( JTable ) source;
			MyTableModel model = ( MyTableModel ) table.getModel();
			int [] selected = table.getSelectedRows();
			for ( int index = selected.length - 1; index >= 0; --
index )
			{
				model.removeRow( selected[ index ] );
			}
		}
	}
	
	public void exportToClipboard( JComponent comp, Clipboard clip, int
action )
	{
		System.err.println
( "CustomTransferHandler::exportToClipboard" );
	}
	
	public int getSourceActions( JComponent c )
	{
		System.err.println
( "CustomTransferHandler::getSourceActions" );
		if ( ( c instanceof JTable ) && ( ( JTable ) c ).getModel()
instanceof MyTableModel )
		{
			return MOVE;
		}
		else
		{
			return NONE;
		}
	}

	/**
	 *	I've commented this out because it doesn't appear to work in
any case.
	 *	The image isn't null but as far as I can tell this method is
never
	 *	invoked.
	 */
	public Icon getVisualRepresentation( Transferable t )
	{
		System.err.println
( "CustomTransferHandler::getVisualRepresentation" );
		if ( t instanceof CustomTransferHandler )
		{
			if ( null == myIcon )
			{
				try
				{
					myIcon = new ImageIcon( getClass
().getClassLoader().getResource( "dndrender.gif" ) );
				}
				catch ( Exception e )
				{
					System.err.println
( "CustomTransferHandler::getVisualRepresentation: exception loading image" );
					e.printStackTrace();
				}
				if ( null == myIcon )
				{
					System.err.println
( "CustomTransferHandler::getVisualRepresentation: myIcon is still NULL" );
				}
			}
			return myIcon;
		}
		else
		{
			return null;
		}
	}
	
	public boolean importData( JComponent comp, Transferable t )
	{
		System.err.println( "CustomTransferHandler::importData" );
		super.importData( comp, t );
		if ( ! ( comp instanceof JTable ) )
		{
			return false;
		}
		if ( ! ( ( ( JTable ) comp ).getModel() instanceof
MyTableModel ) )
		{
			return false;
		}
		if ( clipboardOwner )
		{
			return false;
		}
		if ( !t.isDataFlavorSupported( ROW_ARRAY_FLAVOR ) )
		{
			return false;
		}

		try
		{
			data = ( DataRow [] ) t.getTransferData(
ROW_ARRAY_FLAVOR );
			return true;
		}
		catch ( IOException ioe )
		{
			data = null;
			return false;
		}
		catch ( UnsupportedFlavorException ufe )
		{
			data = null;
			return false;
		}
	}
	
	public Object getTransferData(DataFlavor flavor) throws
UnsupportedFlavorException, IOException
	{
		System.err.println( "MyTransferable::getTransferData" );
		if ( flavor.equals( ROW_ARRAY_FLAVOR ) )
		{
			return data;
		}
		else
		{
			throw new UnsupportedFlavorException( flavor );
		}
	}
	
	public DataFlavor[] getTransferDataFlavors()
	{
		System.err.println( "MyTransferable::getTransferDataFlavors" );
		DataFlavor [] flavors = new DataFlavor[ 1 ];
		flavors[ 0 ] = ROW_ARRAY_FLAVOR;
		return flavors;
	}
	
	public boolean isDataFlavorSupported( DataFlavor flavor )
	{
		System.err.println( "MyTransferable::isDataFlavorSupported" );
		return flavor.equals( ROW_ARRAY_FLAVOR );
	}
	
	public void lostOwnership( Clipboard clipboard, Transferable
transferable )
	{
		clipboardOwner = false;
	}
	
	/** Called while a drag operation is ongoing, when the mouse pointer
enters
	 * the operable part of the drop site for the <code>DropTarget</code>
	 * registered with this listener.
	 *
	 * @param dtde the <code>DropTargetDragEvent</code>
	 *
	 */
	public void dragEnter(DropTargetDragEvent dtde)
	{
	}
	
	/** Called while a drag operation is ongoing, when the mouse pointer
has
	 * exited the operable part of the drop site for the
	 * <code>DropTarget</code> registered with this listener.
	 *
	 * @param dte the <code>DropTargetEvent</code>
	 *
	 */
	public void dragExit(DropTargetEvent dte)
	{
	}
	
	/** Called when a drag operation is ongoing, while the mouse pointer
is still
	 * over the operable part of the drop site for the
<code>DropTarget</code>
	 * registered with this listener.
	 *
	 * @param dtde the <code>DropTargetDragEvent</code>
	 *
	 */
	public void dragOver(DropTargetDragEvent dtde)
	{
	}
	
	/** Called when the drag operatio

Comments
EVALUATION Name: dsR10078 Date: 02/11/2003 This looks like a Swing issue, as the problem is in the Swing drag gesture recognizer. ###@###.### 2003-02-12 ====================================================================== This is a duplicate of the very (un)popular bug 4521075. ###@###.### 2003-02-12
12-02-2003