Перетаскивание пользовательских объектов из FX в Swing

Я работаю над приложением JavaFX, которое должно взаимодействовать с существующим приложением Swing с помощью перетаскивания. Обмен данными с помощью перетаскивания фактически работает, но мы хотим переработать эту часть функциональности, чтобы фактически обмениваться пользовательскими объектами Java вместо простых строк с объектами, сериализованными в JSON. Проблема в том, что пользовательский интерфейс Swing не получает перетаскиваемые данные, если вместо, например, используются пользовательские типы MIME. text/plain, Ниже вы можете найти минимальный пример как для приложения перетаскивания (JavaFX), так и для приложения перетаскивания (Swing).

FxDrag

public class FxDrag extends Application {

    private static final DataFormat format = new DataFormat("application/x-my-mime-type");

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

    @Override
    public void start(Stage stage) throws Exception {
        BorderPane root = new BorderPane();
        root.setOnDragDetected(event -> {
            Dragboard dragboard = root.startDragAndDrop(TransferMode.COPY);
            ClipboardContent content = new ClipboardContent();
            content.putString("Test");
            // content.put(format, "Test");
            dragboard.setContent(content);
            event.consume();
        });
        stage.setScene(new Scene(root, 300, 300));
        stage.setTitle("Drag");
        stage.show();
    }
}

SwingDrop

public class SwingDrop {

    public static void main(String[] args) {
        new SwingDrop().run();
    }

    private void run() {
        JPanel panel = new JPanel();
        panel.setTransferHandler(new TransferHandler() {

            @Override
            public boolean canImport(TransferSupport support) {
                return true;
            }

            @Override
            public boolean importData(TransferSupport support) {
                Stream.of(support.getDataFlavors()).forEach(flavor -> {
                    System.out.println(flavor.getMimeType());
                });
                return super.importData(support);
            }

        });
        JFrame frame = new JFrame();
        frame.setTitle("Drop");
        frame.add(panel);
        frame.setSize(300, 300);
        frame.setVisible(true);
    }
}

Когда положить String с помощью putString к content в приложении JavaFX приложение Swing получает перетаскивание и предоставляет следующие варианты:

application/x-java-serialized-object; class=java.lang.String
text/plain; class=java.io.Reader; charset=Unicode
text/plain; class=java.lang.String; charset=Unicode
text/plain; class=java.nio.CharBuffer; charset=Unicode
text/plain; class="[C"; charset=Unicode
text/plain; class=java.io.InputStream; charset=unicode
text/plain; class=java.nio.ByteBuffer; charset=UTF-16
text/plain; class="[B"; charset=UTF-16
text/plain; class=java.io.InputStream; charset=UTF-8
text/plain; class=java.nio.ByteBuffer; charset=UTF-8
text/plain; class="[B"; charset=UTF-8
text/plain; class=java.io.InputStream; charset=UTF-16BE
text/plain; class=java.nio.ByteBuffer; charset=UTF-16BE
text/plain; class="[B"; charset=UTF-16BE
text/plain; class=java.io.InputStream; charset=UTF-16LE
text/plain; class=java.nio.ByteBuffer; charset=UTF-16LE
text/plain; class="[B"; charset=UTF-16LE
text/plain; class=java.io.InputStream; charset=ISO-8859-1
text/plain; class=java.nio.ByteBuffer; charset=ISO-8859-1
text/plain; class="[B"; charset=ISO-8859-1
text/plain; class=java.io.InputStream; charset=windows-1252
text/plain; class=java.io.InputStream
text/plain; class=java.nio.ByteBuffer; charset=windows-1252
text/plain; class="[B"; charset=windows-1252
text/plain; class=java.io.InputStream; charset=US-ASCII
text/plain; class=java.nio.ByteBuffer; charset=US-ASCII
text/plain; class="[B"; charset=US-ASCII

Я даже могу отбрасывать разные данные из разных приложений, таких как браузеры и т. Д., А приложение Swing предоставляет соответствующие варианты данных в виде (текст, изображения и т. Д.).

Однако, если я использую свой собственный формат, никакие ароматы не будут перечислены вообще. Может ли Swing фильтровать данные, передаваемые через приложение перетаскивания?

2 ответа

Решение

Старый ответ не работал между отдельными приложениями. Новая попытка ниже.


Мне удалось заставить это работать между отдельными приложениями Swing и JavaFX в обоих направлениях. Я загрузил рабочий пример в репозиторий GitLab, если вы хотите его просмотреть, но здесь я рассмотрю некоторые основы.

Если вы посмотрите на хранилище, то заметите, что у меня есть подпроект Gradle model который содержит класс модели com.example.dnd.model.Doctor, Этот класс Serializable и содержит три свойства: firstName, lastName, а также number, Этот проект используется совместно приложениями JavaFX и Swing (т.е. они используют одну и ту же модель). В каждом приложении у меня есть таблица, которая отображает список Doctor с этими свойствами: TableView в JavaFX и JTable в качелях.

Приложения позволяют перетаскивать одну или несколько строк в другое приложение и добавлять их в конец таблицы. Они делают это, отправив список соответствующих Doctor s.

В примере требуется Java 10. GIF примера в действии.


JavaFX

Я обнаружил, что сторона JavaFX намного проще в реализации. На самом деле, единственное, что вам нужно выработать, это как настроить соответствующий DataFormat, Тип MIME, который я использовал, был,

application/x-my-mime-type; class=com.example.dnd.model.Doctor

class= параметр важен на стороне Swing; используется для десериализации. После некоторых проб и ошибок я обнаружил, что при попытке перетащить данные из Swing в JavaFX к данному типу MIME добавляется JAVA_DATAFLAVOR:, делая это:

JAVA_DATAFLAVOR:application/x-my-mime-type; class=com.example.dnd.model.Doctor

Я должен был добавить это к DataFormat используется в onDragDetected Обработчик в противном случае Swing не распознает формат данных. Я не знаю, почему это так, и я не нашел документации об этом. При изменении версий Java и / или платформ я буду осторожен с этим, если это зависит от реализации (если вам не удается найти документацию).

В конце концов, мой DataFormat было объявлено так:

DataFormat format = new DataForamt(
    "JAVA_DATAFLAVOR:application/x-my-mime-type; class=com.example.dnd.model.Doctor",
    "application/x-my-mime-type; class=com.example.dnd.model.Doctor"
);

Я добавил два идентификатора, один с JAVA_DATAFLAVOR и один без, в попытке охватить оба случая (где это необходимо и нет). Я не знаю, нужно ли это, и помогает ли это вообще. Затем я сохранил это в некоторых static final поле для глобального доступа.

Тогда вы просто реализуете onDragXXX обработчики, как и следовало ожидать.


свинг

Swing сторона была немного более вовлечена в мое мнение; хотя это может быть только потому, что мне больше нравится JavaFX. Я хотел бы отметить, что уроки Oracle были очень полезны здесь. В Swing было три важных класса, связанных с DnD:

  • DataFlavor
    • Свинг DataFormat, но сложнее
  • TransferHandler
    • В основном onDragXXX обработчики как один класс
  • Transferable
    • Представляет передаваемые данные

1 - Есть другие участвующие классы, но это были три, которые я нашел наиболее важными в этом случае.

Чтобы это работало, мне пришлось создавать собственные реализации TransferHandler а также Transferable,

TransferHandler

import com.example.dnd.model.Doctor;
import java.awt.datatransfer.Transferable;
import java.util.ArrayList;
import javax.swing.JComponent;
import javax.swing.JTable;
import javax.swing.TransferHandler;

public class DoctorTransferHandler extends TransferHandler {

  @Override
  public boolean canImport(TransferSupport support) {
    return support.isDrop() && support.isDataFlavorSupported(DoctorTransferable.DOCTOR_FLAVOR);
  }

  @Override
  public boolean importData(TransferSupport support) {
    if (!canImport(support)) {
      return false;
    }
    JTable table = (JTable) support.getComponent();
    DoctorTableModel model = (DoctorTableModel) table.getModel();
    try {
      Transferable transferable = support.getTransferable();
      ArrayList<Doctor> list =
          (ArrayList<Doctor>) transferable.getTransferData(DoctorTransferable.DOCTOR_FLAVOR);
      model.addAll(list);
      return true;
    } catch (Exception ex) {
      ex.printStackTrace();
      return false;
    }
  }

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

  @Override
  protected Transferable createTransferable(JComponent c) {
    JTable table = (JTable) c;
    DoctorTableModel model = (DoctorTableModel) table.getModel();
    return new DoctorTransferable(model.getAll(table.getSelectedRows()));
  }

  @Override
  protected void exportDone(JComponent source, Transferable data, int action) {
    if (action == MOVE) {
      JTable table = (JTable) source;
      DoctorTableModel model = (DoctorTableModel) table.getModel();
      model.removeAll(model.getAll(table.getSelectedRows()));
    }
  }

}

переводный

import com.example.dnd.model.Doctor;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;

public class DoctorTransferable implements Transferable {

  public static final DataFlavor DOCTOR_FLAVOR;

  static {
    try {
      DOCTOR_FLAVOR = new DataFlavor("application/x-my-mime-type; class=java.util.ArrayList");
    } catch (ClassNotFoundException ex) {
      throw new RuntimeException(ex);
    }
  }

  private final ArrayList<Doctor> doctors;

  public DoctorTransferable(Collection<? extends Doctor> doctors) {
    this.doctors = new ArrayList<>(doctors);
  }

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

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

  @Override
  public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
    if (DOCTOR_FLAVOR.equals(flavor)) {
      return doctors;
    }
    throw new UnsupportedFlavorException(flavor);
  }

}

Если вы посмотрите на декларацию DataFlavor, внутри Transferable вы увидите, что я использую тот же тип MIME, что и в JavaFX минус JAVA_DATAFLAVOR: немного.

Я считаю, что наиболее важной частью является создание собственного Transferable который обрабатывает ваш пользовательский объект. это Transferable будет создан в защищенном TransferHandler#createTranserfable метод. Только когда я понял, что мне нужно это сделать, мне удалось заставить это работать. Это Transferable который отвечает за сообщение DataFlavor и как получить объекты.

Следующие две важные вещи, которые вы должны сделать, это переопределить canImport а также importData, Эти методы определяют, могут ли перетаскиваемые данные быть успешно отброшены и, если это так, как добавить их в компонент Swing. Мой пример довольно прост и добавляет данные в конец JTable модель.

Для экспорта данных вы также должны переопределить exportDone, Этот метод отвечает за выполнение любой очистки, если передача включала перемещение данных, а не только их копирование.


Я достиг этого решения через приличное количество проб и ошибок. В результате, в сочетании с тем фактом, что я хотел сделать это как можно проще, многие "стандартные" функции не реализованы. Например, данные всегда добавляются в конец таблицы, а не вставляются там, где их отбрасывают. На стороне JavaFX обработчики перетаскивания находятся на всем TableView а не на каждом TableCell (что было бы логичнее, я думаю).

Я надеюсь, что это работает для вас. Если нет, пожалуйста, дайте мне знать.

Просто для удобства добавлю комбинацию отличного решения @Slaw и минимального примера из моего вопроса. Чтобы лучше понять, взгляните на его ответ, так как он более подробный.


FxDrag

public class FxDrag extends Application {

    public static final DataFormat FORMAT = new DataFormat(
        "JAVA_DATAFLAVOR:application/x-my-mime-type; class=java.lang.String",
        "application/x-my-mime-type; class=java.lang.String");

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

    @Override
    public void start(Stage stage) throws Exception {
        BorderPane root = new BorderPane();
        root.setOnDragDetected(event -> {
            Dragboard dragboard = root.startDragAndDrop(TransferMode.COPY);
            ClipboardContent content = new ClipboardContent();
            content.put(FORMAT, "Test123");
            dragboard.setContent(content);
            event.consume();
        });

        stage.setScene(new Scene(root, 300, 300));
        stage.setTitle("Drag");
        stage.show();
    }

}

SwingDrop

public class SwingDrop {

    public static final DataFlavor FLAVOR;

    static {
        try {
            FLAVOR = new DataFlavor("application/x-my-mime-type; class=java.lang.String");
        } catch (ClassNotFoundException ex) {
            throw new RuntimeException(ex);
        }
    }

    public static void main(String[] args) {
        new SwingDrop().run();
    }

    private void run() {
        JPanel panel = new JPanel();
        panel.setTransferHandler(new TransferHandler() {

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

            @Override
            public boolean importData(TransferSupport support) {
                if (!canImport(support)) return false;
                try {
                    String data = (String) support.getTransferable().getTransferData(FLAVOR);
                    System.out.println(data);
                    return true;
                } catch (UnsupportedFlavorException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return false;
            }

        });
        JFrame frame = new JFrame("Drop");
        frame.getContentPane().add(panel);
        frame.setSize(300, 300);
        frame.setVisible(true);
    }

}

Операции DragAndDrop из приложений FX в приложение Swing возможны с этими примерами приложений. Невозможно перетащить в любое другое приложение, даже если переданные данные просто String, Это работает так же, как и для улучшения юзабилити.

Другие вопросы по тегам