javaswingdrag-and-dropjlayeredpaneglasspane

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


In short, I want to set the text of a JLabel to be that of a JTextField in a JPanel (pnlUser) and then drag the JLabel across the screen from JPanel onto another JTextField in another JPanel (pnlGrid).

Here are the details.

I have written a "Solitaire Scrabble" program. The user can either position the text cursor in a grid cell (a JTextField in pnlGrid) and type a letter that is in the list of "User letters" (a JTextField in pnlUser) OR the user can simulate dragging a letter from "User letters" and dropping it into the destination grid cell in pnlGrid.

I say "simulate" because the selected letter is not actually dragged across the screen. I use the mouse pointer HAND_CURSOR to make the drag/drop as real as possible, but I haven't figured out how to make the HAND_CURSOR "grab" the letter and physically drag the letter across the board to its destination.

As it is, the letter is highlighted but left in the "User letters" area while the HAND_CURSOR moves along the grid during the drag operation. When it gets to the destination cell in pnlGrid and the mouse button is released, the letter is erased from "User letters" and suddenly appears in the grid cell.

So the letter is more or less "teleported" (beam me up, Scotty) from "User letters" to a grid cell. This is too abstract. I want the user letter to be at the tip of the HAND_CURSOR's pointing finger and be dragged along the grid into the grid cell where it will be dropped, as shown in the 3 pictures below.

enter image description hereenter image description hereenter image description here

I've successfully made it happen in a small test program (source below) using JLayeredPane, but I can't make it happen in the game. But I knew nothing about JLayeredPane until two days ago so I don't really know what I'm doing. (I adapted an Oracle tutorial program that demos JLayeredPane.)

I just read about the "glass pane" and thought it would maybe be easier to implement until I downloaded the source for that demo, which is quite long, so since it's totally new and will be even harder to adapt.

So I thought before I spend more hours in frustration I should ask:

Is a JLayeredPane or a setGlassPane approach appropriate? Is there an easier or better way to drag a JLabel from one JPanel onto another another JPanel?

(The approach in the program is to determine which "User letter" is being pointed at, store that letter in a JLabel, and then make sure that during mouseDragged the HAND_CURSOR fingertip is right at the bottom center of the letter.)

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]);
    }
  }
}

Solution

  • Thanks to @trashgod, I figured it out by following his links to this example and variation; I adapted the drag/drop of the chessboard found there to my own particular needs for "Scrabble".

    The code below is not final code for my Solitaire Scrabble program, but proof-of-concept, possibly usable by others wishing to drag a cell from a 1xN grid onto a MxM grid.

            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);
              }
            }