JLayeredPanel layout manager free moving objects

2019-03-01 04:30发布

I have a game board, with 8 pieces that I would like to be able to move them anywhere on the jpanel. Currently, I can only do flow or grid layout, however this does not yield the desired results. The long term goal, is to be able to independently click on a piece and drag it to desired location/position. (including on top of other pieces)

Any suggestions or input would be most welcome... (Thanks to Hovercraft Full Of Eels, MadProgrammer, peeskillet, Andrew Thompson for their earlier suggestions)....

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Image;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;

public class FireflyGameBoard extends JFrame implements MouseListener,
    MouseMotionListener
{
  JLayeredPane layeredPane;
  JPanel gameBoard;
  // JPanel background;
  JLabel gamePiece;
  int xAdjustment;
  int yAdjustment;
  ImageIcon bgicon;
  Image bg;
  String Rimagepath = "/Users/yournamehere/Documents/workspace/MotionGraphicsTest/resources/";

  public FireflyGameBoard()
  {
    Dimension boardSize = new Dimension(1920, 1080);

    bgicon = new ImageIcon(Rimagepath + "Backdroptest.png");

    bg = bgicon.getImage();

    ImagePanel background = new ImagePanel(new ImageIcon(Rimagepath
        + "Backdroptest.png").getImage());

    // Use a Layered Pane for this this application
    layeredPane = new JLayeredPane();
    getContentPane().add(layeredPane);
    layeredPane.setPreferredSize(boardSize);
    layeredPane.addMouseListener(this);
    layeredPane.addMouseMotionListener(this);

    // Add a chess board to the Layered Pane

    gameBoard = new ImagePanel(bg);
    layeredPane.add(background, JLayeredPane.DEFAULT_LAYER);
    layeredPane.add(gameBoard, JLayeredPane.MODAL_LAYER);
    gameBoard.setLayout(new FlowLayout());
    gameBoard.setPreferredSize(boardSize);
    gameBoard.setBounds(0, 0, boardSize.width, boardSize.height);

    // for (int i = 0; i < 64; i++)
    // {
    // JPanel square = new JPanel(new BorderLayout());
    // gameBoard.add(square);
    //
    // // square.setBackground(null);
    // }

    // Add a few pieces to the board

    JLabel piece = new JLabel(new ImageIcon(Rimagepath + "alliance.png"));
    gameBoard.add(piece);
    piece = new JLabel(new ImageIcon(Rimagepath + "piece2.png"));
    gameBoard.add(piece);
    piece = new JLabel(new ImageIcon(Rimagepath + "piece3.png"));
    gameBoard.add(piece);
    piece = new JLabel(new ImageIcon(Rimagepath + "piece4.png"));
    gameBoard.add(piece);
    piece = new JLabel(new ImageIcon(Rimagepath + "reaper.png"));
    gameBoard.add(piece);
    piece = new JLabel(new ImageIcon(Rimagepath + "piece6.png"));
    gameBoard.add(piece);
    piece = new JLabel(new ImageIcon(Rimagepath + "piece7.png"));
    gameBoard.add(piece);
    piece = new JLabel(new ImageIcon(Rimagepath + "piece8.png"));
    gameBoard.add(piece);
  }

  @Override
  public void mousePressed(MouseEvent e)
  {
    gamePiece = null;
    Component c = gameBoard.findComponentAt(e.getX(), e.getY());

    if (c instanceof JPanel)
      return;

    Point parentLocation = c.getParent().getLocation();
    xAdjustment = parentLocation.x - e.getX();
    yAdjustment = parentLocation.y - e.getY();
    gamePiece = (JLabel) c;
    gamePiece.setLocation(e.getX() + xAdjustment, e.getY() + yAdjustment);
    gamePiece.setSize(gamePiece.getWidth(), gamePiece.getHeight());
    layeredPane.add(gamePiece, JLayeredPane.DRAG_LAYER);
  }

  // Move the chess piece around

  @Override
  public void mouseDragged(MouseEvent me)
  {
    if (gamePiece == null)
      return;
    gamePiece.setLocation(me.getX() + xAdjustment, me.getY() + yAdjustment);
  }

  // Drop the chess piece back onto the chess board

  @Override
  public void mouseReleased(MouseEvent e)
  {
    if (gamePiece == null)
      return;

    gamePiece.setVisible(false);
    Component c = gameBoard.findComponentAt(e.getX(), e.getY());

    if (c instanceof JLabel)
    {
      Container parent = c.getParent();
      parent.remove(0);
      parent.add(gamePiece);
    }
    else
    {
      Container parent = (Container) c;
      parent.add(gamePiece);
    }

    gamePiece.setVisible(true);
  }

  @Override
  public void mouseClicked(MouseEvent e)
  {

  }

  @Override
  public void mouseMoved(MouseEvent e)
  {
  }

  @Override
  public void mouseEntered(MouseEvent e)
  {

  }

  @Override
  public void mouseExited(MouseEvent e)
  {

  }

  public static void main(String[] args)
  {
    JFrame frame = new FireflyGameBoard();
    frame.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
    frame.pack();
    frame.setResizable(true);
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
  }
}

P.S. This is not a school assignment, my g/f is an avid board game player, and I wanted to make her a digital version of some of her favorites....

1条回答
虎瘦雄心在
2楼-- · 2019-03-01 05:17

Drag'n'Drop is some serious work. When done right it can be really awesome, but be prepared for some serious heavy lifting and design work...

One approach is to try an generate self contained units of work, that is the piece is responsible for managing it's own drag and the cell/grid is responsible for managing the drop.

Drag/Piece

A Piece is a movable game piece, which can be dragged to a new location.

The piece itself is responsible for managing the DragGestureRecognizer which is used to initialise the drag process...

Because I wanted to display an icon in the piece, I choose to override JLabel, as it provides the core functionality for this...

import java.awt.Container;
import java.awt.Cursor;
import java.awt.Point;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragGestureRecognizer;
import java.awt.dnd.DragSource;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
import java.awt.image.BufferedImage;
import javax.swing.JLabel;

public class PieceLabel extends JLabel {
    private DragGestureHandler dragGestureHandler;
    private DragGestureRecognizer dgr;

    public PieceLabel() {
        setHorizontalAlignment(CENTER);
        setVerticalAlignment(CENTER);
    }

    @Override
    public void addNotify() {
        super.addNotify();
        if (dgr == null) {
            dragGestureHandler = new DragGestureHandler(this);
            dgr = DragSource.getDefaultDragSource().createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_MOVE, dragGestureHandler);
        }
    }

    @Override
    public void removeNotify() {
        if (dgr != null) {
            dgr.removeDragGestureListener(dragGestureHandler);
            dragGestureHandler = null;
        }
        dgr = null;
        super.removeNotify();
    }

    public static class DragGestureHandler implements DragGestureListener, DragSourceListener {

        private PieceLabel piece;
        private Container parent;

        public DragGestureHandler(PieceLabel child) {
            this.piece = child;
        }

        public PieceLabel getPiece() {
            return piece;
        }

        protected void setParent(Container parent) {
            this.parent = parent;
        }

        protected Container getParent() {
            return parent;
        }

        @Override
        public void dragGestureRecognized(DragGestureEvent dge) {
            // When the drag begins, we need to grab a reference to the
            // parent container so we can return it if the drop
            // is rejected
            Container parent = getPiece().getParent();
            setParent(parent);
            // Remove the panel from the parent.  If we don't do this, it
            // can cause serialization issues.  We could over come this
            // by allowing the drop target to remove the component, but that's
            // an argument for another day
            parent.remove(getPiece());
            // Update the display
            parent.invalidate();
            parent.repaint();
            // Create our transferable wrapper
            Transferable transferable = new PieceTransferable(getPiece());
            // Start the "drag" process...
            DragSource ds = dge.getDragSource();
            //            ds.startDrag(dge, Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR), transferable, this);
            BufferedImage image = BoardDrag.createBufferedImage(piece.getIcon(), piece);
            Point pp = piece.getLocation();
            Point dp = dge.getDragOrigin();
            int x = image.getWidth() / 2;
            int y = image.getHeight() / 2;
            ds.startDrag(dge, Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR), image, new Point(x, y), transferable, this);
        }

        @Override
        public void dragEnter(DragSourceDragEvent dsde) {
        }

        @Override
        public void dragOver(DragSourceDragEvent dsde) {
        }

        @Override
        public void dropActionChanged(DragSourceDragEvent dsde) {
        }

        @Override
        public void dragExit(DragSourceEvent dse) {
        }

        @Override
        public void dragDropEnd(DragSourceDropEvent dsde) {
            // If the drop was not sucessful, we need to
            // return the component back to it's previous
            // parent
            if (!dsde.getDropSuccess()) {
                getParent().add(getPiece());
                getParent().invalidate();
                getParent().repaint();
            }
        }
    }

}

Drop/Cell/Grid

A cell/grid is just that, it makes up a single element within the over all grid/board. It can contain a Piece (and in fact, you could easily configure it do reject everything else)

This manages the DropTarget, which is responsible for detecting when something is dropped onto it...

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetContext;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import javax.swing.JComponent;
import javax.swing.JPanel;

public class Cell extends JPanel {
    private DropTarget dropTarget;
    private DropHandler dropHandler;

    public Cell() {
        setLayout(new BorderLayout());
    }

    @Override
    public void addNotify() {
        super.addNotify();
        if (dropHandler == null) {
            dropHandler = new DropHandler();
        }
        if (dropTarget == null) {
            dropTarget = new DropTarget(this, DnDConstants.ACTION_MOVE, dropHandler, true);
        }
    }

    @Override
    public void removeNotify() {
        if (dropTarget != null) {
            dropTarget.removeDropTargetListener(dropHandler);
        }
        dropTarget = null;
        dropHandler = null;
        super.removeNotify();
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(48, 48);
    }

    public class DropHandler implements DropTargetListener {

        @Override
        public void dragEnter(DropTargetDragEvent dtde) {
            // Determine if can actual process the contents comming in.
            // You could try and inspect the transferable as well, but
            // There is an issue on the MacOS under some circumstances
            // where it does not actually bundle the data until you accept the
            // drop.
            if (dtde.isDataFlavorSupported(PieceDataFlavor.SHARED_INSTANCE)) {
                dtde.acceptDrag(DnDConstants.ACTION_MOVE);
            } else {
                dtde.rejectDrag();
            }
        }

        @Override
        public void dragOver(DropTargetDragEvent dtde) {
        }

        @Override
        public void dropActionChanged(DropTargetDragEvent dtde) {
        }

        @Override
        public void dragExit(DropTargetEvent dte) {
        }

        @Override
        public void drop(DropTargetDropEvent dtde) {
            boolean success = false;
            // Basically, we want to unwrap the present...
            if (dtde.isDataFlavorSupported(PieceDataFlavor.SHARED_INSTANCE)) {
                Transferable transferable = dtde.getTransferable();
                try {
                    Object data = transferable.getTransferData(PieceDataFlavor.SHARED_INSTANCE);
                    if (data instanceof PieceLabel) {
                        PieceLabel piece = (PieceLabel) data;
                        DropTargetContext dtc = dtde.getDropTargetContext();
                        Component component = dtc.getComponent();
                        if (component instanceof JComponent) {
                            Container parent = piece.getParent();
                            if (parent != null) {
                                parent.remove(piece);
                            }
                            ((JComponent) component).add(piece);
                            success = true;
                            dtde.acceptDrop(DnDConstants.ACTION_MOVE);
                            invalidate();
                            repaint();
                        } else {
                            success = false;
                            dtde.rejectDrop();
                        }
                    } else {
                        success = false;
                        dtde.rejectDrop();
                    }
                } catch (Exception exp) {
                    success = false;
                    dtde.rejectDrop();
                    exp.printStackTrace();
                }
            } else {
                success = false;
                dtde.rejectDrop();
            }
            dtde.dropComplete(success);
        }
    }

}

The Glue

In Drag'n'Drop, there are two special classes which glue the drag to the drop....

The DataFlavor

The DataFlavor is responsible for providing a means by which disconnected elements can determine not only what is been transferred, but how that data should be reconstituted...

For simplicity, I just PieceLabel.class

import java.awt.datatransfer.DataFlavor;

public class PieceDataFlavor extends DataFlavor {
    public static final PieceDataFlavor SHARED_INSTANCE = new PieceDataFlavor();

    public PieceDataFlavor() {
        super(PieceLabel.class, null);
    }

}

The Transferable

The Transferable is a wrapper class which allows data to be moved from one location to another, like the clipboard, for example.

This example is reasonably simple, but you could imagine that a Transferable might contain multiple DataFlavors, depending on which DataFlavor you want could change the type (or the manner in which you get) the data.

import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;

public class PieceTransferable implements Transferable {
    private final DataFlavor[] flavors = new DataFlavor[]{PieceDataFlavor.SHARED_INSTANCE};
    private final PieceLabel piece;

    public PieceTransferable(PieceLabel piece) {
        this.piece = piece;
    }

    @Override
    public DataFlavor[] getTransferDataFlavors() {
        return flavors;
    }

    @Override
    public boolean isDataFlavorSupported(DataFlavor flavor) {
        // Okay, for this example, this is over kill, but makes it easier
        // to add new flavor support by subclassing
        boolean supported = false;
        for (DataFlavor mine : getTransferDataFlavors()) {
            if (mine.equals(flavor)) {
                supported = true;
                break;
            }
        }
        return supported;
    }

    public PieceLabel getPanel() {
        return piece;
    }

    @Override
    public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
        Object data = null;
        if (isDataFlavorSupported(flavor)) {
            data = getPanel();
        } else {
            throw new UnsupportedFlavorException(flavor);
        }
        return data;
    }

}

Putting it all together

Because the components are self contained, putting it together is actually really easy...they basically take care of themselves...

What a drag

import java.awt.Color;
import java.awt.GridLayout;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JPanel;

public class BoardPane extends JPanel {

    public BoardPane() {
        setLayout(new GridLayout(8, 8));
        int index = 0;
        for (int row = 0; row < 8; row++) {
            for (int col = 0; col < 8; col++) {
                Cell cell = new Cell();
                if (index % 2 == 0) {
                    cell.setBackground(Color.WHITE);
                } else {
                    cell.setBackground(Color.BLACK);
                }
                add(cell);
                index++;
            }
            index++;
        }
        try {
            PieceLabel label = new PieceLabel();
            BufferedImage image = ImageIO.read(getClass().getResource("/Piece01.png"));
            label.setIcon(new ImageIcon(image));
            setCellPiece(label, 0, 0);
        } catch (IOException ex) {
            Logger.getLogger(BoardDrag.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void setCellPiece(PieceLabel label, int row, int col) {
        int index = (row * 8) + col;
        Cell cell = (Cell) getComponent(index);
        cell.removeAll();
        cell.add(label);
    }

}

What's the catch?

That's right, you don't get everything of free.

You will have to implement the logic required to determine if a move is valid or not. The logic should be implemented in such away as to actively reject the drag. This might require you to add more information to the Transferable so you can determine the start cell, for example.

I'd personally be looking to implement some kind of "rules" engine, which can be used by your DnD API so that it becomes pluggable

查看更多
登录 后发表回答