Absolute Positioning Graphic JPanel Inside JFrame

2019-01-03 06:57发布

I'm trying to improve my understanding of Java, particularly Java GUI, by making a puzzle program. Currently the user selects an image, which is cut up into a specified number of pieces. The pieces are drawn randomly to the screen but they seem to be covered by blank portions of other pieces, and not all of them show up, but I can print out all the coordinates. I am using absolute positioning because a LayoutManager didn't seem to work. I briefly tried layeredPanes but they confused me and didn't seem to solve the problem. I would really appreciate some help.
Here are the 2 relevant classes:

import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;

public class PuzzlePieceDriver extends JFrame
{
  private static Dimension SCREENSIZE = Toolkit.getDefaultToolkit().getScreenSize();
  private static final int HEIGHT = SCREENSIZE.height;
  private static final int WIDTH = SCREENSIZE.width;

  public static int MY_WIDTH;
  public static int MY_HEIGHT;

  private static BufferedImage image;


  private int xPieces = PuzzleMagicDriver.getXPieces();
  private int yPieces = PuzzleMagicDriver.getYPieces();

  private PuzzlePiece[] puzzle = new PuzzlePiece[xPieces*yPieces];

  public Container pane = this.getContentPane();
  private JLayeredPane layeredPane = new JLayeredPane();


  public PuzzlePieceDriver(ImageIcon myPuzzleImage)
  {
    MY_WIDTH = myPuzzleImage.getIconWidth()+(int)myPuzzleImage.getIconHeight()/2;
    MY_HEIGHT = myPuzzleImage.getIconHeight()+(int)myPuzzleImage.getIconHeight()/2;
    setTitle("Hot Puzz");
setSize(MY_WIDTH,MY_HEIGHT);
setLocationByPlatform(true);

pane.setLayout(null);


image = iconToImage(myPuzzleImage); //pass image into bufferedImage form

puzzle = createClip(image);

//pane.add(layeredPane);


setVisible(true);
  }//end constructor



  public static BufferedImage iconToImage(ImageIcon icon)
  {
    Image img = icon.getImage();
 int w = img.getWidth(null);
 int h = img.getHeight(null);
 BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
 Graphics g = image.createGraphics();
     // Paint the image onto the buffered image
    g.drawImage(img, 0, 0, null);
    g.dispose();

    return image;
  }//end BufferedImage

  protected int randomNumber(int min, int max)
  {
    int temp = 
    min + (int)(Math.random() * ((max - min) + 1));

 return temp;
  }//end randomNumber


  private PuzzlePiece[] createClip(BufferedImage passedImage)
  {

 int cw, ch;
 int w,h;
 w = image.getWidth(null);
 h = image.getHeight(null);
 cw = w/xPieces;
     ch = h/yPieces;

 int[] cells=new int[xPieces*yPieces];

 int dx, dy;

 BufferedImage clip = passedImage;

 //layeredPane.setPreferredSize(new Dimension(w,h));

    for (int x=0; x<xPieces; x++) 
      {
        int sx = x*cw;
        for (int y=0; y<yPieces; y++) 
            {
            int sy = y*ch;
            int cell = cells[x*xPieces+y];
            dx = (cell / xPieces) * cw;
            dy = (cell % yPieces) * ch;

            clip= passedImage.getSubimage(sx, sy, cw, ch);
    int myX = randomNumber(0,(int)w);
    int myY = randomNumber(0,(int)h);

    PuzzlePiece piece=new PuzzlePiece(clip,myX,myY);
    puzzle[x*xPieces+y]=piece;
    piece.setBounds(myX,myY,w,h);
    //layeredPane.setBounds(myX,myY,w,h);
    //layeredPane.add(piece,new Integer(x*xPieces+y));
    pane.add(piece);
    piece.repaint();

        }//end nested for
}//end for
return puzzle;
  }//end createClip

}//end class

Sorry if the spacing is a little messed up!

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;

public class PuzzlePiece extends JPanel
{
private Point imageCorner;      //the image's top-left corner location
private Point prevPt;               //mouse location for previous event
private Boolean insideImage =false;

private BufferedImage image;

public PuzzlePiece(BufferedImage clip, int x, int y)
{
 image = clip;
 imageCorner = new Point(x,y);
 //repaint();
}//end constructor

public void paintComponent(Graphics g)
{
     super.paintComponent(g);
 g.drawImage(image, (int)getImageCornerX(),(int)getImageCornerY(), this);
     System.out.println("paint "+getImageCornerX()+"   "+getImageCornerY());
    //repaint(); 
//g.dispose();
}//end paintComponent

public Point getImageCorner()
{
  return imageCorner;
}//end getImageCorner
public double getImageCornerY()
{
  return imageCorner.getY();
}//end getImageCornerY
public double getImageCornerX()
{
  return imageCorner.getX();
}//end getPoint


}//end class PuzzlePiece

Any help would be appreciated, I've gotten really stuck! Thanks!!

3条回答
在下西门庆
2楼-- · 2019-01-03 07:49

Try using setBorder(new LineBorder(Color.RED)) in your puzzle piece constructor to see where the bounds of your puzzle pieces are. If they are where you'd expect them to be, it's likely that your positioning is wrong. Also make your puzzle pieces extend JComponent instead, or use setOpaque(false) if you're extending JPanel.

查看更多
爷、活的狠高调
3楼-- · 2019-01-03 07:59

There are lots of suggestions I'd like to make, but first...

The way you choose a random position is off...

int myX = randomNumber(0,(int)w);
int myY = randomNumber(0,(int)h);

This allows duplicate position's to be generated (and overlaying cells)

UPDATES (using a layout manager)

Okay, so this is a slight shift in paradigm. Rather then producing a clip and passing it to the piece, I allowed the piece to make chooses about how it was going to render the the piece. Instead, I passed it the Rectangle it was responsible for.

This means, you could simply use something like setCell(Rectangle) to make a piece change (unless you're hell bent on drag'n'drop ;))

I ended up using Board panel due to some interesting behavior under Java 7, but that's another question ;)

package puzzel;

import java.awt.BorderLayout;
import java.awt.EventQueue;
import javax.swing.*;

public class PuzzlePieceDriver extends JFrame {


    public PuzzlePieceDriver(ImageIcon myPuzzleImage) {

        setTitle("Hot Puzz");

        setDefaultCloseOperation(EXIT_ON_CLOSE);

        setLayout(new BorderLayout());
        add(new Board(myPuzzleImage));

        pack();

        setVisible(true);


    }//end constructor

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {

                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException ex) {
                } catch (InstantiationException ex) {
                } catch (IllegalAccessException ex) {
                } catch (UnsupportedLookAndFeelException ex) {
                }

                ImageIcon image = new ImageIcon(PuzzlePieceDriver.class.getResource("/issue459.jpg"));

                PuzzlePieceDriver driver = new PuzzlePieceDriver(image);
                driver.setLocationRelativeTo(null);
                driver.setVisible(true);

            }
        });

    }
}//end class

A piece panel... The panel overrides the preferred and minimum size methods...while it works for this example, it's probably better to use setPreferredSize and setMiniumumSize instead ;)

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package puzzel;

import javax.swing.*;
import java.awt.*;
import java.awt.image.*;

public class PuzzlePiece extends JPanel {

    private BufferedImage masterImage;
    private Rectangle pieceBounds;
    private BufferedImage clip;

    public PuzzlePiece(BufferedImage image, Rectangle bounds) {

        masterImage = image;
        pieceBounds = bounds;

        // Make sure the rectangle fits the image
        int width = Math.min(pieceBounds.x + pieceBounds.width, image.getWidth() - pieceBounds.x);
        int height = Math.min(pieceBounds.y + pieceBounds.height, image.getHeight() - pieceBounds.y);

        clip = image.getSubimage(pieceBounds.x, pieceBounds.y, width, height);

    }//end constructor

    @Override
    public Dimension getPreferredSize() {

        return pieceBounds.getSize();

    }

    @Override
    public Dimension getMinimumSize() {

        return getPreferredSize();

    }

    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        int x = 0;
        int y = 0;

        g.drawImage(clip, x, y, this);

        g.setColor(Color.RED);
        g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);

    }//end paintComponent

}//end class PuzzlePiece

The board panel...used mostly because of some interesting issues I was having with Java 7...Implements a MouseListener, when you run the program, click the board, it's fun ;)

package puzzel;

import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JPanel;

/**
 *
 * @author shane
 */
public class Board extends JPanel {

    public static final int X_PIECES = 4;
    public static final int Y_PIECES = 4;
    private PuzzlePiece[] puzzle = new PuzzlePiece[X_PIECES * Y_PIECES];

    private static BufferedImage image;

    public Board(ImageIcon myPuzzleImage) {

        image = iconToImage(myPuzzleImage); //pass image into bufferedImage form

        puzzle = createClip();

        addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {

                removeAll();

                invalidate();

                createClip();

//                doLayout();

                invalidate();
                revalidate();
                repaint();

            }
        });

    }

    public static BufferedImage iconToImage(ImageIcon icon) {
        Image img = icon.getImage();
        int w = img.getWidth(null);
        int h = img.getHeight(null);
        BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
        Graphics g = image.createGraphics();
        // Paint the image onto the buffered image
        g.drawImage(img, 0, 0, null);
        g.dispose();

        return image;
    }//end BufferedImage

    protected int randomNumber(int min, int max) {
        int temp = min + (int) (Math.random() * ((max - min) + 1));

        return temp;
    }//end randomNumber

    private PuzzlePiece[] createClip() {

        int cw, ch;
        int w, h;
        w = image.getWidth(null);
        h = image.getHeight(null);
        cw = w / X_PIECES;
        ch = h / Y_PIECES;

        // Generate a list of cell bounds
        List<Rectangle> lstBounds = new ArrayList<>(25);

        for (int y = 0; y < h; y += ch) {

            for (int x = 0; x < w; x += cw) {

                lstBounds.add(new Rectangle(x, y, cw, ch));

            }

        }

        BufferedImage clip = image;

        setLayout(new GridBagLayout());

        for (int x = 0; x < X_PIECES; x++) {
            for (int y = 0; y < Y_PIECES; y++) {

                // Get a random index
                int index = randomNumber(0, lstBounds.size() - 1);

                // Remove the bounds so we don't duplicate any positions
                Rectangle bounds = lstBounds.remove(index);

                PuzzlePiece piece = new PuzzlePiece(clip, bounds);
                puzzle[x * X_PIECES + y] = piece;

                GridBagConstraints gbc = new GridBagConstraints();
                gbc.gridx = x;
                gbc.gridy = y;
                gbc.fill = GridBagConstraints.BOTH;

                add(piece, gbc);
                piece.invalidate();
                piece.repaint();

            }//end nested for
        }//end for

        invalidate();
        repaint();

        return puzzle;
    }//end createClip

}

Now I know you eventually going to ask about how to move a piece, GridBagLayout has this wonderful method called getConstraints which allows you to retrieve the constraints used to layout the component in question. You could then modify the gridx and gridy values and use setConstraints to update it (don't forget to call invalidate and repaint ;))

I'd recommend having a read of How to Use GridBagLayout for more information ;)

Eventually, you'll end up with something like:

Puzzle 1Puzzle 2

查看更多
相关推荐>>
4楼-- · 2019-01-03 08:01

I was really intrigued by this idea, so I made another example, using a custom layout manager.

public class MyPuzzelBoard extends JPanel {

    public static final int GRID_X = 4;
    public static final int GRID_Y = 4;
    private BufferedImage image;

    public MyPuzzelBoard(BufferedImage image) {
        setLayout(new VirtualLayoutManager());
        setImage(image);

        addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getClickCount() == 2) {
                    removeAll();
                    generatePuzzel();
                } else {
                    Component comp = getComponentAt(e.getPoint());
                    if (comp != null && comp != MyPuzzelBoard.this) {
                        setComponentZOrder(comp, 0);
                        invalidate();
                        revalidate();
                        repaint();
                    }
                }
            }
        });
    }

    public void setImage(BufferedImage value) {
        if (value != image) {
            image = value;
            removeAll();
            generatePuzzel();
        }
    }

    public BufferedImage getImage() {
        return image;
    }

    protected float generateRandomNumber() {
        return (float) Math.random();
    }

    protected void generatePuzzel() {
        BufferedImage image = getImage();

        if (image != null) {
            int imageWidth = image.getWidth();
            int imageHeight = image.getHeight();

            int clipWidth = imageWidth / GRID_X;
            int clipHeight = imageHeight / GRID_Y;
            for (int x = 0; x < GRID_X; x++) {
                for (int y = 0; y < GRID_Y; y++) {

                    float xPos = generateRandomNumber();
                    float yPos = generateRandomNumber();
                    Rectangle bounds = new Rectangle((x * clipWidth), (y * clipHeight), clipWidth, clipHeight);
                    MyPiece piece = new MyPiece(image, bounds);
                    add(piece, new VirtualPoint(xPos, yPos));

                }
            }
        }

        invalidate();
        revalidate();
        repaint();
    }

    public class VirtualPoint {

        private float x;
        private float y;

        public VirtualPoint(float x, float y) {
            this.x = x;
            this.y = y;
        }

        public float getX() {
            return x;
        }

        public float getY() {
            return y;
        }

        public void setX(float x) {
            this.x = x;
        }

        public void setY(float y) {
            this.y = y;
        }
    }

    public class VirtualLayoutManager implements LayoutManager2 {

        private Map<Component, VirtualPoint> mapConstraints;

        public VirtualLayoutManager() {
            mapConstraints = new WeakHashMap<>(25);
        }

        @Override
        public void addLayoutComponent(Component comp, Object constraints) {
            if (constraints instanceof VirtualPoint) {
                mapConstraints.put(comp, (VirtualPoint) constraints);
            }
        }

        @Override
        public Dimension maximumLayoutSize(Container target) {
            return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
        }

        @Override
        public float getLayoutAlignmentX(Container target) {
            return 0.5f;
        }

        @Override
        public float getLayoutAlignmentY(Container target) {
            return 0.5f;
        }

        @Override
        public void invalidateLayout(Container target) {
        }

        @Override
        public void addLayoutComponent(String name, Component comp) {
        }

        @Override
        public void removeLayoutComponent(Component comp) {
            mapConstraints.remove(comp);
        }

        @Override
        public Dimension preferredLayoutSize(Container parent) {
            return new Dimension(400, 400);
        }

        @Override
        public Dimension minimumLayoutSize(Container parent) {
            return preferredLayoutSize(parent);
        }

        @Override
        public void layoutContainer(Container parent) {
            int width = parent.getWidth();
            int height = parent.getHeight();

            for (Component comp : parent.getComponents()) {

                VirtualPoint p = mapConstraints.get(comp);
                if (p != null) {

                    int x = Math.round(width * p.getX());
                    int y = Math.round(height * p.getY());

                    Dimension size = comp.getPreferredSize();

                    x = Math.min(x, width - size.width);
                    y = Math.min(y, height - size.height);

                    comp.setBounds(x, y, size.width, size.height);

                }
            }
        }
    }
}

Basically, this uses a "virtual" coordinate system, where by rather then supply absolute x/y positions in pixels, you provide them as percentage of the parent container. Now, to be honest, it wouldn't take much to convert back to absolute positioning, just this way, you also get layout scaling.

The example also demonstrates Z-reording (just in case) and the double click simple re-randomizes the puzzel

Oh, I also made the piece transparent (opaque = false)

Randomized layout

Oh, one thing I should mention, while going through this example, I found that it was possible to have pieces placed off screen (completely and partially).

You may want to check your positioning code to make sure that the images when they are laid out aren't been moved off screen ;)

查看更多
登录 后发表回答