Пользовательский Контейнер удаления для Transferable и Transferhandler в JList
Я пытался решить эту проблему на прошлой неделе, но почему-то не могу найти решение. По этой теме не так много информации, поэтому трудно найти примеры или код, на которые можно посмотреть.
Здесь у меня есть JList, который использует пользовательский TransferHandler, который создает пользовательский 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>());
}
}
И визуализация того, как он должен работать, к сожалению, контейнер всегда исчезает, даже если его не перетащить в контейнер HBin. Я думал, что это работает все время, пока я случайно не переместил его за пределы своего кадра, и он все еще исчез. Код выше позволяет только перетаскивать внутри списка, который предназначен.
Мой вопрос заключается в том, как добавить функциональность, чтобы контейнер полностью исчезал только при перетаскивании на контейнер HBin.
Первая часть кода, которую я использовал, была такой
@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();
}
}
Моя логика заключалась в том, что и List, и HBin использовали один и тот же тип (RewardItem), который я мог сравнить, позже я понял (после создания более общей версии этого метода), что данные всегда будут иметь тип RewardItem и всегда будут приводить к в уборке вызова. Это приводит к ошибке, с которой я все еще сталкиваюсь.
Подход, который я использовал ранее сегодня, действительно заставил меня задуматься, а также заставил меня сделать этот пост. Я добавил логическое значение в TransferHandler с именем bin, которое по умолчанию было false. После проверки canImport в importData я добавил bin = info.getComponent() instanceof HBin
который я думал должен работать. Но это поле всегда оставалось ложным. Я пошел дальше и добавил журнал для этого
System.out.println("IMPORT");
if (info.getComponent() instanceof HBin) {
System.out.println("bin");
return bin = true;
}
Это закончилось печатью ИМПОРТА, сопровождаемого беном. После importData вызывается exportData, в котором я затем записал значение bin, которое по какой-то причине снова стало ложным. Между тем поле moveAllowed, похоже, меняется.
Это был мой полностью измененный 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;
}
При перемещении внутри списка все работает нормально (печатает)
ACTION
CREATE
IMPORT
EXPORT true/false
CLEAN
Но когда я падаю на контейнер HBin, я не могу объяснить, что происходит (печатает)
ACTION
CREATE
IMPORT
bin
EXPORT false/false
Я уверен, что это должно быть ложно / верно
Теперь я застрял, не в состоянии заставить Контейнер исчезать только при падении на HBin, и в то же время путаюсь с тем, что значение поля не меняется, когда оно явно регистрирует, что оно установлено в true.
Пожалуйста помоги...
1 ответ
Drag'n'Drop сложен, и ему не помогает тот факт, что есть по крайней мере два способа сделать это.
D'n'D вращается вокруг идеи "обернуть" объект в "переносимый" пакет, который можно "импортировать" с помощью ряда различных средств (например, DataFlavor
s)
Итак, в этом примере я сосредоточился только на удалении элементов из JList
Чтобы сделать это, я создал Trash
объект, который на самом деле поддерживает ссылку на элемент, который будет удален (я также создал ListTrash
объект, чтобы продемонстрировать хотя бы один способ передать больше информации)
Этот объект затем оборачивается в TrashTransferable
когда сопротивление происходит на JList
Основная причина наличия Trash
объект, это позволяет DataFlavor
быть стандартизированным. "Мусорное ведро" заботится только о Trash
объекты, ничего больше. Это особенно важно, если у вас есть больше TransferHandler
делает больше операций
Еще одна вещь, которую я сделал, это создать два TransferHandler
s. Один для "мусорного ведра" и один для JList
Основная причина этого заключается в том, что он изолирует функциональность, которую хочет выполнить каждый обработчик, и снижает сложность, поскольку вы также не пытаетесь определить, какой объект пытается выполнить какую операцию.
В примере также есть другой компонент, который ничего не делает, поэтому он может отклонить операцию удаления.
Если у вас есть другие компоненты, использующие TransferHandler
s, тогда те должны отклонить 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;
}
}
}