当我拖动JLabel时,为什么出现在不同的位置?

我正在做一个跳棋比赛,performance的很奇怪。 到目前为止,我只是想要获得网格布局等基础知识,并且能够获得可移动的部分。 我的8乘8格开放得很好,所有的部分都正确地放在面板上,使用两个组件:

  • 面板JPanels在一个单独的class级,64总黑色和白色,并使用GridLayout填充到中心centerPanel 。 每个面板都有一个特定的行和列值( checkerPanel[row][col] )。
  • PIECES :他们实际上是JLabels的文本“•”他们被放大,以便他们填写面板。 他们被放置在红队前3排的白色面板上,并放在灰色队的最后3排。

当我尝试拖动一块时,从左上方的一块打开一个新的块。 当左上角的一块被拖到不动的旧件上时,旧件被删除,新的件代替。 我怎样才能使这些作品的行为正确,从旧的地方,而不是左上角? 查看java.awt.Graphics会更容易吗?


我已经看了这个StackOverflow的问题 ,并search了Oracle JavaDocs ,但仍然无法弄清楚什么是错的。 这是我目前的计划:

 import javax.swing.*; import java.awt.*; import java.awt.event.*; public class Checkers extends JFrame { private static final long serialVersionUID = 1l; private static final int GRIDSIZE = 8; private CheckerPanel[][] checkerPanel = new CheckerPanel[GRIDSIZE][GRIDSIZE]; public Checkers() { initGUI(); setTitle("Checkers"); pack(); setLocationRelativeTo(null); setVisible(true); setResizable(true); setDefaultCloseOperation(EXIT_ON_CLOSE); } private void initGUI() { JLabel titleLabel = new JLabel("Checkers"); Font titleFont = new Font(Font.SERIF, Font.BOLD, 32); titleLabel.setFont(titleFont); titleLabel.setHorizontalAlignment(JLabel.CENTER); titleLabel.setBackground(Color.BLACK); titleLabel.setForeground(Color.WHITE); titleLabel.setOpaque(true); add(titleLabel, BorderLayout.NORTH); JPanel centerPanel = new JPanel(); centerPanel.setLayout(new GridLayout(GRIDSIZE, GRIDSIZE)); // makes 8*8 grid add(centerPanel, BorderLayout.CENTER); for (int row=0; row<GRIDSIZE; row++) { for (int col=0; col<GRIDSIZE; col++) { JLabel grayPiece = new JLabel("•"); JLabel redPiece = new JLabel("•"); Font font = new Font(Font.SANS_SERIF, Font.PLAIN, 70); grayPiece.setFont(font); redPiece.setFont(font); grayPiece.setForeground(Color.GRAY); redPiece.setForeground(Color.RED); // used so that the pieces are not offset grayPiece.setBorder(BorderFactory.createEmptyBorder(-27, 0, 0, 0)); // used so that the pieces are not offset redPiece.setBorder(BorderFactory.createEmptyBorder(-27, 0, 0, 0)); checkerPanel[row][col] = new CheckerPanel(row, col); final MouseAdapter ma = new MouseAdapter() { // Clicked label. private JLabel selectedLabel = null; // Location of label in panel when it was clicked. private Point selectedLabelLocation = null; // Panel's click point. private Point panelClickPoint = null; public void mousePressed(final MouseEvent e) { final Component pressedComp = centerPanel.findComponentAt(e.getPoint()); if (pressedComp != null && pressedComp instanceof JLabel) { selectedLabel = (JLabel) pressedComp; selectedLabelLocation = selectedLabel.getLocation(); panelClickPoint = e.getPoint(); centerPanel.setComponentZOrder(selectedLabel, 0); selectedLabel.repaint(); } else { selectedLabel = null; //selectedLabelLocation = null; panelClickPoint = null; } if (selectedLabel != null && selectedLabelLocation != null && panelClickPoint != null) { final int newX = selectedLabelLocation.x; final int newY = selectedLabelLocation.y; selectedLabel.setLocation(newX, newY); } } public void mouseDragged(final MouseEvent e) { if (selectedLabel != null && selectedLabelLocation != null && panelClickPoint != null) { final Point newPanelClickPoint = e.getPoint(); // The new location is the press-location plus // the length of the drag for each axis: final int newX = selectedLabelLocation.x + (newPanelClickPoint.x - panelClickPoint.x), newY = selectedLabelLocation.y + (newPanelClickPoint.y - panelClickPoint.y); selectedLabel.setLocation(newX, newY); } } }; centerPanel.addMouseMotionListener(ma); //For mouseDragged(). centerPanel.addMouseListener(ma); //For mousePressed(). if ((row%2 == 0 && col%2 == 0) || ((row+1)%2 == 0 && (col+1)%2 == 0)) { checkerPanel[row][col].setBackground(Color.WHITE); checkerPanel[row][col].isWhite = true; } else { checkerPanel[row][col].setBackground(Color.BLACK); checkerPanel[row][col].isBlack = true; } if (row < 3 && checkerPanel[row][col].isWhite) { checkerPanel[row][col].add(redPiece); checkerPanel[row][col].isPiece = true; } else if (row > 4 && checkerPanel[row][col].isWhite) { checkerPanel[row][col].add(grayPiece); checkerPanel[row][col].isPiece = true; } centerPanel.add(checkerPanel[row][col]); } } } public static void main(String[] args) { try { String className = UIManager.getCrossPlatformLookAndFeelClassName(); UIManager.setLookAndFeel(className); } catch (Exception e) {} EventQueue.invokeLater(new Runnable() { public void run() { new Checkers(); } }); } class CheckerPanel extends JPanel { //the following class makes Panels private static final long serialVersionUID = 1L; private static final int SIZE = 50; public boolean isBlack = false; //if the panel is white public boolean isWhite = false; //if the panel is black public boolean isPiece = false; //if the panel has a piece public CheckerPanel(int row, int col) { Dimension size = new Dimension(SIZE, SIZE); setPreferredSize(size); } } } 

这是窗口的屏幕截图。 当一块被拖动时,一个新的从一个红色的左上角的地方出来。

板

这种影响是由您的代码中的许多问题引起的。

首先,请注意,您正在两个嵌套的for-loops中创建MouseAdapter 。 实际上,这意味着你不只是创建一个 MouseAdapter ,而是实际上创建了GRIDSIZE x GRIDSIZE = 64实例。

但是这不是问题的原因。 相反,它与你如何find用户点击的部分有关。 在mousePressed(...)的顶部,您正在调用centerPanel.findComponentAt(e.getPoint()) 。 但是,由于这种方法的定义方式,它实际上不会find你以后的作品。 这是为什么?

findComponentAt内部工作是这样的:在Container上调用它,遍历所有的子组件。 如果一个孩子本身就是Container一个实例,则recursion调用该孩子上的findComponentAt ; 否则检查给定的点是否在孩子的边界内。 这个方法的问题是(a)当没有孩子通过testing时,它返回this (而不是null ),和(b) JLabelinheritance自Container 。 这两点结合意味着:在centerPanel下面的组件树中的第一个叶子是一个非Container并且包含e.getPoint()或者只是一个Container将被返回。 由于你的作品是JLabelJLabel是容器, findComponentAt总是会返回第一个作品,不管它是否包含给定的作品。

但是等等,还有更多! 事实上,这些仍然不是您当前版本的代码中唯一的问题。 即使你把findComponentAt的调用replace成了一些真正find正确的东西,比如说:

 private JLabel findPiece(Point point) { for (int row = 0; row < GRIDSIZE; row++) { for (int col = 0; col < GRIDSIZE; col++) { CheckerPanel cp = checkerPanel[row][col]; Point cpPoint = cp.getLocation(); for (Component comp : cp.getComponents()) { if (comp instanceof JLabel && comp.contains(point.x - cpPoint.x, point.y - cpPoint.y)) { return (JLabel) comp; } } } } return null; } 

由于组件层次结构的组织方式,您仍然有问题。 您的centerPanel包含64个CheckerPanel ,而CheckerPanel又可以包含或不包含JPanel片断。 不幸的是,这意味着你不能从一个面板到另一个面板绘制一个合适的块运动,因为对于绘制来说,子组件( JLabels )被限制在它们所在的父容器( CheckerPanel )的边界上。 你试图通过调用centerPanel.setComponentZOrder(selectedLabel, 0)来绕过这个问题。 但是,如果您查看setComponentZOrder的文档,您将注意到以下内容:

如果组件是某个其他容器的子组件,则在添加到此容器之前,该组件将从该容器中移除。

这适用于你在这里的情况: selectedLabel最初不是centerPanel的孩子,而是一些CheckerPanel实例。 因此,通过调用setComponentZOrder您将从CheckerPanel centerPanel其删除,并将其添加到centerPanel ,这基本上破坏了您的整个数据结构,其中centerPanel包含CheckerPanel ,其中包含JLabel

在我看来,如果忘记游戏的组件层次结构,就可以让自己的生活变得更加轻松。 组件实际上是为桌面应用程序中的传统GUI而制作的:菜单,button,下拉列表等等。相反,如果您以如下方式实现centerPanel ,将会消除许多问题:覆盖您绘制的paintComponent方法包含所有代码的完整板,即, CenterPanel和这些代码块不再inheritance任何AWT或Swing类,而仅仅是用于数据结构化 – 如果您需要这些类的话!

如果你想更进一步,并有animation,如拖动一块特别平滑,你甚至可以忘记paintComponent ,而是使用一个BufferStrategy主动绘制在选定的帧速率,而不是重绘,当发生某些事件。 然而,这又是一个更多的参与,对于纯粹用于练习的跳棋游戏,你可以坚持前一段的paintComponent方法。