使用秋千渲染放置位置

Posted

技术标签:

【中文标题】使用秋千渲染放置位置【英文标题】:Drop location rendering with swing 【发布时间】:2014-09-02 22:37:19 【问题描述】:

我想为组件(例如 JPanel 或 JLabel)实现自定义放置位置渲染。我的目标是当用户悬停带有可放置项目的组件时显示蓝色边框,并在离开组件时再次移除边框。

摇摆教程提供了这个:Drop Location Rendering 这对我一点帮助都没有。我错过了enterComponent(Event e)exitComponent(Event e) 之类的内容,或者此页面所指的内容与我预期的不同?

所以我搜索了万维网并找到了一些示例(主要来自this blog)。过了一会儿,我可以完成我需要的工作示例(来源如下)。但是我发现所有示例都使用了 java.awt 中的类来呈现放置位置。因为我还没有完全了解所有这些拖放类,所以我没有找到一种方法来仅使用 swing 类来实现自定义放置位置渲染。

所以我的问题是:是否可以仅使用 swing 类在 "enterComponent""exitComponent" 事件上呈现 JComponent?

补充问题: 如果我删除new DropTarget(label, dropListener); 行,ListItemTransfereHandler.importData(...) 将被调用,否则不会被调用。这是什么原因?我真的很感激一些关于 dnd 课程的课程和/或行动图表(这不是答案所必需的)。

(请不要回答 JavaFX)


工作示例的源代码(带有 AWT 类)。很抱歉代码行太多,但是 java dnd 需要很大的空间。

public class DnDTransferableTest 

    public static void main(String[] args) 
        EventQueue.invokeLater(new Runnable() 
            @Override
            public void run() 
                new DnDTransferableTest();
            
        );
    

    public DnDTransferableTest() 
        JFrame frame = new JFrame("Testing");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(300, 300);
        frame.setLocationRelativeTo(null);

//      JPanel panel = new JPanel(new MigLayout("wrap 2, fill", "fill, grow", "align center"));
        JPanel panel = new JPanel(new GridLayout(1,2));

        JList<ListItem> list = new JList<ListItem>();
        JLabel label = new JLabel();

        // Initializing list
        list.setDragEnabled(true);
        list.setTransferHandler(new ListTransferHandler());

        DefaultListModel<ListItem> model = new DefaultListModel<ListItem>();
        for (int index = 0; index < 10; index++) 

            model.addElement(new ListItem("Item", index));

        
        list.setModel(model);

        // Initializing label and its drop listener
        label.setText("Drag on me...");
        label.setTransferHandler(new ListTransferHandler());

        DropTargetListener dropListener = new JLabelDropTargetListener();
        new DropTarget(label, dropListener);

        // Adding the components to the panel
        panel.add(new JScrollPane(list), "sg test");
        panel.add(label, "sg test");

        frame.setContentPane(panel);
        frame.setVisible(true);
    



class ListItem implements Transferable 

    public static final DataFlavor LIST_ITEM_DATA_FLAVOR = new DataFlavor(ListItem.class, ListItem.class.getName());
    private String text;
    private int number;

    public ListItem(String text, int number) 
        this.text = text;
        this.number = number;
    

    public String getText() 
        return text;
    

    public int getNumber() 
        return this.number;
    

    @Override
    public String toString() 
        return this.getText() + ": " + this.getNumber();
    

    @Override
    public DataFlavor[] getTransferDataFlavors() 
        return new DataFlavor[]  LIST_ITEM_DATA_FLAVOR ;
    

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

    @Override
    public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException 
        return this;
    


class ListTransferHandler extends TransferHandler 

    private static final long serialVersionUID = 1L;

    @Override
    public boolean canImport(TransferSupport support) 
        return (support.getComponent() instanceof JLabel) && support.isDataFlavorSupported(ListItem.LIST_ITEM_DATA_FLAVOR);
    

    @Override
    public boolean importData(TransferSupport support) 

        boolean accept = false;
        if (canImport(support)) 
            try 
                Transferable t = support.getTransferable();
                Object value = t.getTransferData(ListItem.LIST_ITEM_DATA_FLAVOR);
                if (value instanceof ListItem) 
                    Component component = support.getComponent();
                    if (component instanceof JLabel) 
                        ((JLabel) component).setText(((ListItem) value).getNumber() + ". " + ((ListItem) value).getText());
                    
                
             catch (Exception exp) 
                exp.printStackTrace();
            
        
        System.out.println("import data " + accept);
        return accept;
    

    @Override
    public int getSourceActions(JComponent c) 
        return COPY;
    

    @Override
    protected Transferable createTransferable(JComponent c) 

        if (c instanceof JList<?>) 
            JList<?> list = (JList<?>) c;
            Object value = list.getSelectedValue();
            if (value instanceof ListItem) 
                return (ListItem) value;
            
        
        return null;
    

    @Override
    protected void exportDone(JComponent source, Transferable data, int action) 
        System.out.println("ExportDone");
        // Here you need to decide how to handle the completion of the
        // transfer,
        // should you remove the item from the list or not...
    



class JLabelDropTargetListener implements DropTargetListener 

    private int thickness = 2;
    private Border blueBorder = BorderFactory.createLineBorder(Color.BLUE, thickness);
    private Border emptyBorder = BorderFactory.createEmptyBorder(thickness, thickness, thickness, thickness);

    @Override
    public void dragEnter(DropTargetDragEvent dtde) 
        System.out.println("dragEnter");
        Object src = dtde.getDropTargetContext().getComponent();

        if (src instanceof JLabel) 
            JLabel label = (JLabel) src;
            label.setForeground(Color.RED);
            label.setBorder(blueBorder);
         else 
            System.out.println(src.getClass().getName());
            System.out.println(dtde.getDropTargetContext().getComponent());
        
    

    @Override
    public void dragOver(DropTargetDragEvent dtde) 
    

    @Override
    public void dropActionChanged(DropTargetDragEvent dtde) 
    

    @Override
    public void dragExit(DropTargetEvent dte) 
        System.out.println("dragExit");
        Component src = dte.getDropTargetContext().getComponent();

        if (src instanceof JLabel) 
            JLabel label = (JLabel) src;
            label.setForeground(null);
            label.setBorder(emptyBorder);
        

    

    @Override
    public void drop(DropTargetDropEvent dtde) 
        System.out.println("drop");
        Component src = dtde.getDropTargetContext().getComponent();

        if (src instanceof JLabel) 
            JLabel label = (JLabel) src;
            label.setForeground(null);
            label.setBorder(emptyBorder);

            Transferable t = dtde.getTransferable();
            Object value;
            try 
                value = t.getTransferData(ListItem.LIST_ITEM_DATA_FLAVOR);

                if (value instanceof ListItem) 
                    ListItem li = (ListItem) value;
                    label.setText(li.getNumber() + ". " + li.getText());
                

             catch (UnsupportedFlavorException e) 
                e.printStackTrace();
             catch (IOException e) 
                e.printStackTrace();
            

        
    


【问题讨论】:

【参考方案1】:

我深入查看了swing的TransferHandler的源代码,发现JComponentsetDropLocation(TransferHandler.DropLocation location, Object state, boolean forDrop)TransferHandler 在每个 DragEvent 上调用它(TransferHandler 有自己的私有 DropTargetListener 类)。首先我想在我自己的 JLabel 扩展类中覆盖它,但后来发现它是受保护的。

有了这些新信息,我偶然发现了这两个帖子:

http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6448332http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6500024

所以答案是:这是一个错误,或者更确切地说是一个尚未实现的功能(我不知道它是否在 java 8 中可用,但我认为 JavaFX 比 Swing 做得更好。不幸的是,切换到 JavaFX 有点现在很晚了)。

【讨论】:

【参考方案2】:

我能够弄清楚如何做到这一点。 根据您的代码:

    DropTarget dropTarget = label.getDropTarget();
    try
    
        dropTarget.addDropTargetListener(dropListener);
    
    catch (TooManyListenersException e)
    
        e.printStackTrace();
    

像往常一样设置传输处理程序。这将处理实际的拖放功能,而您的拖放侦听器处理标签的绘制。 不要创建自己的放置目标对象。

【讨论】:

以上是关于使用秋千渲染放置位置的主要内容,如果未能解决你的问题,请参考以下文章

控制 magento 左侧导航的渲染位置

不使用redux返回时React Router重新渲染

Unity - 在鼠标点击的位置放置对象

Blazor University (14)渲染树

在 jquery drop UI 中,如何使用正确的鼠标位置将拖动元素克隆到放置位置?

React Dnd 实现横向和纵向拖拽排序