Перетаскивание пользовательского объекта из встроенного FX (JFXPanel) в Swing - PullRequest
0 голосов
/ 02 октября 2018

Этот вопрос является дополнительным вопросом к Перетаскивание пользовательского объекта из FX в Swing .

Я работаю над плагином дляприложение Swing, которое использует JavaFX для некоторых графических пользовательских интерфейсов.Мы добавили функцию перетаскивания, чтобы улучшить взаимодействие с пользователем.Во-первых, мы использовали внешнее окно JavaFX (Stage) для нашего Scene, теперь мы хотим встроить его непосредственно в приложение Swing через JFXPanel.

Теперь странно то, чточто для перетаскивания имеет большое значение, загружается ли точно такой же Scene в Stage или в JFXPanel.

Я уже сталкивался с некоторыми проблемами при перетаскиваниинекоторый пользовательский объект Java (в сериализованной форме) с пользовательским типом MIME из приложения JavaFX в приложение Swing.Тем не менее, мои проблемы были решены в вопросе, который я упомянул выше.Теперь, используя встроенное приложение JavaFX, я сталкиваюсь с некоторыми новыми проблемами, поэтому я хотел спросить, есть ли у кого-то похожие проблемы или знает решение для этого сценария.

Я написал MVCE, это простое приложение Javaс поддержкой перетаскивания JFXPanel на одной стороне и поддержкой опускания JPanel на другой стороне:

public class MyApp {

    public static final DataFormat FORMAT = new DataFormat(
        // this works fine in a separate window
        //"JAVA_DATAFLAVOR:application/x-my-mime-type; class=java.lang.String",
        "application/x-my-mime-type; class=java.lang.String");

    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 MyApp().run();
    }

    private void run() {
        JFrame frame = new JFrame();
        frame.setLayout(new GridLayout(1, 2));
        frame.add(buildFX());
        frame.add(buildSwing());
        frame.setSize(300, 300);
        frame.setVisible(true);
    }

    private JFXPanel buildFX() {
        BorderPane parent = new BorderPane();
        parent.setOnDragDetected(event -> {
            Dragboard dragboard = parent.startDragAndDrop(TransferMode.COPY);
            ClipboardContent content = new ClipboardContent();
            content.put(FORMAT, "Test");
            dragboard.setContent(content);
            event.consume();
        });
        JFXPanel panel = new JFXPanel();
        panel.setScene(new Scene(parent));
        return panel;
    }

    @SuppressWarnings("serial")
    private JPanel buildSwing() {
        JPanel panel = new JPanel();
        panel.setBackground(Color.ORANGE);
        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;
            }

        });
        return panel;
    }
}

Согласно ответу на другой вопрос, с использованием префикса JAVA_DATAFLAVOR: вDataFormat необходим для Swing для правильной обработки типа MIME.Однако при использовании такого DataFormat внутри JFXPanel (отключенного в примере) кажется, что Java пытается создать DataFlavor при перетаскивании из приложения FX и не может проанализировать тип MIME с префиксом:

Exception in thread "AWT-EventQueue-0" java.lang.IllegalArgumentException: failed to parse:JAVA_DATAFLAVOR:application/x-my-mime-type; class=java.lang.String
    at java.awt.datatransfer.DataFlavor.<init>(Unknown Source)
    at javafx.embed.swing.SwingDnD$DnDTransferable.getTransferDataFlavors(SwingDnD.java:394)
    at sun.awt.datatransfer.DataTransferer.getFormatsForTransferable(Unknown Source)
    at sun.awt.dnd.SunDragSourceContextPeer.startDrag(Unknown Source)
    at java.awt.dnd.DragSource.startDrag(Unknown Source)
    at java.awt.dnd.DragSource.startDrag(Unknown Source)
    at java.awt.dnd.DragGestureEvent.startDrag(Unknown Source)
    at javafx.embed.swing.SwingDnD.startDrag(SwingDnD.java:280)
    at javafx.embed.swing.SwingDnD.lambda$null$66(SwingDnD.java:247)
    at java.awt.event.InvocationEvent.dispatch(Unknown Source)
    at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
    at java.awt.EventQueue.access$500(Unknown Source)
    at java.awt.EventQueue$3.run(Unknown Source)
    at java.awt.EventQueue$3.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
    at java.awt.EventQueue.dispatchEvent(Unknown Source)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
    at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
    at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
    at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
    at java.awt.EventDispatchThread.run(Unknown Source)

Используя только чистый MIME-тип без префикса, операция перетаскивания работает, и я даже могу получить правильный DataFlavor (java.awt.datatransfer.DataFlavor[mimetype=application/x-my-mime-type;representationclass=java.lang.String]), но пропущенные данные всегда null.Как видно из другого вопроса, используя этот второй подход с двумя раздельными окнами, я даже не могу получить DataFlavor, но теперь он работает каким-то образом до этой ограниченной точки.

1 Ответ

0 голосов
/ 05 октября 2018

Вероятно, существует некоторое недопонимание того, как работает передача.

Попытка извлечения данных переноса непосредственно в виде строки может работать для «текста / обычного» или других стандартных типов текста и, как вы заметили, с некоторыми причудами для конкретных случаев нестандартного незарегистрированного типа.Но я не думаю, что усилия для пользовательских обходных путей оправданы.

Поскольку вы полностью контролируете структуру содержимого для пользовательского типа MIME, а также оба конца источника данных и потребителя в одном приложении, я предлагаю неиметь дело с внутренними префиксами, зависящими от реализации инструментария, или отображениями классов.Вероятно, лучше просто определить свой тип MIME без несвязанных метаданных и искаженных префиксов (как это и должно быть).

Определение типа «application / x-my-mime » и правильноедолжно быть достаточно декодирования данных.


Пример 1 (сериализованные данные)

Нижеприведенное исправление из вашего примера должно сбрасывать данные в фрейм Swing в Java 8.

package jfxtest;

import java.awt.Color;
import java.awt.GridLayout;
import java.awt.datatransfer.DataFlavor;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.util.Collections;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.DataFormat;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.BorderPane;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.TransferHandler;
import javax.swing.TransferHandler.TransferSupport;

public class MyApp {

  final static String MY_MIME_TYPE = "application/x-my-mime";
  public static final DataFormat FORMAT = new DataFormat(MY_MIME_TYPE);
  public static final DataFlavor FLAVOR = new DataFlavor(MY_MIME_TYPE, "My Mime Type");

  private void startDrag(Node node) {
    node.startDragAndDrop(TransferMode.COPY).setContent(
        Collections.singletonMap(FORMAT, "Test"));
  }

  private boolean processData(TransferSupport support) {
    try (InputStream in = (InputStream) support.getTransferable().getTransferData(FLAVOR)) {
      Object transferred = new ObjectInputStream(in).readObject();
      System.out.println("transferred: " + transferred + " (" + transferred.getClass() + ")");
      return true;
    } catch (Exception e) {
      e.printStackTrace();
    }
    return false;
  }  

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

  private void run() {
    JFrame frame = new JFrame();
    frame.setLayout(new GridLayout(1, 2));
    frame.add(buildSwing());
    SwingUtilities.invokeLater(() -> {
      frame.add(buildFX());
    });
    frame.setSize(300, 300);
    frame.setVisible(true);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  }

  private JFXPanel buildFX() {
    BorderPane parent = new BorderPane();
    parent.setOnDragDetected(event -> {
      startDrag(parent);
      event.consume();
    });
    JFXPanel panel = new JFXPanel();
    panel.setScene(new Scene(parent));
    return panel;
  }


  private JPanel buildSwing() {
    JPanel panel = new JPanel();
    panel.setBackground(Color.ORANGE);
    panel.setTransferHandler(new TransferHandler() {
      private static final long serialVersionUID = 1L;

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

      @Override
      public boolean importData(TransferSupport support) {
        if (canImport(support)) {
          return processData(support);
        }
        return false;
      }

    });
    return panel;
  }

}

Вывод: transferred: Test (class java.lang.String)

Здесь приведен существенный отрывок:

...

  final static String MY_MIME_TYPE = "application/x-my-mime";
  public static final DataFormat FORMAT = new DataFormat(MY_MIME_TYPE);
  public static final DataFlavor FLAVOR = new DataFlavor(MY_MIME_TYPE, "My Mime Type");

  private void startDrag(Node node) {
    node.startDragAndDrop(TransferMode.COPY).setContent(
        Collections.singletonMap(FORMAT, "Test"));    
  }

  private boolean processData(TransferSupport support) {
    try (InputStream in = (InputStream) support.getTransferable().getTransferData(FLAVOR)) {
      Object transferred = new ObjectInputStream(in).readObject();
      System.out.println("transferred: " + transferred + " (" + transferred.getClass() + ")");
      return true;
    } catch (Exception e) {
      e.printStackTrace();
    }
    return false;
  }

...  

Обратите внимание, что поиск данных упрощен в целях иллюстрации, для реального применения.вероятно, хотелось бы добавить более точное чтение потока, обработку ошибок и т. д.


Пример 2 (пользовательский MIME с текстом)

Первый пример передает сериализованный объект (который обычнохорошая и простая вещь, поскольку вы можете передавать все, что можно сериализовать, но затрудняет передачу / прием, скажем, стороннего JSON).В маловероятном случае, когда вы хотите создать реальный текст или другой произвольный контент для пользовательского MIME вместо сериализованного объекта, ниже следует выполнить работу:

package jfxtest;

import java.awt.Color;
import java.awt.GridLayout;
import java.awt.datatransfer.DataFlavor;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.DataFormat;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.BorderPane;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.TransferHandler;
import javax.swing.TransferHandler.TransferSupport;

public class MyApp {

  final static String MY_MIME_TYPE = "application/x-my-mime";
  public static final DataFormat FORMAT = new DataFormat(MY_MIME_TYPE);
  public static final DataFlavor FLAVOR = new DataFlavor(MY_MIME_TYPE, "My Mime Type");

  private void startDrag(Node node) {
    node.startDragAndDrop(TransferMode.COPY).setContent(
        // put a ByteBuffer to transfer the content unaffected
        Collections.singletonMap(FORMAT, StandardCharsets.UTF_8.encode("Test")));
  }

  private boolean processData(TransferSupport support) {
    try (InputStream in = (InputStream) support.getTransferable().getTransferData(FLAVOR)) {
      byte[] textBytes = new byte[in.available()];
      in.read(textBytes);
      String transferred = new String(textBytes, StandardCharsets.UTF_8); 
      System.out.println("transferred text: " + transferred);
      return true;
    } catch (Exception e) {
      e.printStackTrace();
    }
    return false;
  }  

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

  private void run() {
    JFrame frame = new JFrame();
    frame.setLayout(new GridLayout(1, 2));
    frame.add(buildSwing());
    SwingUtilities.invokeLater(() -> {
      frame.add(buildFX());
    });
    frame.setSize(300, 300);
    frame.setVisible(true);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  }

  private JFXPanel buildFX() {
    BorderPane parent = new BorderPane();
    parent.setOnDragDetected(event -> {
      startDrag(parent);
      event.consume();
    });
    JFXPanel panel = new JFXPanel();
    panel.setScene(new Scene(parent));
    return panel;
  }


  private JPanel buildSwing() {
    JPanel panel = new JPanel();
    panel.setBackground(Color.ORANGE);
    panel.setTransferHandler(new TransferHandler() {
      private static final long serialVersionUID = 1L;

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

      @Override
      public boolean importData(TransferSupport support) {
        if (canImport(support)) {
          return processData(support);
        }
        return false;
      }

    });
    return panel;
  }

}

Вывод: transferred text: Test

Здесь важная часть:

...

  private void startDrag(Node node) {
    node.startDragAndDrop(TransferMode.COPY).setContent(
        // put a ByteBuffer to transfer the content unaffected
        Collections.singletonMap(FORMAT, StandardCharsets.UTF_8.encode("Test")));
  }

  private boolean processData(TransferSupport support) {
    try (InputStream in = (InputStream) support.getTransferable().getTransferData(FLAVOR)) {
      byte[] textBytes = new byte[in.available()];
      in.read(textBytes);
      String transferred = new String(textBytes, StandardCharsets.UTF_8); 
      System.out.println("transferred text: " + transferred);
      return true;
    } catch (Exception e) {
      e.printStackTrace();
    }
    return false;
  }  

...

Еще раз, в качестве иллюстрации, обработка потока, ошибок и т. Д. Здесь упрощена.


Одна вещь, на которую следует обратить внимание:что существует также предопределенный «application / x-java-serialized-object» (DataFlavor.javaSerializedObjectMimeType) для более общей и простой десериализации.Но долгосрочный пользовательский MIME кажется более гибким и простым в обращении.

...