我应该如何将 JLabel 从 JFrame 中的一个 JPanel 拖到同一 JFrame 中另一个 JPanel 的 JTextField 上?

How should I drag a JLabel from one JPanel in a JFrame onto a JTextField in another JPanel in the same JFrame?

简而言之,我想将 JLabel 的文本设置为 JPanel (pnlUser) 中的 JTextField 的文本,然后拖动 JLabel 穿过屏幕从 JPanel 到另一个 JTextField 在另一个 JPanel (pnlGrid).

这是详细信息。

我写了一个"Solitaire Scrabble"程序。用户可以将文本光标定位在网格单元格中(pnlGrid 中的 JTextField)并键入 "User letters" 列表中的字母(JTextField 中的 pnlUser) 或者用户可以模拟 从"User letters" 拖动一个字母并将其放入pnlGrid.

中的目标网格单元格

我说"simulate"是因为选中的字母实际上并没有被拖过屏幕。我使用鼠标指针 HAND_CURSOR 使 drag/drop 尽可能真实,但我还没有想出如何使 HAND_CURSOR "grab" 字母并物理拖动全线信达目的地。

实际上,字母突出显示但留在 "User letters" 区域,而 HAND_CURSOR 在拖动操作期间沿网格移动。当它到达 pnlGrid 中的目标单元格并释放鼠标按钮时,字母从 "User letters" 中删除并突然出现在网格单元格中。

所以这封信或多或少从 "User letters" 到一个网格单元格 "teleported"(让我振作起来,Scotty)。这太抽象了。我希望用户字母位于 HAND_CURSOR 手指的尖端,并沿着网格拖动到将放置它的网格单元格中,如下面的 3 张图片所示。

我已经使用 JLayeredPane 在一个小型测试程序(下面的源代码)中成功实现了它,但我无法在游戏中实现它。但是直到两天前我对 JLayeredPane 一无所知,所以我真的不知道自己在做什么。 (我改编了一个演示 JLayeredPane 的 Oracle 教程程序。)

我刚刚阅读了有关 "glass pane" 的内容,并认为在我下载该演示的源代码之前可能更容易实施,该演示的源代码很长,因此它是全新的并且更难适应.

所以我想在我沮丧地度过更多时间之前我应该​​问:

JLayeredPanesetGlassPane 方法合适吗?有没有更简单或更好的方法将 JLabel 从一个 JPanel 拖到另一个 JPanel

(程序中的做法是判断指向的是哪个"User letter",将那个字母存入JLabel,然后确保在mouseDragged期间HAND_CURSOR指尖正好在字母的底部中心。)

package mousemoveletter;
import javax.swing.*;
import javax.swing.border.*; 
import java.awt.*;
import static java.awt.Color.*;
import java.awt.event.*;
import static javax.swing.SwingUtilities.invokeLater;

public class LayeredPaneDemo extends JPanel 
{
  private static final int USER7 = 7;
  static Cursor HAND  = new Cursor(Cursor.HAND_CURSOR);
  static Cursor ARROW = new Cursor(Cursor.DEFAULT_CURSOR);

  private static JLayeredPane layeredPane;
  private static JLabel       lblToMove;
  private static JPanel       pnlUser;
  private static JPanel       pnlGrid;
  private static final JTextField[] txtUser = new JTextField[USER7];

  public LayeredPaneDemo()    // constructor
  {
    pnlGrid = new JPanel();
    setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
    layeredPane = new JLayeredPane();
    layeredPane.setPreferredSize(new Dimension(240, 240));
    pnlGrid.setSize(140, 140);
    pnlGrid.setBorder(new EtchedBorder(RED, GREEN));
    pnlGrid.setBackground(YELLOW);

    lblToMove = new JLabel("XXX");
    lblToMove.setSize(new Dimension(40,40));

    layeredPane.add(pnlGrid, 0,0);
    layeredPane.add(lblToMove, new Integer(0), -1);

    add(layeredPane);   
  }

  private static void createAndShowGUI() {
    JFrame frame = new JFrame("LayeredPaneDemo");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    JComponent newContentPane = new LayeredPaneDemo();
    newContentPane.setOpaque(true); //content panes must be opaque
    frame.setContentPane(newContentPane);
    makeUser();

    frame.add(pnlUser);

    frame.pack();
    frame.setVisible(true);
  }

  public static void main(String[] args) {
   invokeLater(new Runnable() 
   {
      public void run() { 
        createAndShowGUI(); 
      }
    });
  }

  private static void makeUser(){
    pnlUser = new JPanel(new GridLayout(1,USER7));
    pnlUser.setPreferredSize(new Dimension(225, 50));
    pnlUser.setBackground(Color.green);
    pnlUser.setBorder(BorderFactory.createLineBorder(Color.BLUE));
    for(int k = 0; k < USER7; k++)
    {
      txtUser[k] = new JTextField("" + (char)(Math.random()*26+65));
      txtUser[k].setName("" + k);
      txtUser[k].setEditable(false);
      txtUser[k].addMouseMotionListener(new MouseMotionAdapter() 
      {
        public void mouseDragged(MouseEvent e) 
        {
          lblToMove.setCursor(HAND);
          int w = Integer.parseInt(e.getComponent().getName());
          lblToMove.setText(txtUser[w].getText());
          layeredPane.setLayer(lblToMove, 0, 0);
          lblToMove.setLocation(e.getX() + (e.getComponent().getWidth())*w, 
                                e.getY() + layeredPane.getHeight() - e.getComponent().getHeight()/2);
        };
      });

      txtUser[k].addMouseListener(new MouseAdapter()
      {
        public void mouseReleased(MouseEvent e)
        {
          lblToMove.setCursor(ARROW);
        }          
      });
      pnlUser.add(txtUser[k]);
    }
  }
}

感谢@trashgod,我通过点击他的链接找到了这个example and variation;我根据自己对 "Scrabble" 的特殊需求调整了那里找到的棋盘 drag/drop。

下面的代码不是我的纸牌拼字游戏程序的最终代码,而是概念验证,可能可供其他希望将单元格从 1xN 网格拖动到MxM 网格。

        package components;
        import java.awt.*;
        import static java.awt.BorderLayout.NORTH;
        import static java.awt.BorderLayout.SOUTH;
        import java.awt.event.*;
        import static java.lang.Integer.parseInt;
        import javax.swing.*;
        import static javax.swing.WindowConstants.DISPOSE_ON_CLOSE;
        import javax.swing.event.MouseInputAdapter;

        public class ChessBoard //implements MouseListener, MouseMotionListener
        {
          static Point parentLocation;
          int homeRow, homeCol; // where to restore moved user letter if dropped on occupied cell

          static int    N     = 11; // NxN 'chessboard' squares
          static int    S     = 44; // square dimensions: SxS 
          static int    W         ; // chessboard dimensions: WxW 
          static int    USER7 = 7;
          static Font   dragFont;
          static JFrame frame;
          JLayeredPane  layeredPane;
          static JPanel gamePanel, // encompasses both pnlGrid and pnlUser
                        pnlGrid, 
                        pnlUser;
          JLabel        userDragLetter = new JLabel(); // main item to drag around or restore if needed
          int           xAdjustment, yAdjustment; // how to locate drops accurately

          String userLetters[] ;

          public ChessBoard() // constructor
          {
            W = S*N;
            dragFont = new Font("Courier", Font.PLAIN, S);

            userLetters = new String[USER7];
            for (int i = 0; i < USER7; i++)
              userLetters[i] = "" + (char)(65 + Math.random()*26);

            Dimension gridSize  = new Dimension(W,  W);  
            Dimension userSize  = new Dimension(W,      S);
            Dimension gameSize  = new Dimension(W, (W + S));

            frame               = new JFrame();
            frame.setSize(new Dimension(gameSize)); // DO NOT USE PREFERRED

            layeredPane = new JLayeredPane();
            layeredPane.setPreferredSize( gameSize ); // NO PREFERRED => NO GRID!

            gamePanel   = new JPanel();

// **EDIT** LOSE THIS LINE            gamePanel.setLayout(new BorderLayout());

            gamePanel.setPreferredSize(gameSize);

            pnlGrid     = new JPanel();
            pnlGrid.setLayout(new GridLayout(N, N));
            pnlGrid.setPreferredSize( gridSize );
            pnlGrid.setBounds(0, 0, gridSize.width, gridSize.height);

            pnlUser     = new JPanel();
            pnlUser.setLayout(new GridLayout(1, N));
            pnlUser.setPreferredSize(userSize);
            pnlUser.setBounds(0, gridSize.height, userSize.width, userSize.height);

            layeredPane.add(pnlGrid, JLayeredPane.DEFAULT_LAYER); // panels to drag over
            layeredPane.add(pnlUser, JLayeredPane.DEFAULT_LAYER); //  "         "

            for (int i = 0; i < N; i++){
              for (int j = 0; j < N; j++){
                JPanel square = new JPanel();
                square.setBackground( (i + j) % 2 == 0 ? Color.red : Color.white );
                pnlGrid.add( square );
              }
            }

            for (int i = 0; i < N; i++) {
              JPanel square = new JPanel(new BorderLayout());
              square.setBackground(Color.YELLOW);
              pnlUser.add(square);
            }

            for (int i = 0; i < USER7; i++)
              addPiece(i, 0, userLetters[i]);

            gamePanel.addMouseListener(new MouseInputAdapter()
            {
              public void mousePressed (MouseEvent e){mousePressedActionPerformed (e);}
              public void mouseReleased(MouseEvent e){mouseReleasedActionPerformed(e);}
            });

            gamePanel.addMouseMotionListener(new MouseMotionAdapter()
            {
              public void mouseDragged(MouseEvent me){mouseDraggedActionPerformed(me);}
            });

    // **EDIT: LOSE THE NEXT TWO LINES AND REPLACE BY THE LINE AFTER THEM** 


        //        gamePanel.add(layeredPane, NORTH);
        //        gamePanel.add(pnlUser,     SOUTH);
                 gamePanel.add(layeredPane);
              }

          private void addPiece(int col, int row, String glyph) {
            JLabel piece = new JLabel(glyph, JLabel.CENTER);
            piece.setFont(dragFont);
            JPanel panel = (JPanel) pnlUser.getComponent(col + row * N);
            piece.setName("piece " + glyph + " @ " + row + " " + col);
            panel.add(piece);
          }

          void mousePressedActionPerformed(MouseEvent e)
          {
            userDragLetter = null; // signal that we're not dragging if no piece is in the square

            gamePanel.setCursor(new Cursor(Cursor.HAND_CURSOR));

            Component c =  pnlGrid.findComponentAt(e.getX(), e.getY());
            if(c != null)
              return; // Illegal to click pnlGrid

            c =  pnlUser.findComponentAt(e.getX(), e.getY() - pnlGrid.getHeight()); 

            if(c ==  null | c instanceof JPanel)
              return; // letter already played; can't drag empty cell

            parentLocation = c.getParent().getLocation();
            xAdjustment = parentLocation.x - e.getX(); 
            yAdjustment = parentLocation.y - e.getY() + gamePanel.getHeight() - pnlUser.getHeight();

            userDragLetter = (JLabel)c;
            userDragLetter.setPreferredSize(new Dimension(S, S)); // prevent 2 letters in a square
            userDragLetter.setLocation(e.getX() + xAdjustment, e.getY() + yAdjustment);

            layeredPane.add(userDragLetter, JLayeredPane.DRAG_LAYER);

            homeRow = parseInt(userDragLetter.getName().substring(10,11)); // save restore location 
            homeCol = parseInt(userDragLetter.getName().substring(12,13));
          }

          void mouseDraggedActionPerformed(MouseEvent me)
          {
            if (userDragLetter == null)
              return; // nothing to drag

            int x = me.getX() + xAdjustment; // make sure grid cell will be chosen in-bounds
            int xMax = layeredPane.getWidth() - userDragLetter.getWidth();
            x = Math.min(x, xMax);
            x = Math.max(x, 0);

            int y = me.getY() + yAdjustment;
            int yMax = layeredPane.getHeight() - userDragLetter.getHeight();
            y = Math.min(y, yMax);
            y = Math.max(y, 0);

            if(y >= pnlGrid.getHeight())
              return; // can't drag to location off grid

            userDragLetter.setLocation(x, y); 
          }

          void mouseReleasedActionPerformed(MouseEvent e)
          {

    //**EDIT: CHANGED NEXT LINE**

             gamePanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));

            if (userDragLetter == null) 
              return; // nothing to drag; nothing to release

            //  Make sure the chess piece is no longer painted on the layered pane
            userDragLetter.setVisible(false);
            layeredPane.remove(userDragLetter);
            userDragLetter.setVisible(true);

            int xMax = layeredPane.getWidth() - userDragLetter.getWidth();
            int x = Math.min(e.getX(), xMax);
            x = Math.max(x, 0);

            int yMax = layeredPane.getHeight()- userDragLetter.getHeight();
            int y = Math.min(e.getY(), yMax);
            y = Math.max(y, 0);

            Component c =  pnlGrid.findComponentAt(x, y); // find deepest nested child component

            if(c == null) // then grid cell is unoccupied so ...
              c = pnlUser.findComponentAt(x, y); // see if there's a letter there ...

            if(c == null | (c instanceof JLabel)){ // and if illegal or there is one, put it back...
              userDragLetter.setLocation(parentLocation.x + xAdjustment, 
                                         parentLocation.y + yAdjustment + gamePanel.getHeight());
              userDragLetter.setVisible(true);
              addPiece(homeCol, homeRow,userDragLetter.getName().substring(6,7));
              layeredPane.remove(userDragLetter);
              return;
            }
            else // but if NO letter ...
            {
              Container parent = (Container)c;
              parent.add( userDragLetter );  // put one in the grid cell
              parent.validate();
            }
            userDragLetter.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
          }

          public static void main(String[] args)
          {
            new ChessBoard();
            frame.add(gamePanel);
            frame.setDefaultCloseOperation( DISPOSE_ON_CLOSE );
        //    frame.setResizable( false );
            frame.pack();
            frame.setLocationRelativeTo( null );
            frame.setVisible(true);
          }
        }