Frage

I have a simple task to implement, it works quite ok, but I am facing a very tricky issue regarding custom drag images in Swing. The idea behind the task is just to allow the user to perform some DND between a list component and a text component, but during the drag operation to display following the mouse the exact same drawing as the renderer inside the list.

For this I use the cell renderer for the selected elements in the list and paint it over a temporary image. Then send this image to the TransferHandler and everything is fine. The problem is evident when I modify the size of the overall component and make it larger. After a certain extent, the picture that is draw no longer appears correct, but instead it has some gradient applied to it, making the content difficult to read. Following is a snippet of code that reproduces the issue:

import java.awt.Color;
import java.awt.Component;
import java.awt.GridLayout;

import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListModel;
import javax.swing.DropMode;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;

public class BasicTextListDND {

    private JList<String> makeList() {
        DefaultListModel<String> m = new DefaultListModel<String>();
        for(int i = 0; i<10; i++) {
            m.addElement("Element "+i);            
        }
        JList<String> list = new JList<String>(m);

        list.setTransferHandler(new BasicListTransferHandler());
        list.setDropMode(DropMode.ON_OR_INSERT);
        list.setDragEnabled(true);
        list.setCellRenderer(new DefaultListCellRenderer() {

            /**
             * Comment for <code>serialVersionUID</code>
             */
            private static final long serialVersionUID = 1L;

            /** {@inheritDoc} */
            public Component getListCellRendererComponent(JList<?> list, Object value, int index,
                    boolean isSelected, boolean cellHasFocus) {
                Component listCellRendererComponent = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
                if (cellHasFocus == false && isSelected == false) { 
                    if (index % 2 == 0) {
                        listCellRendererComponent.setBackground(Color.RED);
                    } else if (index % 3==0) {
                        listCellRendererComponent.setBackground(Color.GREEN);
                    } else {
                        listCellRendererComponent.setBackground(Color.BLUE);
                    }
                }
                return listCellRendererComponent;
            }

        });
        return list;
    }

    private JTextArea makeTextArea() {
        JTextArea textArea = new JTextArea("Drag here from JList!");
        return textArea;
    }

    public JComponent makeUI() {
        JPanel panel = new JPanel(new GridLayout(2,1));
        panel.add(new JScrollPane(makeTextArea()));
        panel.add(new JScrollPane(makeList()));
        return panel;
    }

    private static void createAndShowGUI() {
        JFrame f = new JFrame("BasicDnD");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        BasicTextListDND app = new BasicTextListDND();
        JComponent appContent = app.makeUI();
        f.setContentPane(appContent);
        f.setSize(600, 320);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

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


/**
 * 
 */
public class BasicListTransferHandler extends TransferHandler {

    /**
     * Comment for <code>serialVersionUID</code>
     */
    private static final long serialVersionUID = 1L;

    @Override 
    public boolean canImport(TransferHandler.TransferSupport info) {
        if (!info.isDataFlavorSupported(DataFlavor.stringFlavor)) {
            return false;
        }
        JList.DropLocation dl = (JList.DropLocation)info.getDropLocation();
        if (dl.getIndex() == -1) {
            return false;
        }
        return true;
    }

    @Override 
    public int getSourceActions(JComponent c) {
        BufferedImage dragImage = makeImageFromString(c);
        if (dragImage != null) {
            setDragImage(dragImage);            
            Point mousePosition = c.getMousePosition();
            if (mousePosition != null) {
                setDragImageOffset(mousePosition);
            }
        }
        return COPY;
    }

    private final JPanel tempDrawPanel = new JPanel();

    private BufferedImage createDragImage(JList<String> list) {
        int width = 0;
        int height = 0;
        int[] selectedIndices = list.getSelectedIndices();
        for(int i =0; i<selectedIndices.length; i++){
            int idx = selectedIndices[i];
            Rectangle cellBounds = list.getCellBounds(idx, idx);
            height += cellBounds.height;
            width = Math.max(width, cellBounds.width); // we want to create a drag image as big as the largest cell
        }
        BufferedImage br = null;
        if (width > 0 && height > 0) {
            br = list.getGraphicsConfiguration().createCompatibleImage(width, height, Transparency.TRANSLUCENT);
        }
        return br;
    }

    private BufferedImage makeImageFromString(JComponent src) {
        JList<String> sourceList = (JList<String>)src;
        BufferedImage  br = createDragImage(sourceList);
        if (br != null) {
            int[] selectedIndices = sourceList.getSelectedIndices();            
            int yD = 0;
            Graphics g = br.getGraphics();
            try{
                for(int idx: selectedIndices) {
                    ListCellRenderer<? super String> cellRenderer = sourceList.getCellRenderer();
                    String valueAt = sourceList.getModel().getElementAt(idx);
                    Component c = cellRenderer.getListCellRendererComponent(sourceList, valueAt, idx, false, false);
                    Rectangle itemCellBounds = sourceList.getCellBounds(idx, idx);                
                    SwingUtilities.paintComponent(g, c, tempDrawPanel, itemCellBounds.x, yD, itemCellBounds.width, itemCellBounds.height);
                    yD = itemCellBounds.y+itemCellBounds.height;
                }
            }finally {
                g.dispose();
            }
            br.coerceData(true);
        }
        return br;
    }

    @Override 
    protected Transferable createTransferable(JComponent c) {
        JList<String> list = (JList<String>)c;
        List<String> selectedValuesList = list.getSelectedValuesList();                
        StringBuffer buff = new StringBuffer();
        for (int i = 0; i < selectedValuesList.size(); i++) {
            String val = selectedValuesList.get(i);
            buff.append(val == null ? "" : val.toString());
            if (i != selectedValuesList.size()- 1) {
                buff.append("\n");
            }
        }
        return new StringSelection(buff.toString());
    }
}

The problem lies, I think, somewhere in the makeImageFromString method, but after 2 days of digging through Swing/AWT libraries and understanding how the drag image is drawn, I still fail to fix this issue. The bottom-line question: is there any obscure logic in AWT that applies this gradient if the drag image is over a certain size?

Any help would be greatly appreciated! Marius.

War es hilfreich?

Lösung

How about translates the origin of the graphics context:

//SwingUtilities.paintComponent(g, c, tempDrawPanel, itemCellBounds.x, yD, itemCellBounds.width, itemCellBounds.height);
//yD = itemCellBounds.y+itemCellBounds.height;
SwingUtilities.paintComponent(g, c, tempDrawPanel, 0, 0, itemCellBounds.width, itemCellBounds.height);
g.translate(0, itemCellBounds.height);

Edit:

@user3619696: I misunderstood.

I would guess that the "blurred drag image" opacity depend on the Windows desktop theme. So try using translucent JWindow instead of TransferHandler#setDragImage(...).

enter image description here

import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.dnd.*;
import java.awt.image.*;
import java.util.List;
import javax.swing.*;

public class BasicTextListDND2 {

    private JList<String> makeList() {
        DefaultListModel<String> m = new DefaultListModel<String>();
        for(int i = 0; i<10; i++) {
            m.addElement("Element "+i);
        }
        JList<String> list = new JList<String>(m);

        list.setTransferHandler(new BasicListTransferHandler());
        list.setDropMode(DropMode.ON_OR_INSERT);
        list.setDragEnabled(true);
        list.setCellRenderer(new DefaultListCellRenderer() {

            /**
             * Comment for <code>serialVersionUID</code>
             */
            private static final long serialVersionUID = 1L;

            /** {@inheritDoc} */
            public Component getListCellRendererComponent(JList<?> list, Object value, int index,
                    boolean isSelected, boolean cellHasFocus) {
                Component listCellRendererComponent = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
                if (cellHasFocus == false && isSelected == false) { 
                    if (index % 2 == 0) {
                        listCellRendererComponent.setBackground(Color.RED);
                    } else if (index % 3==0) {
                        listCellRendererComponent.setBackground(Color.GREEN);
                    } else {
                        listCellRendererComponent.setBackground(Color.BLUE);
                    }
                }
                return listCellRendererComponent;
            }

        });
        return list;
    }

    private JTextArea makeTextArea() {
        JTextArea textArea = new JTextArea("Drag here from JList!");
        return textArea;
    }

    public JComponent makeUI() {
        JPanel panel = new JPanel(new GridLayout(2,1));
        panel.add(new JScrollPane(makeTextArea()));
        panel.add(new JScrollPane(makeList()));
        return panel;
    }

    private static void createAndShowGUI() {
        JFrame f = new JFrame("BasicDnD");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        BasicTextListDND2 app = new BasicTextListDND2();
        JComponent appContent = app.makeUI();
        f.setContentPane(appContent);
        f.setSize(600, 320);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

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


/**
 * 
 */
class BasicListTransferHandler extends TransferHandler {

    /**
     * Comment for <code>serialVersionUID</code>
     */
    private static final long serialVersionUID = 1L;

    private final JLabel label = new JLabel() {
        @Override public boolean contains(int x, int y) {
            return false;
        }
    };
    private final JWindow window = new JWindow();
    public BasicListTransferHandler() {
        super();
        window.add(label);
        //window.setBackground(new Color(0, true));
        window.setOpacity(.8f);
        DragSource.getDefaultDragSource().addDragSourceMotionListener(new DragSourceMotionListener() {
            @Override public void dragMouseMoved(DragSourceDragEvent dsde) {
                Point pt = dsde.getLocation();
                pt.translate(10, 10); // offset
                if (!window.isVisible()) {
                    window.setVisible(true);
                }
                window.setLocation(pt);
            }
        });
    }
    @Override protected void exportDone(JComponent c, Transferable data, int action) {
        super.exportDone(c, data, action);
        window.setVisible(false);
    }

    @Override 
    public boolean canImport(TransferHandler.TransferSupport info) {
        if (!info.isDataFlavorSupported(DataFlavor.stringFlavor)) {
            return false;
        }
        JList.DropLocation dl = (JList.DropLocation)info.getDropLocation();
        if (dl.getIndex() == -1) {
            return false;
        }
        return true;
    }

    @Override 
    public int getSourceActions(JComponent c) {
        BufferedImage dragImage = makeImageFromString(c);
        if (dragImage != null) {
            //setDragImage(dragImage);            
            //Point mousePosition = c.getMousePosition();
            //if (mousePosition != null) {
            //    setDragImageOffset(mousePosition);
            //}
            label.setIcon(new ImageIcon(dragImage));
            window.setLocation(-2000, -2000);
            window.pack();
        }
        return COPY;
    }

    private final JPanel tempDrawPanel = new JPanel();

    private BufferedImage createDragImage(JList<String> list) {
        int width = 0;
        int height = 0;
        int[] selectedIndices = list.getSelectedIndices();
        for(int i =0; i<selectedIndices.length; i++){
            int idx = selectedIndices[i];
            Rectangle cellBounds = list.getCellBounds(idx, idx);
            height += cellBounds.height;
            width = Math.max(width, cellBounds.width); // we want to create a drag image as big as the largest cell
        }
        BufferedImage br = null;
        if (width > 0 && height > 0) {
            br = list.getGraphicsConfiguration().createCompatibleImage(width, height, Transparency.TRANSLUCENT);
        }
        return br;
    }

    private BufferedImage makeImageFromString(JComponent src) {
        JList<String> sourceList = (JList<String>)src;
        BufferedImage br = createDragImage(sourceList);
        if (br != null) {
            int[] selectedIndices = sourceList.getSelectedIndices();
            int yD = 0;
            Graphics g = br.getGraphics();
            try{
                for(int idx: selectedIndices) {
                    ListCellRenderer<? super String> cellRenderer = sourceList.getCellRenderer();
                    String valueAt = sourceList.getModel().getElementAt(idx);
                    Component c = cellRenderer.getListCellRendererComponent(sourceList, valueAt, idx, false, false);
                    Rectangle itemCellBounds = sourceList.getCellBounds(idx, idx);
                    //SwingUtilities.paintComponent(g, c, tempDrawPanel, itemCellBounds.x, itemCellBounds.y + yD, itemCellBounds.width, itemCellBounds.height);
                    //yD = itemCellBounds.y+itemCellBounds.height;
                    SwingUtilities.paintComponent(g, c, tempDrawPanel, 0, 0, itemCellBounds.width, itemCellBounds.height);
                    g.translate(0, itemCellBounds.height);
                }
            }finally {
                g.dispose();
            }
            br.coerceData(true);
        }
        return br;
    }

    @Override 
    protected Transferable createTransferable(JComponent c) {
        JList<String> list = (JList<String>)c;
        List<String> selectedValuesList = list.getSelectedValuesList();
        StringBuffer buff = new StringBuffer();
        for (int i = 0; i < selectedValuesList.size(); i++) {
            String val = selectedValuesList.get(i);
            buff.append(val == null ? "" : val.toString());
            if (i != selectedValuesList.size()- 1) {
                buff.append("\n");
            }
        }
        return new StringSelection(buff.toString());
    }
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top