Custom deletion Container for Transferable and Transferhandler on JList

戏子无情 提交于 2020-01-25 06:56:05

问题


I tried to fix this issue for the past week but somehow I cant seem to find a solution. There is not a lot of information about this topic so its hard to find examples or code to look at.

What I have here is a JList which uses a custom TransferHandler that creates a custom Transferable, for reference here's the code of the involved classes:

Transferable:

package org.dinhware.swing.special;

import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;

/**
 * Created by: Niklas
 * Date: 20.10.2017
 * Alias: Dinh
 * Time: 20:03
 */

public class GenericTransferable<T> implements Transferable {
    static DataFlavor FLAVOR;
    private T object;

    GenericTransferable(T object) {
        GenericTransferable.FLAVOR = new DataFlavor(object.getClass(), object.getClass().getCanonicalName());
        this.object = object;
    }

    @Override
    public DataFlavor[] getTransferDataFlavors() {
        return new DataFlavor[]{FLAVOR};
    }

    @Override
    public boolean isDataFlavorSupported(DataFlavor flavor) {
        return flavor.equals(FLAVOR);
    }

    @Override
    public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
        return object;
    }
}

TransferHandler:

package org.dinhware.swing.special;

import javax.swing.*;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;

/**
 * Created by: Niklas
 * Date: 19.10.2017
 * Alias: Dinh
 * Time: 18:54
 */

@SuppressWarnings("unchecked")
public class HListItemTransferHandler<T> extends TransferHandler {

    @Override
    protected Transferable createTransferable(JComponent component) {
        JList<T> list = (JList<T>) component;
        index = list.getSelectedIndex();
        T transferredObject = list.getSelectedValue();
        return new GenericTransferable<>(transferredObject);
    }

    @Override
    public boolean canImport(TransferSupport info) {
        return info.isDataFlavorSupported(GenericTransferable.FLAVOR);
    }

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

    @Override
    public boolean importData(TransferSupport info) {
        if (!canImport(info)) {
            return false;
        }

        JList<Object> target = (JList<Object>) info.getComponent();
        JList.DropLocation dl = (JList.DropLocation) info.getDropLocation();
        DefaultListModel<Object> listModel = (DefaultListModel<Object>) target.getModel();
        int index = dl.getIndex();
        int max = listModel.getSize();

        if (index < 0 || index > max)
            index = max;

        addIndex = index;

        try {
            Object object = info.getTransferable().getTransferData(GenericTransferable.FLAVOR);
            listModel.add(index, object);
            target.addSelectionInterval(index, index);
            return moveAllowed = true;
        } catch (UnsupportedFlavorException | IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    protected void exportDone(JComponent c, Transferable data, int action) {
        if (moveAllowed)
            cleanup(c, action == MOVE, false);

    }

    private void cleanup(JComponent component, boolean remove, boolean bin) {
        if (remove && index != -1) {
            JList<T> source = (JList<T>) component;
            DefaultListModel<T> model = (DefaultListModel<T>) source.getModel();
            int removeAt = index > addIndex ? index + 1 : index;
            model.remove(bin ? removeAt - 1 : removeAt);
        }

        index = -1;
        addIndex = -1;
        moveAllowed = false;
    }

    private int index = -1;
    private int addIndex = -1;
    private boolean moveAllowed = false;
}

HBin

package org.dinhware.swing.child;

import org.dinhware.swing.special.HListItemTransferHandler;

import javax.swing.*;

/**
 * Created by: Niklas
 * Date: 20.10.2017
 * Alias: Dinh
 * Time: 19:57
 */

public class HBin<T> extends HImageLabel {
    public HBin(String text, Icon image, int distance) {
        super(text, image, distance);
        setTransferHandler(new HListItemTransferHandler<T>());
    }
}

And a visualization of how it should work, sadly the container always disappears even when not dragged onto the HBin Container. I thought it was working the entire time until I accidentally moved it outside of my Frame and it still disappeared. The Code above only allows dragging/dropping inside of the List which is intended.

My Question is how to add the functionality to properly make a Container only disappear when dragged onto the HBin Container

The first part of Code I used was this

@Override
protected void exportDone(JComponent c, Transferable data, int action) {
    if (moveAllowed) cleanup(c, action == MOVE, false);
    else try {
        if (data.getTransferData(GenericTransferable.FLAVOR) instanceof RewardItem) {
            cleanup(c, true, true);
        }
    } catch (UnsupportedFlavorException | IOException e) {
        e.printStackTrace();
    }
}

My logic behind it was that both the List and HBin shared the same Type (RewardItem) which I could compare, later I realized (after making a more generic version of that method) that data will always be of the type RewardItem and will always result in a cleanup call. This results in the bug I am currently still facing.

The approach I took earlier today really made me question my mind and also made me do this post. I added a boolean to the TransferHandler called bin which was false by default. After the canImport check in importData I added bin = info.getComponent() instanceof HBin which I thought should work. But this field always stood false. I went ahead and added a log for it

System.out.println("IMPORT");
if (info.getComponent() instanceof HBin) {
    System.out.println("bin");
    return bin = true;
}

It ended up printing IMPORT followed by bin. After importData exportData is called in which I then logged the value of bin, which for whatever reason was now false again. Meanwhile the moveAllowed field seems to change.

This was my full modified TransferHandler

package org.dinhware.swing.special;

import org.dinhware.swing.child.HBin;

import javax.swing.*;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;

/**
 * Created by: Niklas Date: 19.10.2017 Alias: Dinh Time: 18:54
 */

@SuppressWarnings("unchecked")
public class HListItemTransferHandler<T> extends TransferHandler {

    @Override
    protected Transferable createTransferable(JComponent component) {
        System.out.println("CREATE");
        JList<T> list = (JList<T>) component;
        index = list.getSelectedIndex();
        T transferredObject = list.getSelectedValue();
        return new GenericTransferable<>(transferredObject);
    }

    @Override
    public boolean canImport(TransferSupport info) {
        return info.isDataFlavorSupported(GenericTransferable.FLAVOR);
    }

    @Override
    public int getSourceActions(JComponent c) {
        System.out.println("ACTION");
        return MOVE;
    }

    @Override
    public boolean importData(TransferSupport info) {
        System.out.println("IMPORT");
        if (!canImport(info)) {
            return false;
        }
        if (info.getComponent() instanceof HBin) {
            System.out.println("bin");
            return bin = true;
        }

        JList<Object> target = (JList<Object>) info.getComponent();
        JList.DropLocation dl = (JList.DropLocation) info.getDropLocation();
        DefaultListModel<Object> listModel = (DefaultListModel<Object>) target.getModel();
        int index = dl.getIndex();
        int max = listModel.getSize();

        if (index < 0 || index > max)
            index = max;

        addIndex = index;

        try {
            Object object = info.getTransferable().getTransferData(GenericTransferable.FLAVOR);
            listModel.add(index, object);
            target.addSelectionInterval(index, index);
            return moveAllowed = true;
        } catch (UnsupportedFlavorException | IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    protected void exportDone(JComponent c, Transferable data, int action) {
        System.out.println("EXPORT " + moveAllowed + "/" + bin);
        if (moveAllowed)
            cleanup(c, action == MOVE, false);
        else
            cleanup(c, true, true);

    }

    private void cleanup(JComponent component, boolean remove, boolean bin) {
        System.out.println("CLEAN");
        if (remove && index != -1) {
            JList<T> source = (JList<T>) component;
            DefaultListModel<T> model = (DefaultListModel<T>) source.getModel();
            int removeAt = index > addIndex ? index + 1 : index;
            model.remove(bin ? removeAt - 1 : removeAt);
        }

        index = -1;
        addIndex = -1;
        moveAllowed = false;
    }

    private int index = -1;
    private int addIndex = -1;
    private boolean moveAllowed = false, bin = false;
}   

When moving inside of the List everything works fine (prints)

ACTION
CREATE
IMPORT
EXPORT true/false
CLEAN

But when dropping onto the HBin Container I cant explain whats going on (prints)

ACTION
CREATE
IMPORT
bin
EXPORT false/false

I am farily sure that it should be false/true

Now I am stuck, not able to make the Container only disappear when dropped onto HBin whilst also being confused about the field value not changing when it clearly logs that it has been set to true.

Please.. help...


回答1:


Drag'n'Drop is complicated and it's not helped by the fact that there are at least two ways to do it.

D'n'D revolves around the idea of "wrapping" an object up in a "transferable" package, which can be "imported" via a number of different means (i.e. DataFlavors)

So, in this example, I've focused only on removing items from the JList, to do this, I created a Trash object which actually maintains a reference to the item to be removed (I also created a ListTrash object to demonstrate at least one way you could pass more information)

This object is then wrapped in a TrashTransferable when a drag occurs on the JList

The main reason for having a Trash object, it it allows the DataFlavor to be standardised. The "trash can" only cares about Trash objects, nothing else. This is especially important if you have more TransferHandlers doing more operations

One other thing I did was create two TransferHandlers. One for the "trash can" and one for the JList, the main reason for this is it isolates the functionality each handler wants to perform and reduces the complexity, as you're not also trying to determine which object is trying to perform which operation.

The example also has another component which is not doing much at all, so it can reject the drop operation.

If you have another components using TransferHandlers, then those need to reject the TrashTransferable.FLAVOR

import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.io.IOException;
import javax.swing.DefaultListModel;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.TransferHandler;
import javax.swing.TransferHandler.TransferSupport;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame("Test");
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
            setLayout(new GridBagLayout());
            DefaultListModel<String> model = new DefaultListModel<>();
            model.addElement("Cooks_Assistant");
            model.addElement("Romeo_and_Juliet");
            model.addElement("Sheep_Shearer");

            JList list = new JList(model);
            list.setTransferHandler(new HListItemTransferHandler());
            list.setDragEnabled(true);

            JLabel noDrop = new JLabel("No drop here", JLabel.CENTER);
            JLabel trash = new JLabel("All your trash belong to us", JLabel.CENTER);
            trash.setTransferHandler(new BinTransferHandler());

            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridx = 0;
            gbc.gridy = 0;
            gbc.weightx = 0.5;
            gbc.weighty = 1;
            gbc.fill = GridBagConstraints.BOTH;
            gbc.insets = new Insets(4, 4, 4, 4);

            add(new JScrollPane(list), gbc);

            gbc.gridx++;
            add(noDrop, gbc);

            gbc.gridx = 0;
            gbc.gridy++;
            gbc.gridwidth = GridBagConstraints.REMAINDER;
            add(trash, gbc);

        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(300, 300);
        }

    }

    public class BinTransferHandler extends TransferHandler {

        @Override
        public boolean canImport(TransferSupport info) {
            return info.isDataFlavorSupported(TrashTransferable.FLAVOR);
        }

        @Override
        public int getSourceActions(JComponent c) {
            System.out.println("ACTION");
            return DnDConstants.ACTION_MOVE;
        }

        @Override
        public boolean importData(TransferSupport support) {
            if (!canImport(support)) {
                return false;
            }
            // Check target component
            Transferable transferable = support.getTransferable();
            try {
                Trash trash = (Trash) transferable.getTransferData(TrashTransferable.FLAVOR);
                Object item = trash.getItem();
                System.out.println(">> Trash " + item);

                return true;
            } catch (UnsupportedFlavorException | IOException ex) {
                ex.printStackTrace();
            }
            return false;
        }

    }

    public class HListItemTransferHandler<T> extends TransferHandler {

        @Override
        protected Transferable createTransferable(JComponent component) {
            System.out.println("createTransferable");
            JList<T> list = (JList<T>) component;
            int index = list.getSelectedIndex();
            T transferredObject = list.getSelectedValue();
            return new TrashTransferable(new ListTrash<>(list, index, transferredObject));
        }

        @Override
        public boolean canImport(TransferSupport info) {
            return info.isDataFlavorSupported(TrashTransferable.FLAVOR);
        }

        @Override
        public int getSourceActions(JComponent c) {
            return DnDConstants.ACTION_MOVE;
        }

        @Override
        public boolean importData(TransferSupport info) {
            JList<Object> target = (JList<Object>) info.getComponent();
            JList.DropLocation dl = (JList.DropLocation) info.getDropLocation();
            DefaultListModel<Object> listModel = (DefaultListModel<Object>) target.getModel();
            int index = dl.getIndex();
            int max = listModel.getSize();

            if (index < 0 || index > max) {
                index = max;
            }

            try {
                Object object = info.getTransferable().getTransferData(DataFlavor.stringFlavor);
                listModel.add(index, object);
                target.addSelectionInterval(index, index);
                return true;
            } catch (UnsupportedFlavorException | IOException e) {
                e.printStackTrace();
            }
            return false;
        }

        @Override
        protected void exportDone(JComponent c, Transferable data, int action) {
            System.out.println("Export data");
            try {
                if (action != MOVE) {
                    return;
                }
                if (!(c instanceof JList)) {
                    return;
                }
                JList list = (JList) c;
                if (!(list.getModel() instanceof DefaultListModel)) {
                    return;
                }
                DefaultListModel model = (DefaultListModel) list.getModel();
                if (!(data instanceof TrashTransferable)) {
                    return;
                }
                Object transferData = data.getTransferData(TrashTransferable.FLAVOR);
                if (transferData == null || !(transferData instanceof Trash)) {
                    return;
                }
                Trash trash = (Trash) transferData;
                Object item = trash.item;
                int index = model.indexOf(item);
                if (index == -1) {
                    return;
                }
                model.remove(index);
            } catch (UnsupportedFlavorException | IOException ex) {
                ex.printStackTrace();
            }
        }
    }

    public static class ListTrash<T> extends Trash<T> {

        private JList list;
        private int index;

        public ListTrash(JList list, int index, T item) {
            super(item);
            this.list = list;
            this.index = index;
        }

        public JList getList() {
            return list;
        }

        public int getIndex() {
            return index;
        }

    }

    public static class Trash<T> {

        private T item;

        public Trash(T item) {
            this.item = item;
        }

        public T getItem() {
            return item;
        }

    }

    public static class TrashTransferable<T> implements Transferable {

        public static final DataFlavor FLAVOR = new DataFlavor(Trash.class, "Trash");

        private Trash<T> trash;

        TrashTransferable(Trash<T> object) {
            trash = object;
        }

        @Override
        public DataFlavor[] getTransferDataFlavors() {
            return new DataFlavor[]{FLAVOR};
        }

        @Override
        public boolean isDataFlavorSupported(DataFlavor flavor) {
            return flavor.equals(flavor);
        }

        @Override
        public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
            return trash;
        }
    }
}


来源:https://stackoverflow.com/questions/47253790/custom-deletion-container-for-transferable-and-transferhandler-on-jlist

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!