Drag 'n' Drop und Zugriff auf den alten Feldinhalt

Verwandt zu meinem Problem in http://forum.byte-welt.net/java-forum-erste-hilfe-vom-java-welt-kompetenz-zentrum/awt-swing-javafx-swt/15124-drop-inhalt-bearbeiten.html suche ich nach einer Möglichkeit, den originalen Feldinhalt vor dem Einfügen per Drag ‘n’ Drop zu löschen.

Meine Ansätze sehen bisher wie folgt aus:

Testprogramm:


import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.util.TooManyListenersException;

import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

public class DragNDropTest {

    private final JFrame frame;
    private final JTextField nameField;
    private final JTextField addressField;
    private final JTextField phoneField;

    public DragNDropTest() {
        frame = new JFrame();
        nameField = new JTextField();
        addressField = new JTextField();
        phoneField = new JTextField();

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                createGUI();
                nameField.setText("Irgendein störender alter Namensinhalt.");
                addressField.setText("Irgendein störender alter Adressinhalt.");
                phoneField.setText("Irgendein störender alter Telefonnummerninhalt.");
            }
        });
    }

    private void createGUI() {
        frame.setTitle("Wie überschreibe ich bei Drag 'n' Drop?");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLayout(new BorderLayout());

        frame.add(createCenterPart(), BorderLayout.CENTER);
        frame.add(createLowerPart(), BorderLayout.SOUTH);

        frame.pack();
    }

    private Component createCenterPart() {
        JEditorPane editorPane = new JEditorPane();
        editorPane.setEditable(false);
        editorPane.setBackground(new Color(0xf0, 0xe6, 0x8c));
        editorPane.setContentType("text/html");
        editorPane.setCaretPosition(0);
        editorPane.setDragEnabled(true);
        editorPane.setText("<html><body>"
                + "<p>Peter Müller, Musterweg 17, 12345 Musterstadt, +49 123 456 789 000</p>"
                + "<p>Markus Meier, Mustergasse 3a, 12346 Musterhausen, +49 999 123 456 000</p>"
                + "<p>Stefan Schmidt, Musterstraße 1-3, 12347 Bad Musterlingen, +49 456 123 789 000</p>"
                + "</body></html>");

        JScrollPane scroll = new JScrollPane(editorPane);
        scroll.setPreferredSize(new Dimension(600, 200));

        return scroll;
    }

    private Component createLowerPart() {
        JPanel panel = new JPanel();
        panel.setLayout(new GridBagLayout());

        GridBagConstraints gbc = new GridBagConstraints();
        gbc.insets     = new Insets(0, 1, 0, 1);
        gbc.fill       = GridBagConstraints.HORIZONTAL;
        gbc.anchor     = GridBagConstraints.WEST;
        gbc.weighty    = 0.0;

        gbc.gridy      = -1;

        {
            ++gbc.gridy;

            gbc.gridx      = 0;
            gbc.weightx    = 0.0;
            panel.add(new JLabel("Name"), gbc);

            gbc.gridx      = 1;
            gbc.weightx    = 1.0;
            addNormalFieldCorrector(nameField);
            panel.add(nameField, gbc);
        }
        {
            ++gbc.gridy;

            gbc.gridx      = 0;
            gbc.weightx    = 0.0;
            panel.add(new JLabel("Adresse"), gbc);

            gbc.gridx      = 1;
            gbc.weightx    = 1.0;
            addNormalFieldCorrector(addressField);
            panel.add(addressField, gbc);
        }
        {
            ++gbc.gridy;

            gbc.gridx      = 0;
            gbc.weightx    = 0.0;
            panel.add(new JLabel("Telefon"), gbc);

            gbc.gridx      = 1;
            gbc.weightx    = 1.0;
            addNormalFieldCorrector(phoneField);
            panel.add(phoneField, gbc);
        }

        return panel;
    }

    private void addNormalFieldCorrector(final JTextField field) {
        try {
            field.getDropTarget().addDropTargetListener(
                    new NormalFieldDropTargetListener(field));
            //field.getDropTarget().addDropTargetListener(
            //        new RemoveAndInsertFieldDropTargetListener(field));
        }
        catch (TooManyListenersException e1) {
            e1.printStackTrace();
        }
    }

    public void setVisual(final boolean visual) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                frame.setVisible(visual);
            }
        });
    }

    public static void main(String[] args) {
        DragNDropTest demo = new DragNDropTest();
        demo.setVisual(true);
    }

}

Version 1 (im Programm oben aktiviert): Ein DropTargetListener der den alten Feldinhalt löscht, falls der neue Feldinhalt dahinter eingefügt wird:


import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;

import javax.swing.JTextField;
import javax.swing.SwingUtilities;

public class NormalFieldDropTargetListener implements DropTargetListener {

    private final JTextField field;
    //private boolean entered;
    private String oldContent;

    public NormalFieldDropTargetListener(final JTextField field) {
        this.field = field;
    }

    @Override
    public void dragEnter(DropTargetDragEvent dtde) {
        //entered = true;
        oldContent = field.getText();
    }

    @Override
    public void dragOver(DropTargetDragEvent dtde) {
    }

    @Override
    public void dropActionChanged(DropTargetDragEvent dtde) {
    }

    @Override
    public void dragExit(DropTargetEvent dte) {
        //entered = false;
        //oldContent = "";
    }

    @Override
    public void drop(DropTargetDropEvent dtde) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                editContent();
            }
        });
    }

    private void editContent() {
        String text = field.getText();

        //System.out.println("text:" + text);
        //System.out.println("entered:" + entered); // true
        //System.out.println("oldContent:" + oldContent);

        /* Alten Inhalt vorn entfernen: */
        if (text.startsWith(oldContent)) {
            text = text.substring(oldContent.length());
        }
        /*
         * Besser wäre es, den alten Text vorher komplett zu löschen. Aber das
         * ist hier nicht gut möglich.
         * Man kommt nämlich nicht mit diesen Methoden an die Stelle, wo der
         * Mausklick über "unserem" JTextField ausgelöst wurde, der Drop-Text
         * aber noch nicht eingefügt wurde.
         *
         * Löscht man ihn etwa in dragEnter(), wird der Inhalt auch gelöscht,
         * wenn dann auf einem anderen Textfeld die Maus losgelassen wird.
         */

        text = text.trim();

        field.setText(text);
    }

}

Version 2 (dafür müssen die auskommentierten Zeilen oben aktiviert und die davor auskommentiert werden): Ein DropTargetListener der den alten Feldinhalt löscht, sobald der Mousecursor das Feld betritt und ihn wieder einfügt, wenn kein Drop stattfand und das Feld verlassen wurde:


import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;

import javax.swing.JTextField;

public class RemoveAndInsertFieldDropTargetListener implements DropTargetListener {

    private final JTextField field;
    private boolean inserted;
    private String oldContent;

    public RemoveAndInsertFieldDropTargetListener(final JTextField field) {
        this.field = field;
    }

    @Override
    public void dragEnter(DropTargetDragEvent dtde) {
        oldContent = field.getText();
        field.setText("");
        inserted = false;
    }

    @Override
    public void dragOver(DropTargetDragEvent dtde) {
    }

    @Override
    public void dropActionChanged(DropTargetDragEvent dtde) {
    }

    @Override
    public void dragExit(DropTargetEvent dte) {
        if (!inserted) {
            field.setText(oldContent);
        }
        inserted = false;
    }

    @Override
    public void drop(DropTargetDropEvent dtde) {
        inserted = true;
    }

}

Es gibt aber sicher auch eine natürlichere und elegantere Lösung. Kennt sie jemand?

nicht unbedingt mit den von dir angesprochenden Eigenschaften, aber um nochmal für meine Ideen im vorherigen Thread Werbung zu machen :wink: :

d.addDocumentListener(new DocumentListener()    {
                public void insertUpdate(DocumentEvent e)      {
                    boolean drop1 = isDrop();
                    boolean drop2 = RemoveAndInsertFieldDropTargetListener.tf == field;

                    System.out.println("insertUpdate " + drop1 + ", " + drop2);
                    if (drop1 || drop2)  {
                        String t = field.getText();
                        final String n = t.substring(e.getOffset(), e.getOffset() + e.getLength());
                        RemoveAndInsertFieldDropTargetListener.tf = null; // Info löschen

                        SwingUtilities.invokeLater(new Runnable() {
                                public void run()  {
                                    field.setText(n);
                                }
                            });
                    }
                }

                private boolean isDrop()     {
                    StackTraceElement[] trace = Thread.currentThread().getStackTrace();
                    for (StackTraceElement e : trace)
                        if (e.getClassName().contains("DropTarget")) return true;
                    return false;
                }

der DocumentListener greift auch erst nach Einfügen, etwas unschön,
vorher vielleicht nur mit Überschreiben irgendwelcher Methoden im TextField oder eher dem Document möglich,
ein Document ist aber auch nicht umsonst ein Interface, ein Wrapper ist auch denkbar

der neue Text läßt sich hier anscheinend herausschneiden und mit invokeLater neu setzen

zur Unterscheidung von anderen Aktionen wie früher angesprochen zwei Möglichkeiten:
im StackTrace nachschauen (drop1) oder vorher eine Info setzen, etwa per dragEnter-Methode des DropTargetListener (drop2)

wenn für drop2 nicht die Info gleich selber gelöscht wird gibt es eine Endlosschleife, allgemein aufzupassen,
dürfte mit DocumentWrapper ohne neues Einfügen-Ereignis kein Problem sein

die Info-Variante sollte sicher gegen User-Eingaben sein, kein Focus währenddessen,
aber theoretisch denkbar ist ein programmatischer setText()-Aufruf während die Maus darüber schwebt → als Drop interpretiert,
StackTrace wohl einfacher wenn man das akzeptiert


soll es eigentlich einen Unterschied machen ob das Drop hinter dem vorhandnen Text oder beliebig in die Mitte gesetzt wird?
deine text.startsWith(oldContent)-Variante, Version 1, funktioniert ja nur wenn der alte Text zusammenhänged bleibt

Mit dem DocumentListener werde ich vielleicht auch nochmal herumexperimentieren. Aber es wundert mich, dass dieses in meinen Augen nicht so fern liegende Problem offenbar etwas komplizierter zu lösen ist.

soll es eigentlich einen Unterschied machen ob das Drop hinter dem vorhandnen Text oder beliebig in die Mitte gesetzt wird?
deine text.startsWith(oldContent)-Variante, Version 1, funktioniert ja nur wenn der alte Text zusammenhänged bleibt

Nein, das war ein Notbehelf, damit man den Inhalt überhaupt einfach überschreiben kann.